Add missing fix mediator balance (#510)

This commit is contained in:
Kirill Fedoseev 2020-10-15 20:27:12 +03:00 committed by GitHub
parent f70426c841
commit 2def0318bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 475 additions and 118 deletions

View File

@ -102,7 +102,12 @@ contract ERC677BridgeToken is IBurnableMintableERC677Token, DetailedERC20, Burna
revert();
}
function claimTokens(address _token, address _to) public onlyOwner validAddress(_to) {
/**
* @dev Withdraws the erc20 tokens or native coins from this contract.
* @param _token address of the claimed token or address(0) for native coins.
* @param _to address of the tokens/coins receiver.
*/
function claimTokens(address _token, address _to) external onlyOwner {
claimValues(_token, _to);
}

View File

@ -4,5 +4,5 @@ import "../interfaces/ERC677.sol";
contract IBurnableMintableERC677Token is ERC677 {
function mint(address _to, uint256 _amount) public returns (bool);
function burn(uint256 _value) public;
function claimTokens(address _token, address _to) public;
function claimTokens(address _token, address _to) external;
}

View File

@ -50,10 +50,6 @@ contract BasicBridge is
return uintStorage[REQUIRED_BLOCK_CONFIRMATIONS];
}
function claimTokens(address _token, address _to) public onlyIfUpgradeabilityOwner validAddress(_to) {
claimValues(_token, _to);
}
/**
* @dev Internal function for updating fallback gas price value.
* @param _gasPrice new value for the gas price, zero gas price is allowed.

View File

@ -1,18 +1,31 @@
pragma solidity 0.4.24;
import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";
import "../libraries/Address.sol";
import "../libraries/SafeERC20.sol";
/**
* @title Claimable
* @dev Implementation of the claiming utils that can be useful for withdrawing accidentally sent tokens that are not used in bridge operations.
*/
contract Claimable {
bytes4 internal constant TRANSFER = 0xa9059cbb; // transfer(address,uint256)
using SafeERC20 for address;
/**
* Throws if a given address is equal to address(0)
*/
modifier validAddress(address _to) {
require(_to != address(0));
/* solcov ignore next */
_;
}
function claimValues(address _token, address _to) internal {
/**
* @dev Withdraws the erc20 tokens or native coins from this contract.
* Caller should additionally check that the claimed token is not a part of bridge operations (i.e. that token != erc20token()).
* @param _token address of the claimed token or address(0) for native coins.
* @param _to address of the tokens/coins receiver.
*/
function claimValues(address _token, address _to) internal validAddress(_to) {
if (_token == address(0)) {
claimNativeCoins(_to);
} else {
@ -20,35 +33,23 @@ contract Claimable {
}
}
/**
* @dev Internal function for withdrawing all native coins from the contract.
* @param _to address of the coins receiver.
*/
function claimNativeCoins(address _to) internal {
uint256 value = address(this).balance;
Address.safeSendValue(_to, value);
}
/**
* @dev Internal function for withdrawing all tokens of ssome particular ERC20 contract from this contract.
* @param _token address of the claimed ERC20 token.
* @param _to address of the tokens receiver.
*/
function claimErc20Tokens(address _token, address _to) internal {
ERC20Basic token = ERC20Basic(_token);
uint256 balance = token.balanceOf(this);
safeTransfer(_token, _to, balance);
}
function safeTransfer(address _token, address _to, uint256 _value) internal {
bytes memory returnData;
bool returnDataResult;
bytes memory callData = abi.encodeWithSelector(TRANSFER, _to, _value);
assembly {
let result := call(gas, _token, 0x0, add(callData, 0x20), mload(callData), 0, 32)
returnData := mload(0)
returnDataResult := mload(0)
switch result
case 0 {
revert(0, 0)
}
}
// Return data is optional
if (returnData.length > 0) {
require(returnDataResult);
}
_token.safeTransfer(_to, balance);
}
}

View File

@ -100,7 +100,8 @@ contract InterestReceiver is ERC677Receiver, Ownable, Claimable, TokenSwapper {
* @param _token address of claimed token, address(0) for native
* @param _to address of tokens receiver
*/
function claimTokens(address _token, address _to) external onlyOwner validAddress(_to) {
function claimTokens(address _token, address _to) external onlyOwner {
// Only tokens other than CHAI/DAI can be claimed from this contract.
require(_token != address(chaiToken()) && _token != address(daiToken()));
claimValues(_token, _to);
}

View File

@ -0,0 +1,27 @@
pragma solidity 0.4.24;
import "../upgradeability/EternalStorage.sol";
/**
* @title MediatorBalanceStorage
* @dev Storage helpers for the mediator balance tracking.
*/
contract MediatorBalanceStorage is EternalStorage {
bytes32 internal constant MEDIATOR_BALANCE = 0x3db340e280667ee926fa8c51e8f9fcf88a0ff221a66d84d63b4778127d97d139; // keccak256(abi.encodePacked("mediatorBalance"))
/**
* @dev Tells the expected mediator balance.
* @return the current expected mediator balance.
*/
function mediatorBalance() public view returns (uint256) {
return uintStorage[MEDIATOR_BALANCE];
}
/**
* @dev Sets the expected mediator balance of the contract.
* @param _balance the new expected mediator balance value.
*/
function _setMediatorBalance(uint256 _balance) internal {
uintStorage[MEDIATOR_BALANCE] = _balance;
}
}

View File

@ -4,17 +4,16 @@ import "./BasicAMBErc20ToNative.sol";
import "../BaseERC677Bridge.sol";
import "../ReentrancyGuard.sol";
import "../../libraries/SafeERC20.sol";
import "../MediatorBalanceStorage.sol";
/**
* @title ForeignAMBErc20ToNative
* @dev Foreign mediator implementation for erc20-to-native bridge intended to work on top of AMB bridge.
* It is design to be used as implementation contract of EternalStorageProxy contract.
*/
contract ForeignAMBErc20ToNative is BasicAMBErc20ToNative, ReentrancyGuard, BaseERC677Bridge {
contract ForeignAMBErc20ToNative is BasicAMBErc20ToNative, ReentrancyGuard, BaseERC677Bridge, MediatorBalanceStorage {
using SafeERC20 for ERC677;
bytes32 internal constant MEDIATOR_BALANCE = 0x3db340e280667ee926fa8c51e8f9fcf88a0ff221a66d84d63b4778127d97d139; // keccak256(abi.encodePacked("mediatorBalance"))
/**
* @dev Stores the initial parameters of the mediator.
* @param _bridgeContract the address of the AMB bridge contract.
@ -109,25 +108,18 @@ contract ForeignAMBErc20ToNative is BasicAMBErc20ToNative, ReentrancyGuard, Base
* @param _token address of the token, if it is not provided, native tokens will be transferred.
* @param _to address that will receive the locked tokens on this contract.
*/
function claimTokens(address _token, address _to) public onlyIfUpgradeabilityOwner validAddress(_to) {
function claimTokens(address _token, address _to) external onlyIfUpgradeabilityOwner {
// Since bridged tokens are locked at this contract, it is not allowed to claim them with the use of claimTokens function
require(_token != address(_erc677token()));
claimValues(_token, _to);
}
/**
* @dev Tells the token balance of the contract.
* @return the current tracked token balance of the contract.
*/
function mediatorBalance() public view returns (uint256) {
return uintStorage[MEDIATOR_BALANCE];
}
/**
* @dev Allows to send to the other network the amount of locked native tokens that can be forced into the contract
* @dev Allows to send to the other network the amount of locked tokens that can be forced into the contract
* without the invocation of the required methods.
* @param _receiver the address that will receive the tokens on the other network
* @param _receiver the address that will receive the native coins on the other network
*/
function fixMediatorBalance(address _receiver) public onlyIfUpgradeabilityOwner {
function fixMediatorBalance(address _receiver) external onlyIfUpgradeabilityOwner validAddress(_receiver) {
uint256 balance = _erc677token().balanceOf(address(this));
uint256 expectedBalance = mediatorBalance();
require(balance > expectedBalance);
@ -192,12 +184,4 @@ contract ForeignAMBErc20ToNative is BasicAMBErc20ToNative, ReentrancyGuard, Base
function bridgeContractOnOtherSide() internal view returns (address) {
return mediatorContractOnOtherSide();
}
/**
* @dev Sets the updated token balance of the contract.
* @param _balance the new token balance of the contract.
*/
function _setMediatorBalance(uint256 _balance) internal {
uintStorage[MEDIATOR_BALANCE] = _balance;
}
}

View File

@ -202,12 +202,13 @@ contract HomeAMBErc20ToNative is BasicAMBErc20ToNative, BlockRewardBridge, HomeF
}
/**
* @dev Allows to transfer any locked token on this contract that is not part of the bridge operations.
* Native tokens are not allowed to be claimed.
* @param _token address of the token.
* @dev Allows to transfer any locked tokens or native coins on this contract.
* @param _token address of the token, address(0) for native coins.
* @param _to address that will receive the locked tokens on this contract.
*/
function claimTokens(address _token, address _to) external onlyIfUpgradeabilityOwner validAddress(_to) {
function claimTokens(address _token, address _to) external onlyIfUpgradeabilityOwner {
// In case native coins were forced into this contract by using a selfdestruct opcode,
// they should be handled by a call to fixMediatorBalance, instead of using a claimTokens function.
require(_token != address(0));
claimValues(_token, _to);
}
@ -217,7 +218,7 @@ contract HomeAMBErc20ToNative is BasicAMBErc20ToNative, BlockRewardBridge, HomeF
* without the invocation of the required methods.
* @param _receiver the address that will receive the tokens on the other network
*/
function fixMediatorBalance(address _receiver) external onlyIfUpgradeabilityOwner {
function fixMediatorBalance(address _receiver) external onlyIfUpgradeabilityOwner validAddress(_receiver) {
uint256 balance = address(this).balance;
uint256 available = maxAvailablePerTx();
if (balance > available) {

View File

@ -146,8 +146,4 @@ contract BasicAMBErc677ToErc677 is
passMessage(recipient, recipient, valueToUnlock);
}
}
function claimTokens(address _token, address _to) public onlyIfUpgradeabilityOwner validAddress(_to) {
claimValues(_token, _to);
}
}

View File

@ -2,13 +2,14 @@ pragma solidity 0.4.24;
import "./BasicAMBErc677ToErc677.sol";
import "../../libraries/SafeERC20.sol";
import "../MediatorBalanceStorage.sol";
/**
* @title ForeignAMBErc677ToErc677
* @dev Foreign side implementation for erc677-to-erc677 mediator intended to work on top of AMB bridge.
* It is designed to be used as an implementation contract of EternalStorageProxy contract.
*/
contract ForeignAMBErc677ToErc677 is BasicAMBErc677ToErc677 {
contract ForeignAMBErc677ToErc677 is BasicAMBErc677ToErc677, MediatorBalanceStorage {
using SafeERC20 for ERC677;
/**
@ -19,7 +20,10 @@ contract ForeignAMBErc677ToErc677 is BasicAMBErc677ToErc677 {
function executeActionOnBridgedTokens(address _recipient, uint256 _value) internal {
uint256 value = _unshiftValue(_value);
bytes32 _messageId = messageId();
_setMediatorBalance(mediatorBalance().sub(value));
erc677token().safeTransfer(_recipient, value);
emit TokensBridged(_recipient, value, _messageId);
}
@ -44,6 +48,26 @@ contract ForeignAMBErc677ToErc677 is BasicAMBErc677ToErc677 {
bridgeSpecificActionsOnTokenTransfer(token, msg.sender, _value, abi.encodePacked(_receiver));
}
/**
* @dev Allows to send to the other network the amount of locked tokens that can be forced into the contract
* without the invocation of the required methods.
* @param _receiver the address that will receive the tokens on the other network
*/
function fixMediatorBalance(address _receiver) external onlyIfUpgradeabilityOwner validAddress(_receiver) {
uint256 balance = _erc677token().balanceOf(address(this));
uint256 expectedBalance = mediatorBalance();
require(balance > expectedBalance);
uint256 diff = balance - expectedBalance;
uint256 available = maxAvailablePerTx();
require(available > 0);
if (diff > available) {
diff = available;
}
addTotalSpentPerDay(getCurrentDay(), diff);
_setMediatorBalance(expectedBalance.add(diff));
passMessage(_receiver, _receiver, diff);
}
/**
* @dev Executes action on deposit of bridged tokens
* @param _from address of tokens sender
@ -57,11 +81,28 @@ contract ForeignAMBErc677ToErc677 is BasicAMBErc677ToErc677 {
bytes _data
) internal {
if (!lock()) {
_setMediatorBalance(mediatorBalance().add(_value));
passMessage(_from, chooseReceiver(_from, _data), _value);
}
}
/**
* @dev Unlock back the amount of tokens that were bridged to the other network but failed.
* @param _recipient address that will receive the tokens
* @param _value amount of tokens to be received
*/
function executeActionOnFixedTokens(address _recipient, uint256 _value) internal {
_setMediatorBalance(mediatorBalance().sub(_value));
erc677token().safeTransfer(_recipient, _value);
}
/**
* @dev Allows to transfer any locked token on this contract that is not part of the bridge operations.
* @param _token address of the token, if it is not provided, native tokens will be transferred.
* @param _to address that will receive the locked tokens on this contract.
*/
function claimTokens(address _token, address _to) external onlyIfUpgradeabilityOwner {
require(_token != address(_erc677token()));
claimValues(_token, _to);
}
}

View File

@ -64,4 +64,14 @@ contract ForeignStakeTokenMediator is BasicStakeTokenMediator {
token.transfer(_recipient, _value);
}
}
/**
* @dev Allows to transfer any locked token on this contract other than stake token.
* @param _token address of the token, if it is not provided, native tokens will be transferred.
* @param _to address that will receive the locked tokens on this contract.
*/
function claimTokens(address _token, address _to) external onlyIfUpgradeabilityOwner {
require(_token != address(_erc677token()));
claimValues(_token, _to);
}
}

View File

@ -35,6 +35,17 @@ contract HomeAMBErc677ToErc677 is BasicAMBErc677ToErc677 {
}
}
/**
* @dev Withdraws the erc20 tokens or native coins from this contract.
* @param _token address of the claimed token or address(0) for native coins.
* @param _to address of the tokens/coins receiver.
*/
function claimTokens(address _token, address _to) external onlyIfUpgradeabilityOwner {
// For home side of the bridge, tokens are not locked at the contract, they are minted and burned instead.
// So, its is safe to allow claiming of any tokens. Native coins are allowed as well.
claimValues(_token, _to);
}
function executeActionOnFixedTokens(address _recipient, uint256 _value) internal {
IBurnableMintableERC677Token(erc677token()).mint(_recipient, _value);
}

View File

@ -154,6 +154,17 @@ contract HomeStakeTokenMediator is BasicStakeTokenMediator, HomeStakeTokenFeeMan
}
}
/**
* @dev Withdraws the erc20 tokens or native coins from this contract.
* @param _token address of the claimed token or address(0) for native coins.
* @param _to address of the tokens/coins receiver.
*/
function claimTokens(address _token, address _to) external onlyIfUpgradeabilityOwner {
// For home side of the bridge, tokens are not locked at the contract, they are minted and burned instead.
// So, its is safe to allow claiming of any tokens. Native coins are allowed as well.
claimValues(_token, _to);
}
/**
* @dev Executes action on relayed request to fix the failed transfer of tokens
* @param _recipient address of tokens receiver

View File

@ -81,13 +81,4 @@ contract BasicAMBNativeToErc20 is
) internal {
revert();
}
/**
* @dev Allows to transfer any locked token on this contract that is not part of the bridge operations.
* @param _token address of the token, if it is not provided, native tokens will be transferred.
* @param _to address that will receive the locked tokens on this contract.
*/
function claimTokens(address _token, address _to) public onlyIfUpgradeabilityOwner validAddress(_to) {
claimValues(_token, _to);
}
}

View File

@ -157,6 +157,17 @@ contract ForeignAMBNativeToErc20 is BasicAMBNativeToErc20, ReentrancyGuard, Base
IBurnableMintableERC677Token(erc677token()).mint(_feeManager, _fee);
}
/**
* @dev Allows to transfer any locked token on this contract that is not part of the bridge operations.
* @param _token address of the token, if it is not provided, native tokens will be transferred.
* @param _to address that will receive the locked tokens on this contract.
*/
function claimTokens(address _token, address _to) external onlyIfUpgradeabilityOwner {
// For foreign side of the bridge, tokens are not locked at the contract, they are minted and burned instead.
// So, its is safe to allow claiming of any tokens. Native coins are allowed as well.
claimValues(_token, _to);
}
/**
* @dev Allows to transfer any locked token on the ERC677 token contract.
* @param _token address of the token, if it is not provided, native tokens will be transferred.

View File

@ -1,5 +1,6 @@
pragma solidity 0.4.24;
import "../MediatorBalanceStorage.sol";
import "./BasicAMBNativeToErc20.sol";
/**
@ -7,9 +8,7 @@ import "./BasicAMBNativeToErc20.sol";
* @dev Home mediator implementation for native-to-erc20 bridge intended to work on top of AMB bridge.
* It is design to be used as implementation contract of EternalStorageProxy contract.
*/
contract HomeAMBNativeToErc20 is BasicAMBNativeToErc20 {
bytes32 internal constant MEDIATOR_BALANCE = 0x3db340e280667ee926fa8c51e8f9fcf88a0ff221a66d84d63b4778127d97d139; // keccak256(abi.encodePacked("mediatorBalance"))
contract HomeAMBNativeToErc20 is BasicAMBNativeToErc20, MediatorBalanceStorage {
/**
* @dev Stores the initial parameters of the mediator.
* @param _bridgeContract the address of the AMB bridge contract.
@ -73,7 +72,7 @@ contract HomeAMBNativeToErc20 is BasicAMBNativeToErc20 {
require(msg.value > 0);
require(withinLimit(msg.value));
addTotalSpentPerDay(getCurrentDay(), msg.value);
setMediatorBalance(mediatorBalance().add(msg.value));
_setMediatorBalance(mediatorBalance().add(msg.value));
passMessage(msg.sender, _receiver, msg.value);
}
@ -85,7 +84,7 @@ contract HomeAMBNativeToErc20 is BasicAMBNativeToErc20 {
*/
function executeActionOnBridgedTokens(address _receiver, uint256 _value) internal {
uint256 valueToTransfer = _shiftValue(_value);
setMediatorBalance(mediatorBalance().sub(valueToTransfer));
_setMediatorBalance(mediatorBalance().sub(valueToTransfer));
bytes32 _messageId = messageId();
IMediatorFeeManager feeManager = feeManagerContract();
@ -107,7 +106,7 @@ contract HomeAMBNativeToErc20 is BasicAMBNativeToErc20 {
* @param _value amount of native tokens to be received
*/
function executeActionOnFixedTokens(address _receiver, uint256 _value) internal {
setMediatorBalance(mediatorBalance().sub(_value));
_setMediatorBalance(mediatorBalance().sub(_value));
Address.safeSendValue(_receiver, _value);
}
@ -120,31 +119,16 @@ contract HomeAMBNativeToErc20 is BasicAMBNativeToErc20 {
Address.safeSendValue(_feeManager, _fee);
}
/**
* @dev Tells the native balance of the contract.
* @return the current tracked native balance of the contract.
*/
function mediatorBalance() public view returns (uint256) {
return uintStorage[MEDIATOR_BALANCE];
}
/**
* @dev Sets the updated native balance of the contract.
* @param _balance the new native balance of the contract.
*/
function setMediatorBalance(uint256 _balance) internal {
uintStorage[MEDIATOR_BALANCE] = _balance;
}
/**
* @dev Allows to transfer any locked token on this contract that is not part of the bridge operations.
* Native tokens are not allowed to be claimed.
* @param _token address of the token.
* @param _to address that will receive the locked tokens on this contract.
*/
function claimTokens(address _token, address _to) public {
function claimTokens(address _token, address _to) external onlyIfUpgradeabilityOwner {
// Since bridged coins are locked at this contract, it is not allowed to claim them with the use of claimTokens function
require(_token != address(0));
super.claimTokens(_token, _to);
claimValues(_token, _to);
}
/**
@ -152,7 +136,7 @@ contract HomeAMBNativeToErc20 is BasicAMBNativeToErc20 {
* without the invocation of the required methods.
* @param _receiver the address that will receive the tokens on the other network
*/
function fixMediatorBalance(address _receiver) public onlyIfUpgradeabilityOwner {
function fixMediatorBalance(address _receiver) external onlyIfUpgradeabilityOwner validAddress(_receiver) {
uint256 balance = address(this).balance;
uint256 expectedBalance = mediatorBalance();
require(balance > expectedBalance);
@ -163,7 +147,7 @@ contract HomeAMBNativeToErc20 is BasicAMBNativeToErc20 {
diff = available;
}
addTotalSpentPerDay(getCurrentDay(), diff);
setMediatorBalance(expectedBalance.add(diff));
_setMediatorBalance(expectedBalance.add(diff));
passMessage(_receiver, _receiver, diff);
}
}

View File

@ -123,6 +123,7 @@ BasicHomeAMB::executeAffirmation
>>Mediator
........TokenBridgeMediator::handleBridgedTokens
..........HomeAMBNativeToErc20::executeActionOnBridgedTokens
............MediatorBalanceStorage::_setMediatorBalance
............RewardableMediator::distributeFee
..............HomeAMBNativeToErc20::onFeeDistribution
................Address::safeSendValue
@ -214,6 +215,7 @@ BasicHomeAMB::executeAffirmation
..........TokenBridgeMediator::messageHashRecipient
..........TokenBridgeMediator::messageHashValue
..........HomeAMBNativeToErc20::executeActionOnFixedTokens
............MediatorBalanceStorage::_setMediatorBalance
............Address::safeSendValue
..........emit FailedMessageFixed
>>Bridge
@ -308,6 +310,7 @@ The mediator on the Home side has the `relayTokens` method which is payable. The
>>Mediator
HomeAMBNativeToErc20::relayTokens
..HomeAMBNativeToErc20::nativeTransfer
....MediatorBalanceStorage::_setMediatorBalance
....TokenBridgeMediator::passMessage
......TokenBridgeMediator::setMessageHashValue
......TokenBridgeMediator::setMessageHashRecipient

View File

@ -33,9 +33,15 @@ contract BasicForeignBridgeErcToErc is BasicForeignBridge {
return 0xba4690f5; // bytes4(keccak256(abi.encodePacked("erc-to-erc-core")))
}
function claimTokens(address _token, address _to) public {
/**
* @dev Withdraws the erc20 tokens or native coins from this contract. Bridged token cannot be withdrawn by this function.
* @param _token address of the claimed token or address(0) for native coins.
* @param _to address of the tokens/coins receiver.
*/
function claimTokens(address _token, address _to) external onlyIfUpgradeabilityOwner {
// Since bridged tokens are locked at this contract, it is not allowed to claim them with the use of claimTokens function
require(_token != address(erc20token()));
super.claimTokens(_token, _to);
claimValues(_token, _to);
}
function onExecuteMessage(

View File

@ -121,6 +121,11 @@ contract HomeBridgeErcToErc is
setErc677token(_erc677token);
}
/**
* @dev Withdraws erc20 tokens or native coins from the token contract. It is required since the bridge contract is the owner of the token contract.
* @param _token address of the claimed token or address(0) for native coins.
* @param _to address of the tokens/coins receiver.
*/
function claimTokensFromErc677(address _token, address _to) external onlyIfUpgradeabilityOwner {
IBurnableMintableERC677Token(erc677token()).claimTokens(_token, _to);
}

View File

@ -39,11 +39,17 @@ contract ForeignBridgeErcToNative is BasicForeignBridge, ERC20Bridge, OtherSideB
return 0x18762d46; // bytes4(keccak256(abi.encodePacked("erc-to-native-core")))
}
function claimTokens(address _token, address _to) public {
/**
* @dev Withdraws the erc20 tokens or native coins from this contract.
* @param _token address of the claimed token or address(0) for native coins.
* @param _to address of the tokens/coins receiver.
*/
function claimTokens(address _token, address _to) external onlyIfUpgradeabilityOwner {
// Since bridged tokens are locked at this contract, it is not allowed to claim them with the use of claimTokens function
require(_token != address(erc20token()));
// Chai token is not claimable if investing into Chai is enabled
require(_token != address(chaiToken()) || !isChaiTokenEnabled());
super.claimTokens(_token, _to);
claimValues(_token, _to);
}
function onExecuteMessage(

View File

@ -119,6 +119,20 @@ contract HomeBridgeErcToNative is
_setBlockRewardContract(_blockReward);
}
/**
* @dev Withdraws the erc20 tokens or native coins from this contract.
* @param _token address of the claimed token or address(0) for native coins.
* @param _to address of the tokens/coins receiver.
*/
function claimTokens(address _token, address _to) external onlyIfUpgradeabilityOwner {
// Since native coins are being minted by the blockReward contract and burned by sending them to the address(0),
// they are not locked at the contract during the normal operation. However, they can be still forced into this contract
// by using a selfdestruct opcode, or by using this contract address as a coinbase account.
// In this case it is necessary to allow claiming native coins back.
// Any other erc20 token can be safely claimed as well.
claimValues(_token, _to);
}
function _initialize(
address _validatorContract,
uint256[3] _dailyLimitMaxPerTxMinPerTxArray, // [ 0 = _dailyLimit, 1 = _maxPerTx, 2 = _minPerTx ]

View File

@ -75,8 +75,9 @@ contract BasicMultiAMBErc20ToErc677 is
* @param _token address of claimed token, address(0) for native
* @param _to address of tokens receiver
*/
function claimTokens(address _token, address _to) external onlyIfUpgradeabilityOwner validAddress(_to) {
require(_token == address(0) || !isTokenRegistered(_token)); // native coins or token not registered
function claimTokens(address _token, address _to) external onlyIfUpgradeabilityOwner {
// Only unregistered tokens and native coins are allowed to be claimed with the use of this function
require(_token == address(0) || !isTokenRegistered(_token));
claimValues(_token, _to);
}

View File

@ -200,7 +200,11 @@ contract ForeignMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677 {
* @param _token address of the token contract.
* @param _receiver the address that will receive the tokens on the other network.
*/
function fixMediatorBalance(address _token, address _receiver) public onlyIfUpgradeabilityOwner {
function fixMediatorBalance(address _token, address _receiver)
external
onlyIfUpgradeabilityOwner
validAddress(_receiver)
{
require(isTokenRegistered(_token));
uint256 balance = ERC677(_token).balanceOf(address(this));
uint256 expectedBalance = mediatorBalance(_token);

View File

@ -72,10 +72,26 @@ contract ForeignBridgeNativeToErc is
return 0x92a8d7fe; // bytes4(keccak256(abi.encodePacked("native-to-erc-core")))
}
/**
* @dev Withdraws erc20 tokens or native coins from the token contract. It is required since the bridge contract is the owner of the token contract.
* @param _token address of the claimed token or address(0) for native coins.
* @param _to address of the tokens/coins receiver.
*/
function claimTokensFromErc677(address _token, address _to) external onlyIfUpgradeabilityOwner {
IBurnableMintableERC677Token(erc677token()).claimTokens(_token, _to);
}
/**
* @dev Withdraws the erc20 tokens or native coins from this contract.
* @param _token address of the claimed token or address(0) for native coins.
* @param _to address of the tokens/coins receiver.
*/
function claimTokens(address _token, address _to) external onlyIfUpgradeabilityOwner {
// For foreign side of the bridge, tokens are not locked at the contract, they are minted and burned instead.
// So, its is safe to allow claiming of any tokens. Native coins are allowed as well.
claimValues(_token, _to);
}
function _initialize(
address _validatorContract,
address _erc677token,

View File

@ -83,6 +83,18 @@ contract HomeBridgeNativeToErc is EternalStorage, BasicHomeBridge, RewardableHom
return 0x92a8d7fe; // bytes4(keccak256(abi.encodePacked("native-to-erc-core")))
}
/**
* @dev Allows to transfer any locked token on this contract that is not part of the bridge operations.
* Native tokens are not allowed to be claimed.
* @param _token address of the token.
* @param _to address that will receive the locked tokens on this contract.
*/
function claimTokens(address _token, address _to) external onlyIfUpgradeabilityOwner {
// Since bridged coins are locked at this contract, it is not allowed to claim them with the use of claimTokens function
require(_token != address(0));
claimValues(_token, _to);
}
function _initialize(
address _validatorContract,
uint256[3] _dailyLimitMaxPerTxMinPerTxArray, // [ 0 = _dailyLimit, 1 = _maxPerTx, 2 = _minPerTx ]

View File

@ -751,6 +751,7 @@ contract('ForeignAMBErc20ToNative', async accounts => {
expect(events.length).to.be.equal(1)
await contract.fixMediatorBalance(owner, { from: user }).should.be.rejected
await contract.fixMediatorBalance(ZERO_ADDRESS, { from: owner }).should.be.rejected
await contract.fixMediatorBalance(owner, { from: owner }).should.be.fulfilled
await contract.fixMediatorBalance(owner, { from: owner }).should.be.rejected

View File

@ -969,6 +969,8 @@ contract('HomeAMBErc20ToNative', async accounts => {
// only owner can call the method
await contract.fixMediatorBalance(user, { from: user }).should.be.rejected
await contract.fixMediatorBalance(ZERO_ADDRESS, { from: owner }).should.be.rejected
await contract.fixMediatorBalance(user, { from: owner }).should.be.fulfilled
expect(await contract.totalBurntCoins()).to.be.bignumber.equal(ether('0.1'))

View File

@ -10,7 +10,7 @@ const { expect } = require('chai')
const { shouldBehaveLikeBasicAMBErc677ToErc677 } = require('./AMBErc677ToErc677Behavior.test')
const { ether } = require('../helpers/helpers')
const { getEvents, strip0x } = require('../helpers/helpers')
const { ERROR_MSG, toBN } = require('../setup')
const { ERROR_MSG, toBN, ZERO_ADDRESS } = require('../setup')
const ZERO = toBN(0)
const halfEther = ether('0.5')
@ -98,6 +98,7 @@ contract('ForeignAMBErc677ToErc677', async accounts => {
expect(events.length).to.be.equal(1)
expect(events[0].returnValues.encodedData.includes(strip0x(user).toLowerCase())).to.be.equal(true)
expect(await foreignBridge.totalSpentPerDay(currentDay)).to.be.bignumber.equal(halfEther)
expect(await foreignBridge.mediatorBalance()).to.be.bignumber.equal(halfEther)
})
it('should be able to specify a different receiver', async () => {
// Given
@ -126,6 +127,7 @@ contract('ForeignAMBErc677ToErc677', async accounts => {
expect(events.length).to.be.equal(1)
expect(events[0].returnValues.encodedData.includes(strip0x(user2).toLowerCase())).to.be.equal(true)
expect(await foreignBridge.totalSpentPerDay(currentDay)).to.be.bignumber.equal(halfEther)
expect(await foreignBridge.mediatorBalance()).to.be.bignumber.equal(halfEther)
})
})
describe('handleBridgedTokens', () => {
@ -146,8 +148,9 @@ contract('ForeignAMBErc677ToErc677', async accounts => {
decimalShiftZero,
owner
).should.be.fulfilled
await erc677Token.mint(foreignBridge.address, twoEthers, { from: owner }).should.be.fulfilled
await erc677Token.transferOwnership(foreignBridge.address)
await erc677Token.mint(user, twoEthers, { from: owner }).should.be.fulfilled
await erc677Token.transfer(foreignBridge.address, oneEther, { from: user }).should.be.fulfilled
await erc677Token.transfer(foreignBridge.address, oneEther, { from: user }).should.be.fulfilled
})
it('should transfer locked tokens on message from amb', async () => {
// Given
@ -156,6 +159,7 @@ contract('ForeignAMBErc677ToErc677', async accounts => {
const initialEvents = await getEvents(erc677Token, { event: 'Transfer' })
expect(initialEvents.length).to.be.equal(0)
expect(await erc677Token.balanceOf(foreignBridge.address)).to.be.bignumber.equal(twoEthers)
expect(await foreignBridge.mediatorBalance()).to.be.bignumber.equal(twoEthers)
expect(await erc677Token.balanceOf(user)).to.be.bignumber.equal(ZERO)
// can't be called by user
@ -184,6 +188,7 @@ contract('ForeignAMBErc677ToErc677', async accounts => {
// Then
expect(await foreignBridge.totalExecutedPerDay(currentDay)).to.be.bignumber.equal(oneEther)
expect(await erc677Token.balanceOf(foreignBridge.address)).to.be.bignumber.equal(oneEther)
expect(await foreignBridge.mediatorBalance()).to.be.bignumber.equal(oneEther)
expect(await erc677Token.balanceOf(user)).to.be.bignumber.equal(oneEther)
const TokensBridgedEvent = await getEvents(foreignBridge, { event: 'TokensBridged' })
@ -208,14 +213,16 @@ contract('ForeignAMBErc677ToErc677', async accounts => {
decimalShift,
owner
).should.be.fulfilled
await erc677Token.mint(foreignBridge.address, twoEthers, { from: owner }).should.be.fulfilled
await erc677Token.transferOwnership(foreignBridge.address)
await erc677Token.mint(user, twoEthers, { from: owner }).should.be.fulfilled
await erc677Token.transfer(foreignBridge.address, oneEther, { from: user }).should.be.fulfilled
await erc677Token.transfer(foreignBridge.address, oneEther, { from: user }).should.be.fulfilled
const currentDay = await foreignBridge.getCurrentDay()
expect(await foreignBridge.totalExecutedPerDay(currentDay)).to.be.bignumber.equal(ZERO)
const initialEvents = await getEvents(erc677Token, { event: 'Transfer' })
expect(initialEvents.length).to.be.equal(0)
expect(await erc677Token.balanceOf(foreignBridge.address)).to.be.bignumber.equal(twoEthers)
expect(await foreignBridge.mediatorBalance()).to.be.bignumber.equal(twoEthers)
expect(await erc677Token.balanceOf(user)).to.be.bignumber.equal(ZERO)
const valueOnForeign = toBN('1000')
@ -242,6 +249,7 @@ contract('ForeignAMBErc677ToErc677', async accounts => {
// Then
expect(await foreignBridge.totalExecutedPerDay(currentDay)).to.be.bignumber.equal(valueOnHome)
expect(await erc677Token.balanceOf(foreignBridge.address)).to.be.bignumber.equal(twoEthers.sub(valueOnForeign))
expect(await foreignBridge.mediatorBalance()).to.be.bignumber.equal(twoEthers.sub(valueOnForeign))
expect(await erc677Token.balanceOf(user)).to.be.bignumber.equal(valueOnForeign)
const TokensBridgedEvent = await getEvents(foreignBridge, { event: 'TokensBridged' })
@ -258,6 +266,7 @@ contract('ForeignAMBErc677ToErc677', async accounts => {
const initialEvents = await getEvents(erc677Token, { event: 'Transfer' })
expect(initialEvents.length).to.be.equal(0)
expect(await erc677Token.balanceOf(foreignBridge.address)).to.be.bignumber.equal(twoEthers)
expect(await foreignBridge.mediatorBalance()).to.be.bignumber.equal(twoEthers)
expect(await erc677Token.balanceOf(user)).to.be.bignumber.equal(ZERO)
const outOfLimitValueData = await foreignBridge.contract.methods
@ -278,6 +287,7 @@ contract('ForeignAMBErc677ToErc677', async accounts => {
// Then
expect(await foreignBridge.totalExecutedPerDay(currentDay)).to.be.bignumber.equal(ZERO)
expect(await erc677Token.balanceOf(foreignBridge.address)).to.be.bignumber.equal(twoEthers)
expect(await foreignBridge.mediatorBalance()).to.be.bignumber.equal(twoEthers)
expect(await erc677Token.balanceOf(user)).to.be.bignumber.equal(ZERO)
expect(await foreignBridge.outOfLimitAmount()).to.be.bignumber.equal(twoEthers)
@ -291,4 +301,186 @@ contract('ForeignAMBErc677ToErc677', async accounts => {
expect(TokensBridgedEvent.length).to.be.equal(0)
})
})
describe('fixFailedMessage', () => {
it('should update mediatorBalance ', async () => {
ambBridgeContract = await AMBMock.new()
await ambBridgeContract.setMaxGasPerTx(maxGasPerTx)
mediatorContract = await HomeAMBErc677ToErc677.new()
erc677Token = await ERC677BridgeToken.new('test', 'TST', 18)
await erc677Token.mint(user, twoEthers, { from: owner }).should.be.fulfilled
foreignBridge = await ForeignAMBErc677ToErc677.new()
await foreignBridge.initialize(
ambBridgeContract.address,
mediatorContract.address,
erc677Token.address,
[dailyLimit, maxPerTx, minPerTx],
[executionDailyLimit, executionMaxPerTx],
maxGasPerTx,
decimalShiftZero,
owner
).should.be.fulfilled
await erc677Token.transferOwnership(foreignBridge.address)
expect(await erc677Token.balanceOf(user)).to.be.bignumber.equal(twoEthers)
expect(await erc677Token.totalSupply()).to.be.bignumber.equal(twoEthers)
// User transfer tokens
await erc677Token.transferAndCall(foreignBridge.address, oneEther, '0x', { from: user }).should.be.fulfilled
expect(await erc677Token.balanceOf(user)).to.be.bignumber.equal(oneEther)
expect(await foreignBridge.mediatorBalance()).to.be.bignumber.equal(oneEther)
const events = await getEvents(ambBridgeContract, { event: 'MockedEvent' })
expect(events.length).to.be.equal(1)
const transferMessageId = events[0].returnValues.messageId
// Given
expect(await foreignBridge.messageFixed(transferMessageId)).to.be.equal(false)
// When
const fixData = await foreignBridge.contract.methods.fixFailedMessage(transferMessageId).encodeABI()
await ambBridgeContract.executeMessageCall(
foreignBridge.address,
mediatorContract.address,
fixData,
exampleMessageId,
1000000
).should.be.fulfilled
// Then
expect(await ambBridgeContract.messageCallStatus(exampleMessageId)).to.be.equal(true)
expect(await foreignBridge.mediatorBalance()).to.be.bignumber.equal(ZERO)
expect(await erc677Token.balanceOf(user)).to.be.bignumber.equal(twoEthers)
expect(await erc677Token.totalSupply()).to.be.bignumber.equal(twoEthers)
expect(await foreignBridge.messageFixed(transferMessageId)).to.be.equal(true)
})
})
describe('fixMediatorBalance', () => {
let currentDay
beforeEach(async () => {
const storageProxy = await EternalStorageProxy.new()
foreignBridge = await ForeignAMBErc677ToErc677.new()
await storageProxy.upgradeTo('1', foreignBridge.address).should.be.fulfilled
foreignBridge = await ForeignAMBErc677ToErc677.at(storageProxy.address)
erc677Token = await ERC677BridgeToken.new('test', 'TST', 18)
await erc677Token.mint(user, twoEthers, { from: owner }).should.be.fulfilled
await erc677Token.mint(foreignBridge.address, twoEthers, { from: owner }).should.be.fulfilled
ambBridgeContract = await AMBMock.new()
await ambBridgeContract.setMaxGasPerTx(maxGasPerTx)
await foreignBridge.initialize(
ambBridgeContract.address,
mediatorContract.address,
erc677Token.address,
[dailyLimit, maxPerTx, minPerTx],
[executionDailyLimit, executionMaxPerTx],
maxGasPerTx,
decimalShiftZero,
owner
).should.be.fulfilled
currentDay = await foreignBridge.getCurrentDay()
expect(await foreignBridge.totalSpentPerDay(currentDay)).to.be.bignumber.equal(ZERO)
const initialEvents = await getEvents(ambBridgeContract, { event: 'MockedEvent' })
expect(initialEvents.length).to.be.equal(0)
})
it('should allow to fix extra mediator balance', async () => {
expect(await foreignBridge.mediatorBalance()).to.be.bignumber.equal(ZERO)
expect(await erc677Token.balanceOf(foreignBridge.address)).to.be.bignumber.equal(twoEthers)
await erc677Token.transferAndCall(foreignBridge.address, halfEther, '0x', { from: user }).should.be.fulfilled
await foreignBridge.setDailyLimit(ether('3')).should.be.fulfilled
await foreignBridge.setMaxPerTx(twoEthers).should.be.fulfilled
expect(await foreignBridge.mediatorBalance()).to.be.bignumber.equal(halfEther)
expect(await erc677Token.balanceOf(foreignBridge.address)).to.be.bignumber.equal(twoEthers.add(halfEther))
expect(await foreignBridge.totalSpentPerDay(currentDay)).to.be.bignumber.equal(halfEther)
let events = await getEvents(ambBridgeContract, { event: 'MockedEvent' })
expect(events.length).to.be.equal(1)
await foreignBridge.fixMediatorBalance(owner, { from: user }).should.be.rejected
await foreignBridge.fixMediatorBalance(ZERO_ADDRESS, { from: owner }).should.be.rejected
await foreignBridge.fixMediatorBalance(owner, { from: owner }).should.be.fulfilled
await foreignBridge.fixMediatorBalance(owner, { from: owner }).should.be.rejected
expect(await foreignBridge.mediatorBalance()).to.be.bignumber.equal(twoEthers.add(halfEther))
expect(await erc677Token.balanceOf(foreignBridge.address)).to.be.bignumber.equal(twoEthers.add(halfEther))
expect(await foreignBridge.totalSpentPerDay(currentDay)).to.be.bignumber.equal(twoEthers.add(halfEther))
events = await getEvents(ambBridgeContract, { event: 'MockedEvent' })
expect(events.length).to.be.equal(2)
})
it('should allow to fix extra mediator balance with respect to limits', async () => {
expect(await foreignBridge.mediatorBalance()).to.be.bignumber.equal(ZERO)
expect(await erc677Token.balanceOf(foreignBridge.address)).to.be.bignumber.equal(twoEthers)
await erc677Token.transferAndCall(foreignBridge.address, halfEther, '0x', { from: user }).should.be.fulfilled
await foreignBridge.setMinPerTx('1').should.be.fulfilled
await foreignBridge.setMaxPerTx(halfEther).should.be.fulfilled
await foreignBridge.setDailyLimit(ether('1.25')).should.be.fulfilled
expect(await foreignBridge.mediatorBalance()).to.be.bignumber.equal(halfEther)
expect(await erc677Token.balanceOf(foreignBridge.address)).to.be.bignumber.equal(twoEthers.add(halfEther))
expect(await foreignBridge.totalSpentPerDay(currentDay)).to.be.bignumber.equal(halfEther)
let events = await getEvents(ambBridgeContract, { event: 'MockedEvent' })
expect(events.length).to.be.equal(1)
await foreignBridge.fixMediatorBalance(owner, { from: user }).should.be.rejected
// should fix 0.5 ether
await foreignBridge.fixMediatorBalance(owner, { from: owner }).should.be.fulfilled
expect(await foreignBridge.mediatorBalance()).to.be.bignumber.equal(oneEther)
expect(await foreignBridge.totalSpentPerDay(currentDay)).to.be.bignumber.equal(oneEther)
// should fix 0.25 ether
await foreignBridge.fixMediatorBalance(owner, { from: owner }).should.be.fulfilled
// no remaining daily quota
await foreignBridge.fixMediatorBalance(owner, { from: owner }).should.be.rejected
await foreignBridge.setDailyLimit(oneEther).should.be.fulfilled
// no remaining daily quota
await foreignBridge.fixMediatorBalance(owner, { from: owner }).should.be.rejected
expect(await foreignBridge.mediatorBalance()).to.be.bignumber.equal(ether('1.25'))
expect(await foreignBridge.totalSpentPerDay(currentDay)).to.be.bignumber.equal(ether('1.25'))
expect(await erc677Token.balanceOf(foreignBridge.address)).to.be.bignumber.equal(twoEthers.add(halfEther))
events = await getEvents(ambBridgeContract, { event: 'MockedEvent' })
expect(events.length).to.be.equal(3)
})
})
describe('claimTokens', () => {
it('should not allow to claim bridged token', async () => {
const storageProxy = await EternalStorageProxy.new()
foreignBridge = await ForeignAMBErc677ToErc677.new()
await storageProxy.upgradeTo('1', foreignBridge.address).should.be.fulfilled
foreignBridge = await ForeignAMBErc677ToErc677.at(storageProxy.address)
erc677Token = await ERC677BridgeToken.new('test', 'TST', 18)
await erc677Token.mint(foreignBridge.address, twoEthers, { from: owner }).should.be.fulfilled
ambBridgeContract = await AMBMock.new()
await ambBridgeContract.setMaxGasPerTx(maxGasPerTx)
await foreignBridge.initialize(
ambBridgeContract.address,
mediatorContract.address,
erc677Token.address,
[dailyLimit, maxPerTx, minPerTx],
[executionDailyLimit, executionMaxPerTx],
maxGasPerTx,
decimalShiftZero,
owner
).should.be.fulfilled
await foreignBridge.claimTokens(erc677Token.address, accounts[3], { from: accounts[3] }).should.be.rejected
await foreignBridge.claimTokens(erc677Token.address, accounts[3], { from: owner }).should.be.rejected
})
})
})

View File

@ -1442,6 +1442,8 @@ contract('HomeAMBNativeToErc20', async accounts => {
// only owner can call the method
await contract.fixMediatorBalance(user, { from: user }).should.be.rejectedWith(ERROR_MSG)
await contract.fixMediatorBalance(ZERO_ADDRESS, { from: owner }).should.be.rejected
await contract.fixMediatorBalance(user, { from: owner }).should.be.fulfilled
// imbalance was already fixed

View File

@ -865,6 +865,7 @@ contract('ForeignMultiAMBErc20ToErc677', async accounts => {
await contract.fixMediatorBalance(token.address, owner, { from: user }).should.be.rejected
await contract.fixMediatorBalance(ZERO_ADDRESS, owner, { from: owner }).should.be.rejected
await contract.fixMediatorBalance(token.address, ZERO_ADDRESS, { from: owner }).should.be.rejected
await contract.fixMediatorBalance(token.address, owner, { from: owner }).should.be.fulfilled
await contract.fixMediatorBalance(token.address, owner, { from: owner }).should.be.rejected

View File

@ -1083,7 +1083,7 @@ contract('HomeBridge', async accounts => {
expect(await tokenMock.balanceOf(homeBridge.address)).to.be.bignumber.equal(ZERO)
expect(await tokenMock.balanceOf(accounts[3])).to.be.bignumber.equal(halfEther)
})
it('should work for native coins', async () => {
it('should not work for native coins', async () => {
const storageProxy = await EternalStorageProxy.new()
const data = homeContract.contract.methods
.initialize(
@ -1099,14 +1099,11 @@ contract('HomeBridge', async accounts => {
await storageProxy.upgradeToAndCall('1', homeContract.address, data).should.be.fulfilled
const homeBridge = await HomeBridge.at(storageProxy.address)
const balanceBefore = toBN(await web3.eth.getBalance(accounts[3]))
await homeBridge.sendTransaction({ from: accounts[2], value: halfEther }).should.be.fulfilled
expect(toBN(await web3.eth.getBalance(homeBridge.address))).to.be.bignumber.equal(halfEther)
await homeBridge.claimTokens(ZERO_ADDRESS, accounts[3], { from: owner }).should.be.fulfilled
expect(toBN(await web3.eth.getBalance(homeBridge.address))).to.be.bignumber.equal(ZERO)
expect(toBN(await web3.eth.getBalance(accounts[3]))).to.be.bignumber.equal(balanceBefore.add(halfEther))
await homeBridge.claimTokens(ZERO_ADDRESS, accounts[3], { from: accounts[3] }).should.be.rejected
await homeBridge.claimTokens(ZERO_ADDRESS, accounts[3], { from: owner }).should.be.rejected
})
})

View File

@ -1,5 +1,6 @@
const ForeignStakeTokenMediator = artifacts.require('ForeignStakeTokenMediator.sol')
const HomeStakeTokenMediator = artifacts.require('HomeStakeTokenMediator.sol')
const EternalStorageProxy = artifacts.require('EternalStorageProxy.sol')
const ERC677BridgeToken = artifacts.require('ERC677BridgeToken.sol')
const AMBMock = artifacts.require('AMBMock.sol')
@ -263,4 +264,28 @@ contract('ForeignStakeTokenMediator', async accounts => {
await token.transferAndCall(foreignMediator.address, halfEther, user, { from: user }).should.be.fulfilled
})
})
describe('claimTokens', () => {
it('should not allow to claim bridged token', async () => {
const storageProxy = await EternalStorageProxy.new()
await storageProxy.upgradeTo('1', foreignMediator.address).should.be.fulfilled
foreignMediator = await ForeignStakeTokenMediator.at(storageProxy.address)
token = await ERC677BridgeToken.new('test', 'TST', 18)
await token.mint(foreignMediator.address, twoEthers, { from: owner }).should.be.fulfilled
await foreignMediator.initialize(
foreignBridge.address,
homeMediator.address,
token.address,
[dailyLimit, maxPerTx, minPerTx],
[executionDailyLimit, executionMaxPerTx],
maxGasPerTx,
decimalShiftZero,
owner
).should.be.fulfilled
await foreignMediator.claimTokens(token.address, accounts[3], { from: accounts[3] }).should.be.rejected
await foreignMediator.claimTokens(token.address, accounts[3], { from: owner }).should.be.rejected
})
})
})