From ab3529e7761adc3dc95c8725663e3ae8c48e78c5 Mon Sep 17 00:00:00 2001 From: Alexander Kolotov Date: Mon, 3 Aug 2020 15:35:02 +0300 Subject: [PATCH 1/4] increase the version of AMB contracts (#461) --- .../upgradeable_contracts/arbitrary_message/VersionableAMB.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/upgradeable_contracts/arbitrary_message/VersionableAMB.sol b/contracts/upgradeable_contracts/arbitrary_message/VersionableAMB.sol index d8ddbd64c..6546aea19 100644 --- a/contracts/upgradeable_contracts/arbitrary_message/VersionableAMB.sol +++ b/contracts/upgradeable_contracts/arbitrary_message/VersionableAMB.sol @@ -17,6 +17,6 @@ contract VersionableAMB is VersionableBridge { * @return (major, minor, patch) version triple */ function getBridgeInterfacesVersion() external pure returns (uint64 major, uint64 minor, uint64 patch) { - return (5, 0, 2); + return (5, 3, 0); } } From a1ff878b1f7129bd8401e77b17eb47b34bacdd1c Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Mon, 3 Aug 2020 19:36:12 +0700 Subject: [PATCH 2/4] Move fee manager into home mediator (#462) --- contracts/mocks/PermittableTokenMock.sol | 23 + .../BasicAMBMediator.sol | 9 + .../TokenBridgeMediator.sol | 8 +- .../BasicMultiTokenBridge.sol | 12 +- .../ForeignMultiAMBErc20ToErc677.sol | 61 +-- ...> HomeFeeManagerMultiAMBErc20ToErc677.sol} | 11 +- .../HomeMultiAMBErc20ToErc677.sol | 54 ++- .../MultiTokenBridgeMediator.sol | 8 +- .../multi_amb_erc20_to_erc677/callflows.md | 21 +- deploy/README.md | 18 +- deploy/src/loadEnv.js | 12 +- .../initializeForeign.js | 34 +- .../initializeHome.js | 34 +- .../foreign_mediator.test.js | 386 ++------------- .../home_mediator.test.js | 446 +++++++++++++++++- test/poa20_test.js | 56 ++- 16 files changed, 680 insertions(+), 513 deletions(-) create mode 100644 contracts/mocks/PermittableTokenMock.sol rename contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/{ForeignFeeManagerMultiAMBErc20ToErc677.sol => HomeFeeManagerMultiAMBErc20ToErc677.sol} (93%) diff --git a/contracts/mocks/PermittableTokenMock.sol b/contracts/mocks/PermittableTokenMock.sol new file mode 100644 index 000000000..dfe0d90e0 --- /dev/null +++ b/contracts/mocks/PermittableTokenMock.sol @@ -0,0 +1,23 @@ +pragma solidity 0.4.24; + +import "../PermittableToken.sol"; + +contract PermittableTokenMock is PermittableToken { + uint256 private _blockTimestamp; + + constructor(string _name, string _symbol, uint8 _decimals, uint256 _chainId) + public + PermittableToken(_name, _symbol, _decimals, _chainId) + { + // solhint-disable-previous-line no-empty-blocks + } + + function setNow(uint256 _timestamp) public { + _blockTimestamp = _timestamp; + } + + function _now() internal view returns (uint256) { + return _blockTimestamp != 0 ? _blockTimestamp : now; + } + +} diff --git a/contracts/upgradeable_contracts/BasicAMBMediator.sol b/contracts/upgradeable_contracts/BasicAMBMediator.sol index 1fd3d63ef..cece0d330 100644 --- a/contracts/upgradeable_contracts/BasicAMBMediator.sol +++ b/contracts/upgradeable_contracts/BasicAMBMediator.sol @@ -14,6 +14,15 @@ contract BasicAMBMediator is Ownable { bytes32 internal constant MEDIATOR_CONTRACT = 0x98aa806e31e94a687a31c65769cb99670064dd7f5a87526da075c5fb4eab9880; // keccak256(abi.encodePacked("mediatorContract")) bytes32 internal constant REQUEST_GAS_LIMIT = 0x2dfd6c9f781bb6bbb5369c114e949b69ebb440ef3d4dd6b2836225eb1dc3a2be; // keccak256(abi.encodePacked("requestGasLimit")) + /** + * @dev Throws if caller on the other side if not an associated mediator. + */ + modifier onlyMediator { + require(msg.sender == address(bridgeContract())); + require(messageSender() == mediatorContractOnOtherSide()); + _; + } + /** * @dev Sets the AMB bridge contract address. Only the owner can call this method. * @param _bridgeContract the address of the bridge contract. diff --git a/contracts/upgradeable_contracts/TokenBridgeMediator.sol b/contracts/upgradeable_contracts/TokenBridgeMediator.sol index ec19cd9ab..5cf9a972d 100644 --- a/contracts/upgradeable_contracts/TokenBridgeMediator.sol +++ b/contracts/upgradeable_contracts/TokenBridgeMediator.sol @@ -40,9 +40,7 @@ contract TokenBridgeMediator is BasicAMBMediator, BasicTokenBridge, TransferInfo * @param _recipient address that will receive the tokens * @param _value amount of tokens to be received */ - function handleBridgedTokens(address _recipient, uint256 _value) external { - require(msg.sender == address(bridgeContract())); - require(messageSender() == mediatorContractOnOtherSide()); + function handleBridgedTokens(address _recipient, uint256 _value) external onlyMediator { if (withinExecutionLimit(_value)) { addTotalExecutedPerDay(getCurrentDay(), _value); executeActionOnBridgedTokens(_recipient, _value); @@ -71,9 +69,7 @@ contract TokenBridgeMediator is BasicAMBMediator, BasicTokenBridge, TransferInfo * It uses the information stored by passMessage method when the assets were initially transferred * @param _messageId id of the message which execution failed on the other network. */ - function fixFailedMessage(bytes32 _messageId) external { - require(msg.sender == address(bridgeContract())); - require(messageSender() == mediatorContractOnOtherSide()); + function fixFailedMessage(bytes32 _messageId) external onlyMediator { require(!messageFixed(_messageId)); address recipient = messageRecipient(_messageId); diff --git a/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/BasicMultiTokenBridge.sol b/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/BasicMultiTokenBridge.sol index 3c9d208e1..a70eaabf0 100644 --- a/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/BasicMultiTokenBridge.sol +++ b/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/BasicMultiTokenBridge.sol @@ -266,14 +266,18 @@ contract BasicMultiTokenBridge is EternalStorage, Ownable { uint256 _dailyLimit = dailyLimit(address(0)).div(factor); uint256 _executionMaxPerTx = executionMaxPerTx(address(0)).div(factor); uint256 _executionDailyLimit = executionDailyLimit(address(0)).div(factor); + + // such situation can happen when calculated limits relative to the token decimals are too low + // e.g. minPerTx(address(0)) == 10 ** 14, _decimals == 3. _minPerTx happens to be 0, which is not allowed. + // in this case, limits are raised to the default values if (_minPerTx == 0) { _minPerTx = 1; if (_maxPerTx <= _minPerTx) { - _maxPerTx = 2; - _executionMaxPerTx = 2; + _maxPerTx = 100; + _executionMaxPerTx = 100; if (_dailyLimit <= _maxPerTx || _executionDailyLimit <= _executionMaxPerTx) { - _dailyLimit = 3; - _executionDailyLimit = 3; + _dailyLimit = 10000; + _executionDailyLimit = 10000; } } } diff --git a/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/ForeignMultiAMBErc20ToErc677.sol b/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/ForeignMultiAMBErc20ToErc677.sol index 7e1c7dbe4..5bf104c7d 100644 --- a/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/ForeignMultiAMBErc20ToErc677.sol +++ b/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/ForeignMultiAMBErc20ToErc677.sol @@ -2,7 +2,7 @@ pragma solidity 0.4.24; import "openzeppelin-solidity/contracts/token/ERC20/DetailedERC20.sol"; import "./BasicMultiAMBErc20ToErc677.sol"; -import "./ForeignFeeManagerMultiAMBErc20ToErc677.sol"; +import "./HomeMultiAMBErc20ToErc677.sol"; import "../../libraries/TokenReader.sol"; /** @@ -10,9 +10,7 @@ import "../../libraries/TokenReader.sol"; * @dev Foreign side implementation for multi-erc20-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 ForeignMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677, ForeignFeeManagerMultiAMBErc20ToErc677 { - bytes4 internal constant DEPLOY_AND_HANDLE_BRIDGE_TOKENS = 0x2ae87cdd; // deployAndHandleBridgedTokens(address,string,string,uint8,address,uint256) - +contract ForeignMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677 { /** * @dev Stores the initial parameters of the mediator. * @param _bridgeContract the address of the AMB bridge contract. @@ -23,9 +21,6 @@ contract ForeignMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677, ForeignFeeM * [ 0 = executionDailyLimit, 1 = executionMaxPerTx ] * @param _requestGasLimit the gas limit for the message execution. * @param _owner address of the owner of the mediator contract. - * @param _rewardAddreses list of reward addresses, between whom fees will be distributed. - * @param _fees array with initial fees for both bridge firections. - * [ 0 = homeToForeignFee, 1 = foreignToHomeFee ] */ function initialize( address _bridgeContract, @@ -33,9 +28,7 @@ contract ForeignMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677, ForeignFeeM uint256[3] _dailyLimitMaxPerTxMinPerTxArray, // [ 0 = _dailyLimit, 1 = _maxPerTx, 2 = _minPerTx ] uint256[2] _executionDailyLimitExecutionMaxPerTxArray, // [ 0 = _executionDailyLimit, 1 = _executionMaxPerTx ] uint256 _requestGasLimit, - address _owner, - address[] _rewardAddreses, - uint256[2] _fees // [ 0 = homeToForeignFee, 1 = foreignToHomeFee ] + address _owner ) external onlyRelevantSender returns (bool) { require(!isInitialized()); require(_owner != address(0)); @@ -46,11 +39,7 @@ contract ForeignMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677, ForeignFeeM _setExecutionLimits(address(0), _executionDailyLimitExecutionMaxPerTxArray); _setRequestGasLimit(_requestGasLimit); setOwner(_owner); - if (_rewardAddreses.length > 0) { - _setRewardAddressList(_rewardAddreses); - } - _setFee(HOME_TO_FOREIGN_FEE, address(0), _fees[0]); - _setFee(FOREIGN_TO_HOME_FEE, address(0), _fees[1]); + setInitialize(); return isInitialized(); @@ -64,15 +53,9 @@ contract ForeignMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677, ForeignFeeM */ function executeActionOnBridgedTokens(address _token, address _recipient, uint256 _value) internal { bytes32 _messageId = messageId(); - uint256 valueToTransfer = _value; - uint256 fee = _distributeFee(HOME_TO_FOREIGN_FEE, _token, valueToTransfer); - if (fee > 0) { - emit FeeDistributed(fee, _token, _messageId); - valueToTransfer = valueToTransfer.sub(fee); - } - ERC677(_token).transfer(_recipient, valueToTransfer); + ERC677(_token).transfer(_recipient, _value); _setMediatorBalance(_token, mediatorBalance(_token).sub(_value)); - emit TokensBridged(_token, _recipient, valueToTransfer, _messageId); + emit TokensBridged(_token, _recipient, _value, _messageId); } /** @@ -89,6 +72,18 @@ contract ForeignMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677, ForeignFeeM return true; } + /** + * @dev Handles the bridged tokens. Checks that the value is inside the execution limits and invokes the method + * to execute the Mint or Unlock accordingly. + * @param _token bridged ERC20 token. + * @param _recipient address that will receive the tokens. + * @param _value amount of tokens to be received. + */ + function handleBridgedTokens(ERC677 _token, address _recipient, uint256 _value) external onlyMediator { + require(isTokenRegistered(_token)); + _handleBridgedTokens(_token, _recipient, _value); + } + /** * @dev Validates that the token amount is inside the limits, calls transferFrom to transfer the tokens to the contract * and invokes the method to burn/lock the tokens and unlock/mint the tokens on the other network. @@ -130,8 +125,6 @@ contract ForeignMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677, ForeignFeeM require(bytes(name).length > 0 || bytes(symbol).length > 0); _initializeTokenBridgeLimits(_token, decimals); - _setFee(HOME_TO_FOREIGN_FEE, _token, getFee(HOME_TO_FOREIGN_FEE, address(0))); - _setFee(FOREIGN_TO_HOME_FEE, _token, getFee(FOREIGN_TO_HOME_FEE, address(0))); } require(withinLimit(_token, _value)); @@ -139,30 +132,22 @@ contract ForeignMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677, ForeignFeeM bytes memory data; address receiver = chooseReceiver(_from, _data); - uint256 valueToBridge = _value; - uint256 fee = _distributeFee(FOREIGN_TO_HOME_FEE, _token, valueToBridge); - if (fee > 0) { - emit FeeDistributed(fee, _token, _messageId); - valueToBridge = valueToBridge.sub(fee); - } if (isKnownToken) { - data = abi.encodeWithSelector(this.handleBridgedTokens.selector, _token, receiver, valueToBridge); + data = abi.encodeWithSelector(this.handleBridgedTokens.selector, _token, receiver, _value); } else { data = abi.encodeWithSelector( - DEPLOY_AND_HANDLE_BRIDGE_TOKENS, + HomeMultiAMBErc20ToErc677(this).deployAndHandleBridgedTokens.selector, _token, name, symbol, decimals, receiver, - valueToBridge + _value ); } - // avoid stack too deep error by using existing variable - fee = mediatorBalance(_token).add(valueToBridge); - _setMediatorBalance(_token, fee); + _setMediatorBalance(_token, mediatorBalance(_token).add(_value)); bytes32 _messageId = bridgeContract().requireToPassMessage( mediatorContractOnOtherSide(), @@ -171,7 +156,7 @@ contract ForeignMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677, ForeignFeeM ); setMessageToken(_messageId, _token); - setMessageValue(_messageId, valueToBridge); + setMessageValue(_messageId, _value); setMessageRecipient(_messageId, _from); } diff --git a/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/ForeignFeeManagerMultiAMBErc20ToErc677.sol b/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/HomeFeeManagerMultiAMBErc20ToErc677.sol similarity index 93% rename from contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/ForeignFeeManagerMultiAMBErc20ToErc677.sol rename to contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/HomeFeeManagerMultiAMBErc20ToErc677.sol index 0cc44d265..a5e2ca184 100644 --- a/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/ForeignFeeManagerMultiAMBErc20ToErc677.sol +++ b/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/HomeFeeManagerMultiAMBErc20ToErc677.sol @@ -4,13 +4,14 @@ import "./BasicMultiTokenBridge.sol"; import "../BaseRewardAddressList.sol"; import "../Ownable.sol"; import "../../interfaces/ERC677.sol"; +import "../../interfaces/IBurnableMintableERC677Token.sol"; /** -* @title ForeignFeeManagerMultiAMBErc20ToErc677 +* @title HomeFeeManagerMultiAMBErc20ToErc677 * @dev Implements the logic to distribute fees from the multi erc20 to erc677 mediator contract operations. * The fees are distributed in the form of native tokens to the list of reward accounts. */ -contract ForeignFeeManagerMultiAMBErc20ToErc677 is BaseRewardAddressList, Ownable, BasicMultiTokenBridge { +contract HomeFeeManagerMultiAMBErc20ToErc677 is BaseRewardAddressList, Ownable, BasicMultiTokenBridge { using SafeMath for uint256; event FeeUpdated(bytes32 feeType, address indexed token, uint256 fee); @@ -141,7 +142,11 @@ contract ForeignFeeManagerMultiAMBErc20ToErc677 is BaseRewardAddressList, Ownabl feeToDistribute = feeToDistribute.add(diff); } - ERC677(_token).transfer(nextAddr, feeToDistribute); + if (_feeType == HOME_TO_FOREIGN_FEE) { + ERC677(_token).transfer(nextAddr, feeToDistribute); + } else { + IBurnableMintableERC677Token(_token).mint(nextAddr, feeToDistribute); + } nextAddr = getNextRewardAddress(nextAddr); require(nextAddr != address(0)); diff --git a/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/HomeMultiAMBErc20ToErc677.sol b/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/HomeMultiAMBErc20ToErc677.sol index b5daf4f74..d6a462374 100644 --- a/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/HomeMultiAMBErc20ToErc677.sol +++ b/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/HomeMultiAMBErc20ToErc677.sol @@ -2,6 +2,7 @@ pragma solidity 0.4.24; import "./BasicMultiAMBErc20ToErc677.sol"; import "./TokenProxy.sol"; +import "./HomeFeeManagerMultiAMBErc20ToErc677.sol"; import "../../interfaces/IBurnableMintableERC677Token.sol"; /** @@ -9,7 +10,7 @@ import "../../interfaces/IBurnableMintableERC677Token.sol"; * @dev Home side implementation for multi-erc20-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 HomeMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677 { +contract HomeMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677, HomeFeeManagerMultiAMBErc20ToErc677 { bytes32 internal constant TOKEN_IMAGE_CONTRACT = 0x20b8ca26cc94f39fab299954184cf3a9bd04f69543e4f454fab299f015b8130f; // keccak256(abi.encodePacked("tokenImageContract")) event NewTokenRegistered(address indexed foreignToken, address indexed homeToken); @@ -25,6 +26,9 @@ contract HomeMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677 { * @param _requestGasLimit the gas limit for the message execution. * @param _owner address of the owner of the mediator contract. * @param _tokenImage address of the PermittableToken contract that will be used for deploying of new tokens. + * @param _rewardAddreses list of reward addresses, between whom fees will be distributed. + * @param _fees array with initial fees for both bridge firections. + * [ 0 = homeToForeignFee, 1 = foreignToHomeFee ] */ function initialize( address _bridgeContract, @@ -33,7 +37,9 @@ contract HomeMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677 { uint256[2] _executionDailyLimitExecutionMaxPerTxArray, // [ 0 = _executionDailyLimit, 1 = _executionMaxPerTx ] uint256 _requestGasLimit, address _owner, - address _tokenImage + address _tokenImage, + address[] _rewardAddreses, + uint256[2] _fees // [ 0 = homeToForeignFee, 1 = foreignToHomeFee ] ) external onlyRelevantSender returns (bool) { require(!isInitialized()); require(_owner != address(0)); @@ -45,6 +51,12 @@ contract HomeMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677 { _setRequestGasLimit(_requestGasLimit); setOwner(_owner); _setTokenImage(_tokenImage); + if (_rewardAddreses.length > 0) { + _setRewardAddressList(_rewardAddreses); + } + _setFee(HOME_TO_FOREIGN_FEE, address(0), _fees[0]); + _setFee(FOREIGN_TO_HOME_FEE, address(0), _fees[1]); + setInitialize(); return isInitialized(); @@ -84,7 +96,7 @@ contract HomeMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677 { uint8 _decimals, address _recipient, uint256 _value - ) external { + ) external onlyMediator { string memory name = _name; string memory symbol = _symbol; if (bytes(name).length == 0) { @@ -96,7 +108,9 @@ contract HomeMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677 { address homeToken = new TokenProxy(tokenImage(), name, symbol, _decimals, bridgeContract().sourceChainId()); _setTokenAddressPair(_token, homeToken); _initializeTokenBridgeLimits(homeToken, _decimals); - super.handleBridgedTokens(ERC677(homeToken), _recipient, _value); + _setFee(HOME_TO_FOREIGN_FEE, homeToken, getFee(HOME_TO_FOREIGN_FEE, address(0))); + _setFee(FOREIGN_TO_HOME_FEE, homeToken, getFee(FOREIGN_TO_HOME_FEE, address(0))); + _handleBridgedTokens(ERC677(homeToken), _recipient, _value); emit NewTokenRegistered(_token, homeToken); } @@ -108,9 +122,10 @@ contract HomeMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677 { * @param _recipient address that will receive the tokens. * @param _value amount of tokens to be received. */ - function handleBridgedTokens(ERC677 _token, address _recipient, uint256 _value) public { + function handleBridgedTokens(ERC677 _token, address _recipient, uint256 _value) external onlyMediator { ERC677 homeToken = ERC677(homeTokenAddress(_token)); - super.handleBridgedTokens(homeToken, _recipient, _value); + require(isTokenRegistered(homeToken)); + _handleBridgedTokens(homeToken, _recipient, _value); } /** @@ -123,6 +138,9 @@ contract HomeMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677 { // if onTokenTransfer is called as a part of call to _relayTokens, this callback does nothing if (!lock()) { ERC677 token = ERC677(msg.sender); + // if msg.sender if not a valid token contract, this check will fail, since limits are zeros + // so the following check is not needed + // require(isTokenRegistered(token)); require(withinLimit(token, _value)); addTotalSpentPerDay(token, getCurrentDay(), _value); bridgeSpecificActionsOnTokenTransfer(token, _from, _value, _data); @@ -145,6 +163,9 @@ contract HomeMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677 { // which will call passMessage. require(!lock()); address to = address(this); + // if msg.sender if not a valid token contract, this check will fail, since limits are zeros + // so the following check is not needed + // require(isTokenRegistered(token)); require(withinLimit(token, _value)); addTotalSpentPerDay(token, getCurrentDay(), _value); @@ -161,8 +182,14 @@ contract HomeMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677 { */ function executeActionOnBridgedTokens(address _token, address _recipient, uint256 _value) internal { bytes32 _messageId = messageId(); - IBurnableMintableERC677Token(_token).mint(_recipient, _value); - emit TokensBridged(_token, _recipient, _value, _messageId); + uint256 valueToMint = _value; + uint256 fee = _distributeFee(FOREIGN_TO_HOME_FEE, _token, valueToMint); + if (fee > 0) { + emit FeeDistributed(fee, _token, _messageId); + valueToMint = valueToMint.sub(fee); + } + IBurnableMintableERC677Token(_token).mint(_recipient, valueToMint); + emit TokensBridged(_token, _recipient, valueToMint, _messageId); } /** @@ -221,8 +248,15 @@ contract HomeMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677 { */ function bridgeSpecificActionsOnTokenTransfer(ERC677 _token, address _from, uint256 _value, bytes _data) internal { if (!lock()) { - IBurnableMintableERC677Token(_token).burn(_value); - passMessage(_token, _from, chooseReceiver(_from, _data), _value); + bytes32 _messageId = messageId(); + uint256 valueToBridge = _value; + uint256 fee = _distributeFee(HOME_TO_FOREIGN_FEE, _token, valueToBridge); + if (fee > 0) { + emit FeeDistributed(fee, _token, _messageId); + valueToBridge = valueToBridge.sub(fee); + } + IBurnableMintableERC677Token(_token).burn(valueToBridge); + passMessage(_token, _from, chooseReceiver(_from, _data), valueToBridge); } } diff --git a/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/MultiTokenBridgeMediator.sol b/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/MultiTokenBridgeMediator.sol index e6832a02a..ab3be5a32 100644 --- a/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/MultiTokenBridgeMediator.sol +++ b/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/MultiTokenBridgeMediator.sol @@ -43,9 +43,7 @@ contract MultiTokenBridgeMediator is * @param _recipient address that will receive the tokens * @param _value amount of tokens to be received */ - function handleBridgedTokens(ERC677 _token, address _recipient, uint256 _value) public { - require(msg.sender == address(bridgeContract())); - require(messageSender() == mediatorContractOnOtherSide()); + function _handleBridgedTokens(ERC677 _token, address _recipient, uint256 _value) internal { if (withinExecutionLimit(_token, _value)) { addTotalExecutedPerDay(_token, getCurrentDay(), _value); executeActionOnBridgedTokens(_token, _recipient, _value); @@ -74,9 +72,7 @@ contract MultiTokenBridgeMediator is * It uses the information stored by passMessage method when the assets were initially transferred * @param _messageId id of the message which execution failed on the other network. */ - function fixFailedMessage(bytes32 _messageId) external { - require(msg.sender == address(bridgeContract())); - require(messageSender() == mediatorContractOnOtherSide()); + function fixFailedMessage(bytes32 _messageId) external onlyMediator { require(!messageFixed(_messageId)); address token = messageToken(_messageId); diff --git a/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/callflows.md b/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/callflows.md index 1e0bfb6f3..106662ab4 100644 --- a/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/callflows.md +++ b/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/callflows.md @@ -72,7 +72,7 @@ BasicHomeAMB::executeAffirmation ..........HomeMultiAMBErc20ToErc677::_setTokenAddressPair ..........BasicMultiTokenBridge::_initializeTokenBridgeLimits ............emit NewTokenRegistered -..........MultiTokenBridgeMediator::handleBridgedTokens +..........MultiTokenBridgeMediator::_handleBridgedTokens ............HomeMultiAMBErc20ToErc677::executeActionOnBridgedTokens ..............ERC677BridgeToken::mint ................<######> @@ -96,7 +96,7 @@ BasicHomeAMB::executeAffirmation >>Mediator ........HomeMultiAMBErc20ToErc677::handleBridgedTokens ..........HomeMultiAMBErc20ToErc677::homeTokenAddress -..........MultiTokenBridgeMediator::handleBridgedTokens +..........MultiTokenBridgeMediator::_handleBridgedTokens ............HomeMultiAMBErc20ToErc677::executeActionOnBridgedTokens ..............ERC677BridgeToken::mint ................<######> @@ -156,14 +156,15 @@ BasicForeignAMB::executeSignatures ........MessageProcessor::setMessageSender ........MessageProcessor::setMessageId >>Mediator -........MultiTokenBridgeMediator::handleBridgedTokens -..........ForeignMultiAMBErc20ToErc677::executeActionOnBridgedTokens -............ForeignFeeManagerMultiAMBErc20ToErc677::_distributeFee -............ForeignMultiAMBErc20ToErc677::_setMediatorBalance +........ForeignMultiAMBErc20ToErc677::handleBridgedTokens +..........MultiTokenBridgeMediator::_handleBridgedTokens +............ForeignMultiAMBErc20ToErc677::executeActionOnBridgedTokens +..............ForeignFeeManagerMultiAMBErc20ToErc677::_distributeFee +..............ForeignMultiAMBErc20ToErc677::_setMediatorBalance >>Token -..............ERC20::transfer +................ERC20::transfer >>Mediator -............emit TokensBridged +..............emit TokensBridged >>Bridge ......MessageProcessor::setMessageCallStatus ......ForeignAMB::emitEventOnMessageProcessed @@ -199,7 +200,7 @@ BasicForeignAMB::executeSignatures ........MessageProcessor::setMessageSender ........MessageProcessor::setMessageId >>Mediator -........[failed MultiTokenBridgeMediator::handleBridgedTokens] +........[failed ForeignMultiAMBErc20ToErc677::handleBridgedTokens] >>Bridge ......MessageProcessor::setMessageCallStatus ......MessageProcessor::setFailedMessageReceiver @@ -269,7 +270,7 @@ BasicHomeAMB::executeAffirmation ........MessageProcessor::setMessageSender ........MessageProcessor::setMessageId >>Mediator -........[failed MultiTokenBridgeMediator::handleBridgedTokens/deployAndHandleBridgedTokens] +........[failed HomeMultiAMBErc20ToErc677::handleBridgedTokens/deployAndHandleBridgedTokens] >>Bridge ......MessageProcessor::setMessageCallStatus ......MessageProcessor::setFailedMessageReceiver diff --git a/deploy/README.md b/deploy/README.md index eb1db1177..25d43fedf 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -1053,28 +1053,28 @@ HOME_MEDIATOR_REQUEST_GAS_LIMIT=2000000 FOREIGN_MEDIATOR_REQUEST_GAS_LIMIT=2000000 # Variable to define whether to collect fee on bridge transfers -# On this this bridge mode, fees collection on home side is not supported, should be false. -HOME_REWARDABLE=false # On this bridge mode only BOTH_DIRECTIONS is supported, leave false to disable fees collection +HOME_REWARDABLE=false +# On this this bridge mode, fees collection on home side is not supported, should be false. FOREIGN_REWARDABLE=false # Fee to be taken for every transaction directed from the Home network to the Foreign network -# Makes sense only when FOREIGN_REWARDABLE=BOTH_DIRECTIONS +# Makes sense only when HOME_REWARDABLE=BOTH_DIRECTIONS # e.g. 0.1% fee HOME_TRANSACTIONS_FEE=0.001 # Fee to be taken for every transaction directed from the Foreign network to the Home network -# Makes sense only when FOREIGN_REWARDABLE=BOTH_DIRECTIONS +# Makes sense only when HOME_REWARDABLE=BOTH_DIRECTIONS # e.g. 0.1% fee FOREIGN_TRANSACTIONS_FEE=0.001 # List of accounts where rewards should be transferred in Home network separated by space without quotes -# Makes sense only when FOREIGN_REWARDABLE=BOTH_DIRECTIONS -#E.g. FOREIGN_MEDIATOR_REWARD_ACCOUNTS=0x 0x 0x -FOREIGN_MEDIATOR_REWARD_ACCOUNTS=0x +# Makes sense only when HOME_REWARDABLE=BOTH_DIRECTIONS +#E.g. HOME_MEDIATOR_REWARD_ACCOUNTS=0x 0x 0x +HOME_MEDIATOR_REWARD_ACCOUNTS=0x # address of an already deployed PermittableToken contract that will be used as an implementation for all new created tokens -# leave 0x, if you want to deploy a new PermittableToken for further usage -HOME_ERC677_TOKEN_IMAGE=0x +# leave empty, if you want to deploy a new PermittableToken for further usage +HOME_ERC677_TOKEN_IMAGE= # The api url of an explorer to verify all the deployed contracts in Home network. Supported explorers: Blockscout, etherscan #HOME_EXPLORER_URL=https://blockscout.com/poa/core/api diff --git a/deploy/src/loadEnv.js b/deploy/src/loadEnv.js index bdebf7d60..2a988c692 100644 --- a/deploy/src/loadEnv.js +++ b/deploy/src/loadEnv.js @@ -408,20 +408,20 @@ if (env.BRIDGE_MODE === 'AMB_ERC_TO_NATIVE') { } if (env.BRIDGE_MODE === 'MULTI_AMB_ERC_TO_ERC') { - if (FOREIGN_REWARDABLE === 'ONE_DIRECTION') { + if (HOME_REWARDABLE === 'ONE_DIRECTION') { throw new Error( - `Only BOTH_DIRECTIONS is supported for collecting fees on Foreign Network on ${BRIDGE_MODE} bridge mode.` + `Only BOTH_DIRECTIONS is supported for collecting fees on Home Network on ${BRIDGE_MODE} bridge mode.` ) } - if (HOME_REWARDABLE !== 'false') { - throw new Error(`Collecting fees on Home Network on ${BRIDGE_MODE} bridge mode is not supported.`) + if (FOREIGN_REWARDABLE !== 'false') { + throw new Error(`Collecting fees on Foreign Network on ${BRIDGE_MODE} bridge mode is not supported.`) } - if (FOREIGN_REWARDABLE === 'BOTH_DIRECTIONS') { + if (HOME_REWARDABLE === 'BOTH_DIRECTIONS') { validations = { ...validations, - FOREIGN_MEDIATOR_REWARD_ACCOUNTS: addressesValidator() + HOME_MEDIATOR_REWARD_ACCOUNTS: addressesValidator() } } validations = { diff --git a/deploy/src/multi_amb_erc20_to_erc677/initializeForeign.js b/deploy/src/multi_amb_erc20_to_erc677/initializeForeign.js index 31a46984c..7ecae86cd 100644 --- a/deploy/src/multi_amb_erc20_to_erc677/initializeForeign.js +++ b/deploy/src/multi_amb_erc20_to_erc677/initializeForeign.js @@ -21,11 +21,7 @@ const { FOREIGN_UPGRADEABLE_ADMIN, FOREIGN_AMB_BRIDGE, FOREIGN_MEDIATOR_REQUEST_GAS_LIMIT, - DEPLOYMENT_ACCOUNT_PRIVATE_KEY, - FOREIGN_REWARDABLE, - HOME_TRANSACTIONS_FEE, - FOREIGN_TRANSACTIONS_FEE, - FOREIGN_MEDIATOR_REWARD_ACCOUNTS + DEPLOYMENT_ACCOUNT_PRIVATE_KEY } = require('../loadEnv') const DEPLOYMENT_ACCOUNT_ADDRESS = privateKeyToAddress(DEPLOYMENT_ACCOUNT_PRIVATE_KEY) @@ -41,10 +37,7 @@ async function initializeMediator({ executionDailyLimit, executionMaxPerTx, requestGasLimit, - owner, - rewardAddressList, - homeToForeignFee, - foreignToHomeFee + owner } }) { console.log(` @@ -56,14 +49,7 @@ async function initializeMediator({ EXECUTION_DAILY_LIMIT : ${executionDailyLimit} which is ${Web3Utils.fromWei(executionDailyLimit)} in eth, EXECUTION_MAX_AMOUNT_PER_TX: ${executionMaxPerTx} which is ${Web3Utils.fromWei(executionMaxPerTx)} in eth, MEDIATOR_REQUEST_GAS_LIMIT : ${requestGasLimit}, - OWNER: ${owner}, - REWARD_ADDRESS_LIST: [${rewardAddressList.join(', ')}]`) - if (FOREIGN_REWARDABLE) { - console.log(` - HOME_TO_FOREIGN_FEE: ${homeToForeignFee} which is ${HOME_TRANSACTIONS_FEE * 100}% - FOREIGN_TO_HOME_FEE: ${foreignToHomeFee} which is ${FOREIGN_TRANSACTIONS_FEE * 100}% - `) - } + OWNER: ${owner}`) return contract.methods .initialize( @@ -72,9 +58,7 @@ async function initializeMediator({ [dailyLimit.toString(), maxPerTx.toString(), minPerTx.toString()], [executionDailyLimit.toString(), executionMaxPerTx.toString()], requestGasLimit, - owner, - rewardAddressList, - [homeToForeignFee.toString(), foreignToHomeFee.toString()] + owner ) .encodeABI() } @@ -84,13 +68,6 @@ async function initialize({ homeBridge, foreignBridge }) { const contract = new web3Home.eth.Contract(ForeignBridge.abi, foreignBridge) console.log('\n[Foreign] Initializing Bridge Mediator with following parameters:') - let homeFeeInWei = '0' - let foreignFeeInWei = '0' - if (FOREIGN_REWARDABLE) { - homeFeeInWei = Web3Utils.toWei(HOME_TRANSACTIONS_FEE.toString(), 'ether') - foreignFeeInWei = Web3Utils.toWei(FOREIGN_TRANSACTIONS_FEE.toString(), 'ether') - } - const rewardList = FOREIGN_MEDIATOR_REWARD_ACCOUNTS.split(' ') const initializeData = await initializeMediator({ contract, @@ -104,9 +81,6 @@ async function initialize({ homeBridge, foreignBridge }) { minPerTx: FOREIGN_MIN_AMOUNT_PER_TX, executionDailyLimit: HOME_DAILY_LIMIT, executionMaxPerTx: HOME_MAX_AMOUNT_PER_TX, - rewardAddressList: rewardList, - homeToForeignFee: homeFeeInWei, - foreignToHomeFee: foreignFeeInWei } }) diff --git a/deploy/src/multi_amb_erc20_to_erc677/initializeHome.js b/deploy/src/multi_amb_erc20_to_erc677/initializeHome.js index 559d7d3a8..0bd529e37 100644 --- a/deploy/src/multi_amb_erc20_to_erc677/initializeHome.js +++ b/deploy/src/multi_amb_erc20_to_erc677/initializeHome.js @@ -22,6 +22,10 @@ const { HOME_BRIDGE_OWNER, HOME_UPGRADEABLE_ADMIN, DEPLOYMENT_ACCOUNT_PRIVATE_KEY, + HOME_REWARDABLE, + HOME_TRANSACTIONS_FEE, + FOREIGN_TRANSACTIONS_FEE, + HOME_MEDIATOR_REWARD_ACCOUNTS } = require('../loadEnv') const DEPLOYMENT_ACCOUNT_ADDRESS = privateKeyToAddress(DEPLOYMENT_ACCOUNT_PRIVATE_KEY) @@ -38,7 +42,10 @@ async function initializeMediator({ executionMaxPerTx, requestGasLimit, owner, - tokenImage + tokenImage, + rewardAddressList, + homeToForeignFee, + foreignToHomeFee } }) { console.log(` @@ -51,8 +58,15 @@ async function initializeMediator({ EXECUTION_MAX_AMOUNT_PER_TX: ${executionMaxPerTx} which is ${Web3Utils.fromWei(executionMaxPerTx)} in eth, MEDIATOR_REQUEST_GAS_LIMIT : ${requestGasLimit}, OWNER: ${owner}, - TOKEN_IMAGE: ${tokenImage} + TOKEN_IMAGE: ${tokenImage}, + REWARD_ADDRESS_LIST: [${rewardAddressList.join(', ')}] `) + if (HOME_REWARDABLE === 'BOTH_DIRECTIONS') { + console.log(` + HOME_TO_FOREIGN_FEE: ${homeToForeignFee} which is ${HOME_TRANSACTIONS_FEE * 100}% + FOREIGN_TO_HOME_FEE: ${foreignToHomeFee} which is ${FOREIGN_TRANSACTIONS_FEE * 100}% + `) + } return contract.methods .initialize( @@ -62,7 +76,9 @@ async function initializeMediator({ [executionDailyLimit.toString(), executionMaxPerTx.toString()], requestGasLimit, owner, - tokenImage + tokenImage, + rewardAddressList, + [homeToForeignFee.toString(), foreignToHomeFee.toString()] ) .encodeABI() } @@ -72,6 +88,13 @@ async function initialize({ homeBridge, foreignBridge, homeTokenImage }) { const mediatorContract = new web3Home.eth.Contract(HomeBridge.abi, homeBridge) console.log('\n[Home] Initializing Bridge Mediator with following parameters:') + let homeFeeInWei = '0' + let foreignFeeInWei = '0' + if (HOME_REWARDABLE === 'BOTH_DIRECTIONS') { + homeFeeInWei = Web3Utils.toWei(HOME_TRANSACTIONS_FEE.toString(), 'ether') + foreignFeeInWei = Web3Utils.toWei(FOREIGN_TRANSACTIONS_FEE.toString(), 'ether') + } + const rewardList = HOME_MEDIATOR_REWARD_ACCOUNTS.split(' ') const initializeMediatorData = await initializeMediator({ contract: mediatorContract, @@ -85,7 +108,10 @@ async function initialize({ homeBridge, foreignBridge, homeTokenImage }) { minPerTx: HOME_MIN_AMOUNT_PER_TX, executionDailyLimit: FOREIGN_DAILY_LIMIT, executionMaxPerTx: FOREIGN_MAX_AMOUNT_PER_TX, - tokenImage: homeTokenImage + tokenImage: homeTokenImage, + rewardAddressList: rewardList, + homeToForeignFee: homeFeeInWei, + foreignToHomeFee: foreignFeeInWei } }) diff --git a/test/multi_amb_erc20_to_erc677/foreign_mediator.test.js b/test/multi_amb_erc20_to_erc677/foreign_mediator.test.js index 51244bcb3..3270d168f 100644 --- a/test/multi_amb_erc20_to_erc677/foreign_mediator.test.js +++ b/test/multi_amb_erc20_to_erc677/foreign_mediator.test.js @@ -115,9 +115,7 @@ contract('ForeignMultiAMBErc20ToErc677', async accounts => { [dailyLimit, maxPerTx, minPerTx], [executionDailyLimit, executionMaxPerTx], maxGasPerTx, - owner, - [], - [ZERO, ZERO] + owner ).should.be.rejected // dailyLimit > maxPerTx @@ -127,9 +125,7 @@ contract('ForeignMultiAMBErc20ToErc677', async accounts => { [maxPerTx, maxPerTx, minPerTx], [executionDailyLimit, executionMaxPerTx], maxGasPerTx, - owner, - [], - [ZERO, ZERO] + owner ).should.be.rejected // maxPerTx > minPerTx @@ -139,9 +135,7 @@ contract('ForeignMultiAMBErc20ToErc677', async accounts => { [dailyLimit, minPerTx, minPerTx], [executionDailyLimit, executionMaxPerTx], maxGasPerTx, - owner, - [], - [ZERO, ZERO] + owner ).should.be.rejected // executionDailyLimit > executionMaxPerTx @@ -151,9 +145,7 @@ contract('ForeignMultiAMBErc20ToErc677', async accounts => { [dailyLimit, maxPerTx, minPerTx], [executionDailyLimit, executionDailyLimit], maxGasPerTx, - owner, - [], - [ZERO, ZERO] + owner ).should.be.rejected // maxGasPerTx > bridge maxGasPerTx @@ -163,9 +155,7 @@ contract('ForeignMultiAMBErc20ToErc677', async accounts => { [dailyLimit, maxPerTx, minPerTx], [executionDailyLimit, executionMaxPerTx], twoEthers, - owner, - [], - [ZERO, ZERO] + owner ).should.be.rejected // not valid owner @@ -175,9 +165,7 @@ contract('ForeignMultiAMBErc20ToErc677', async accounts => { [dailyLimit, maxPerTx, minPerTx], [executionDailyLimit, executionMaxPerTx], maxGasPerTx, - ZERO_ADDRESS, - [], - [ZERO, ZERO] + ZERO_ADDRESS ).should.be.rejected const { logs } = await contract.initialize( @@ -186,9 +174,7 @@ contract('ForeignMultiAMBErc20ToErc677', async accounts => { [dailyLimit, maxPerTx, minPerTx], [executionDailyLimit, executionMaxPerTx], maxGasPerTx, - owner, - [], - [ZERO, ZERO] + owner ).should.be.fulfilled // already initialized @@ -198,9 +184,7 @@ contract('ForeignMultiAMBErc20ToErc677', async accounts => { [dailyLimit, maxPerTx, minPerTx], [executionDailyLimit, executionMaxPerTx], maxGasPerTx, - owner, - [], - [ZERO, ZERO] + owner ).should.be.rejected // Then @@ -243,9 +227,7 @@ contract('ForeignMultiAMBErc20ToErc677', async accounts => { [dailyLimit, maxPerTx, minPerTx], [executionDailyLimit, executionMaxPerTx], maxGasPerTx, - owner, - [user2], - [ether('0.1'), ZERO] + owner ).should.be.fulfilled }) @@ -298,9 +280,7 @@ contract('ForeignMultiAMBErc20ToErc677', async accounts => { [dailyLimit, maxPerTx, minPerTx], [executionDailyLimit, executionMaxPerTx], maxGasPerTx, - owner, - [], - [ZERO, ZERO] + owner ).should.be.fulfilled const initialEvents = await getEvents(ambBridgeContract, { event: 'MockedEvent' }) @@ -620,30 +600,11 @@ contract('ForeignMultiAMBErc20ToErc677', async accounts => { await token.mint(user, '1').should.be.fulfilled await token.transfer(contract.address, '1', { from: user }).should.be.fulfilled - expect(await contract.dailyLimit(token.address)).to.be.bignumber.equal('3') - expect(await contract.maxPerTx(token.address)).to.be.bignumber.equal('2') + expect(await contract.dailyLimit(token.address)).to.be.bignumber.equal('10000') + expect(await contract.maxPerTx(token.address)).to.be.bignumber.equal('100') expect(await contract.minPerTx(token.address)).to.be.bignumber.equal('1') - expect(await contract.executionDailyLimit(token.address)).to.be.bignumber.equal('3') - expect(await contract.executionMaxPerTx(token.address)).to.be.bignumber.equal('2') - }) - - it('should initialize fees', async () => { - const HOME_TO_FOREIGN_FEE = await contract.HOME_TO_FOREIGN_FEE() - const FOREIGN_TO_HOME_FEE = await contract.FOREIGN_TO_HOME_FEE() - await contract.setFee(HOME_TO_FOREIGN_FEE, ZERO_ADDRESS, ether('0.01')) - await contract.setFee(FOREIGN_TO_HOME_FEE, ZERO_ADDRESS, ether('0.02')) - - expect(await contract.getFee(HOME_TO_FOREIGN_FEE, ZERO_ADDRESS)).to.be.bignumber.equal(ether('0.01')) - expect(await contract.getFee(FOREIGN_TO_HOME_FEE, ZERO_ADDRESS)).to.be.bignumber.equal(ether('0.02')) - expect(await contract.getFee(HOME_TO_FOREIGN_FEE, token.address)).to.be.bignumber.equal(ZERO) - expect(await contract.getFee(FOREIGN_TO_HOME_FEE, token.address)).to.be.bignumber.equal(ZERO) - - await token.transfer(contract.address, value, { from: user }).should.be.fulfilled - - expect(await contract.getFee(HOME_TO_FOREIGN_FEE, ZERO_ADDRESS)).to.be.bignumber.equal(ether('0.01')) - expect(await contract.getFee(FOREIGN_TO_HOME_FEE, ZERO_ADDRESS)).to.be.bignumber.equal(ether('0.02')) - expect(await contract.getFee(HOME_TO_FOREIGN_FEE, token.address)).to.be.bignumber.equal(ether('0.01')) - expect(await contract.getFee(FOREIGN_TO_HOME_FEE, token.address)).to.be.bignumber.equal(ether('0.02')) + expect(await contract.executionDailyLimit(token.address)).to.be.bignumber.equal('10000') + expect(await contract.executionMaxPerTx(token.address)).to.be.bignumber.equal('100') }) }) @@ -692,6 +653,24 @@ contract('ForeignMultiAMBErc20ToErc677', async accounts => { expect(event[0].returnValues.value).to.be.equal(value.toString()) expect(event[0].returnValues.messageId).to.be.equal(exampleMessageId) }) + + it('should not allow to use unregistered tokens', async () => { + const otherToken = await ERC20Mock.new('Test', 'TST', 18) + await otherToken.mint(contract.address, value) + const data = await contract.contract.methods + .handleBridgedTokens(otherToken.address, user, value.toString()) + .encodeABI() + + await ambBridgeContract.executeMessageCall( + contract.address, + otherSideMediator.address, + data, + failedMessageId, + 1000000 + ).should.be.fulfilled + + expect(await ambBridgeContract.messageCallStatus(failedMessageId)).to.be.equal(false) + }) }) describe('requestFailedMessageFix', () => { @@ -896,9 +875,7 @@ contract('ForeignMultiAMBErc20ToErc677', async accounts => { [dailyLimit, maxPerTx, minPerTx], [executionDailyLimit, executionMaxPerTx], maxGasPerTx, - owner, - [owner], - [ZERO, ZERO] + owner ).should.be.fulfilled expect(await contract.totalSpentPerDay(token.address, currentDay)).to.be.bignumber.equal(ZERO) @@ -911,7 +888,6 @@ contract('ForeignMultiAMBErc20ToErc677', async accounts => { expect(await token.balanceOf(contract.address)).to.be.bignumber.equal(twoEthers) await token.transfer(contract.address, halfEther, { from: user }).should.be.fulfilled - await contract.setFee(await contract.FOREIGN_TO_HOME_FEE(), token.address, ether('0.1')).should.be.fulfilled await contract.setDailyLimit(token.address, ether('5')).should.be.fulfilled await contract.setMaxPerTx(token.address, ether('2')).should.be.fulfilled @@ -939,7 +915,6 @@ contract('ForeignMultiAMBErc20ToErc677', async accounts => { expect(await token.balanceOf(contract.address)).to.be.bignumber.equal(twoEthers) await token.transfer(contract.address, halfEther, { from: user }).should.be.fulfilled - await contract.setFee(await contract.FOREIGN_TO_HOME_FEE(), token.address, ether('0.1')).should.be.fulfilled expect(await contract.mediatorBalance(token.address)).to.be.bignumber.equal(halfEther) expect(await token.balanceOf(contract.address)).to.be.bignumber.equal(twoEthers.add(halfEther)) @@ -969,299 +944,4 @@ contract('ForeignMultiAMBErc20ToErc677', async accounts => { expect(events.length).to.be.equal(3) }) }) - - describe('fees management', () => { - beforeEach(async () => { - await contract.initialize( - ambBridgeContract.address, - otherSideMediator.address, - [dailyLimit, maxPerTx, minPerTx], - [executionDailyLimit, executionMaxPerTx], - maxGasPerTx, - owner, - [owner], - [ether('0.02'), ether('0.01')] - ).should.be.fulfilled - - const initialEvents = await getEvents(ambBridgeContract, { event: 'MockedEvent' }) - expect(initialEvents.length).to.be.equal(0) - }) - - it('change reward addresses', async () => { - await contract.addRewardAddress(accounts[8], { from: user }).should.be.rejected - await contract.addRewardAddress(owner).should.be.rejected - await contract.addRewardAddress(accounts[8]).should.be.fulfilled - - expect(await contract.rewardAddressList()).to.be.eql([accounts[8], owner]) - expect(await contract.rewardAddressCount()).to.be.bignumber.equal('2') - expect(await contract.isRewardAddress(owner)).to.be.equal(true) - expect(await contract.isRewardAddress(accounts[8])).to.be.equal(true) - - await contract.addRewardAddress(accounts[9]).should.be.fulfilled - expect(await contract.rewardAddressList()).to.be.eql([accounts[9], accounts[8], owner]) - expect(await contract.rewardAddressCount()).to.be.bignumber.equal('3') - - await contract.removeRewardAddress(owner, { from: user }).should.be.rejected - await contract.removeRewardAddress(accounts[7]).should.be.rejected - await contract.removeRewardAddress(accounts[8]).should.be.fulfilled - await contract.removeRewardAddress(accounts[8]).should.be.rejected - - expect(await contract.rewardAddressList()).to.be.eql([accounts[9], owner]) - expect(await contract.rewardAddressCount()).to.be.bignumber.equal('2') - expect(await contract.isRewardAddress(accounts[8])).to.be.equal(false) - - await contract.removeRewardAddress(owner).should.be.fulfilled - expect(await contract.rewardAddressList()).to.be.eql([accounts[9]]) - expect(await contract.rewardAddressCount()).to.be.bignumber.equal('1') - expect(await contract.isRewardAddress(owner)).to.be.equal(false) - - await contract.removeRewardAddress(accounts[9]).should.be.fulfilled - expect(await contract.rewardAddressList()).to.be.eql([]) - expect(await contract.rewardAddressCount()).to.be.bignumber.equal('0') - expect(await contract.isRewardAddress(accounts[9])).to.be.equal(false) - }) - - describe('update fee parameters', () => { - it('should update default fee value', async () => { - const feeType = await contract.HOME_TO_FOREIGN_FEE() - await contract.setFee(feeType, ZERO_ADDRESS, ether('0.1'), { from: user }).should.be.rejected - await contract.setFee(feeType, ZERO_ADDRESS, ether('1.1'), { from: owner }).should.be.rejected - const { logs } = await contract.setFee(feeType, ZERO_ADDRESS, ether('0.1'), { from: owner }).should.be.fulfilled - - expectEventInLogs(logs, 'FeeUpdated') - expect(await contract.getFee(feeType, ZERO_ADDRESS)).to.be.bignumber.equal(ether('0.1')) - expect(await contract.getFee(await contract.FOREIGN_TO_HOME_FEE(), ZERO_ADDRESS)).to.be.bignumber.equal( - ether('0.01') - ) - }) - - it('should update default opposite direction fee value', async () => { - const feeType = await contract.FOREIGN_TO_HOME_FEE() - await contract.setFee(feeType, ZERO_ADDRESS, ether('0.1'), { from: user }).should.be.rejected - await contract.setFee(feeType, ZERO_ADDRESS, ether('1.1'), { from: owner }).should.be.rejected - const { logs } = await contract.setFee(feeType, ZERO_ADDRESS, ether('0.1'), { from: owner }).should.be.fulfilled - - expectEventInLogs(logs, 'FeeUpdated') - expect(await contract.getFee(feeType, ZERO_ADDRESS)).to.be.bignumber.equal(ether('0.1')) - expect(await contract.getFee(await contract.HOME_TO_FOREIGN_FEE(), ZERO_ADDRESS)).to.be.bignumber.equal( - ether('0.02') - ) - }) - - it('should update fee value for registered token', async () => { - const feeType = await contract.HOME_TO_FOREIGN_FEE() - await token.mint(user, twoEthers, { from: owner }).should.be.fulfilled - - await contract.setFee(feeType, token.address, ether('0.1'), { from: user }).should.be.rejected - await contract.setFee(feeType, token.address, ether('1.1'), { from: owner }).should.be.rejected - await contract.setFee(feeType, token.address, ether('0.1'), { from: owner }).should.be.rejected - await token.transfer(contract.address, value, { from: user }).should.be.fulfilled - await contract.setFee(feeType, token.address, ether('0.1'), { from: user }).should.be.rejected - await contract.setFee(feeType, token.address, ether('1.1'), { from: owner }).should.be.rejected - const { logs } = await contract.setFee(feeType, token.address, ether('0.1'), { from: owner }).should.be - .fulfilled - - expectEventInLogs(logs, 'FeeUpdated') - expect(await contract.getFee(feeType, token.address)).to.be.bignumber.equal(ether('0.1')) - expect(await contract.getFee(await contract.FOREIGN_TO_HOME_FEE(), token.address)).to.be.bignumber.equal( - ether('0.01') - ) - }) - - it('should update opposite direction fee value for registered token', async () => { - const feeType = await contract.FOREIGN_TO_HOME_FEE() - await token.mint(user, twoEthers, { from: owner }).should.be.fulfilled - - await contract.setFee(feeType, token.address, ether('0.1'), { from: user }).should.be.rejected - await contract.setFee(feeType, token.address, ether('1.1'), { from: owner }).should.be.rejected - await contract.setFee(feeType, token.address, ether('0.1'), { from: owner }).should.be.rejected - await token.transfer(contract.address, value, { from: user }).should.be.fulfilled - await contract.setFee(feeType, token.address, ether('0.1'), { from: user }).should.be.rejected - await contract.setFee(feeType, token.address, ether('1.1'), { from: owner }).should.be.rejected - const { logs } = await contract.setFee(feeType, token.address, ether('0.1'), { from: owner }).should.be - .fulfilled - - expectEventInLogs(logs, 'FeeUpdated') - expect(await contract.getFee(feeType, token.address)).to.be.bignumber.equal(ether('0.1')) - expect(await contract.getFee(await contract.HOME_TO_FOREIGN_FEE(), token.address)).to.be.bignumber.equal( - ether('0.02') - ) - }) - }) - - describe('distribute fee for home => foreign direction', async () => { - beforeEach(async () => { - await token.mint(user, twoEthers, { from: owner }).should.be.fulfilled - expect(await token.balanceOf(user)).to.be.bignumber.equal(twoEthers) - await contract.setFee(await contract.FOREIGN_TO_HOME_FEE(), ZERO_ADDRESS, ZERO).should.be.fulfilled - await token.transfer(contract.address, value, { from: user }).should.be.fulfilled - expect(await token.balanceOf(user)).to.be.bignumber.equal(oneEther) - }) - - it('should collect and distribute 0% fee', async () => { - await contract.setFee(await contract.HOME_TO_FOREIGN_FEE(), token.address, ZERO).should.be.fulfilled - - const data = await contract.contract.methods - .handleBridgedTokens(token.address, user, value.toString()) - .encodeABI() - - await ambBridgeContract.executeMessageCall( - contract.address, - otherSideMediator.address, - data, - exampleMessageId, - 1000000 - ).should.be.fulfilled - - expect(await ambBridgeContract.messageCallStatus(exampleMessageId)).to.be.equal(true) - expect(await contract.totalExecutedPerDay(token.address, currentDay)).to.be.bignumber.equal(value) - - const event = await getEvents(contract, { event: 'TokensBridged' }) - expect(event.length).to.be.equal(1) - expect(event[0].returnValues.token).to.be.equal(token.address) - expect(event[0].returnValues.recipient).to.be.equal(user) - expect(event[0].returnValues.value).to.be.equal(value.toString()) - expect(event[0].returnValues.messageId).to.be.equal(exampleMessageId) - - const feeEvents = await getEvents(contract, { event: 'FeeDistributed' }) - expect(feeEvents.length).to.be.equal(0) - - expect(await token.balanceOf(user)).to.be.bignumber.equal(twoEthers) - expect(await token.balanceOf(contract.address)).to.be.bignumber.equal(ZERO) - expect(await token.balanceOf(owner)).to.be.bignumber.equal(ZERO) - }) - - it('should collect and distribute 2% fee', async () => { - const data = await contract.contract.methods - .handleBridgedTokens(token.address, user, value.toString()) - .encodeABI() - - await ambBridgeContract.executeMessageCall( - contract.address, - otherSideMediator.address, - data, - exampleMessageId, - 1000000 - ).should.be.fulfilled - - expect(await ambBridgeContract.messageCallStatus(exampleMessageId)).to.be.equal(true) - expect(await contract.totalExecutedPerDay(token.address, currentDay)).to.be.bignumber.equal(value) - - const event = await getEvents(contract, { event: 'TokensBridged' }) - expect(event.length).to.be.equal(1) - expect(event[0].returnValues.token).to.be.equal(token.address) - expect(event[0].returnValues.recipient).to.be.equal(user) - expect(event[0].returnValues.value).to.be.equal(ether('0.98').toString()) - expect(event[0].returnValues.messageId).to.be.equal(exampleMessageId) - - const feeEvents = await getEvents(contract, { event: 'FeeDistributed' }) - expect(feeEvents.length).to.be.equal(1) - - expect(await token.balanceOf(user)).to.be.bignumber.equal(ether('1.98')) - expect(await token.balanceOf(contract.address)).to.be.bignumber.equal(ZERO) - expect(await token.balanceOf(owner)).to.be.bignumber.equal(ether('0.02')) - }) - - it('should collect and distribute 2% fee between two reward addresses', async () => { - await contract.addRewardAddress(accounts[9]).should.be.fulfilled - expect(await contract.rewardAddressCount()).to.be.bignumber.equal('2') - - const data = await contract.contract.methods - .handleBridgedTokens(token.address, user, ether('0.100000000000000050').toString(10)) - .encodeABI() - - await ambBridgeContract.executeMessageCall( - contract.address, - otherSideMediator.address, - data, - exampleMessageId, - 1000000 - ).should.be.fulfilled - - expect(await ambBridgeContract.messageCallStatus(exampleMessageId)).to.be.equal(true) - expect(await contract.totalExecutedPerDay(token.address, currentDay)).to.be.bignumber.equal( - ether('0.100000000000000050') - ) - - const event = await getEvents(contract, { event: 'TokensBridged' }) - expect(event.length).to.be.equal(1) - - const feeEvents = await getEvents(contract, { event: 'FeeDistributed' }) - expect(feeEvents.length).to.be.equal(1) - - expect(await token.balanceOf(user)).to.be.bignumber.equal(ether('1.098000000000000049')) - expect(await token.balanceOf(contract.address)).to.be.bignumber.equal(ether('0.899999999999999950')) - const balance1 = (await token.balanceOf(owner)).toString() - const balance2 = (await token.balanceOf(accounts[9])).toString() - expect( - (balance1 === '1000000000000001' && balance2 === '1000000000000000') || - (balance1 === '1000000000000000' && balance2 === '1000000000000001') - ).to.be.equal(true) - }) - }) - - describe('distribute fee for foreign => home direction', async () => { - beforeEach(async () => { - await token.mint(user, twoEthers).should.be.fulfilled - }) - - it('should collect and distribute 0% fee', async () => { - await contract.setFee(await contract.FOREIGN_TO_HOME_FEE(), ZERO_ADDRESS, ZERO).should.be.fulfilled - - expect(await contract.totalSpentPerDay(token.address, currentDay)).to.be.bignumber.equal(ZERO) - await token.transfer(contract.address, value, { from: user }) - expect(await contract.totalSpentPerDay(token.address, currentDay)).to.be.bignumber.equal(value) - expect(await token.balanceOf(contract.address)).to.be.bignumber.equal(value) - await token.transfer(contract.address, value, { from: user }) - expect(await contract.totalSpentPerDay(token.address, currentDay)).to.be.bignumber.equal(twoEthers) - expect(await token.balanceOf(contract.address)).to.be.bignumber.equal(twoEthers) - - const feeEvents = await getEvents(contract, { event: 'FeeDistributed' }) - expect(feeEvents.length).to.be.equal(0) - }) - - it('should collect and distribute 1% fee', async () => { - expect(await contract.totalSpentPerDay(token.address, currentDay)).to.be.bignumber.equal(ZERO) - await token.transfer(contract.address, value, { from: user }) - expect(await contract.totalSpentPerDay(token.address, currentDay)).to.be.bignumber.equal(value) - expect(await token.balanceOf(contract.address)).to.be.bignumber.equal(ether('0.99')) - expect(await token.balanceOf(owner)).to.be.bignumber.equal(ether('0.01')) - await token.transfer(contract.address, value, { from: user }) - expect(await contract.totalSpentPerDay(token.address, currentDay)).to.be.bignumber.equal(twoEthers) - expect(await token.balanceOf(contract.address)).to.be.bignumber.equal(ether('1.98')) - expect(await token.balanceOf(owner)).to.be.bignumber.equal(ether('0.02')) - - const feeEvents = await getEvents(contract, { event: 'FeeDistributed' }) - expect(feeEvents.length).to.be.equal(2) - }) - - it('should collect and distribute 1% fee between two reward addresses', async () => { - await contract.addRewardAddress(accounts[9]).should.be.fulfilled - expect(await contract.rewardAddressCount()).to.be.bignumber.equal('2') - - expect(await contract.totalSpentPerDay(token.address, currentDay)).to.be.bignumber.equal(ZERO) - await token.transfer(contract.address, ether('0.200000000000000100'), { from: user }) - expect(await contract.totalSpentPerDay(token.address, currentDay)).to.be.bignumber.equal( - ether('0.200000000000000100') - ) - expect(await token.balanceOf(contract.address)).to.be.bignumber.equal(ether('0.198000000000000099')) - - const balance1 = (await token.balanceOf(owner)).toString() - const balance2 = (await token.balanceOf(accounts[9])).toString() - expect( - (balance1 === '1000000000000001' && balance2 === '1000000000000000') || - (balance1 === '1000000000000000' && balance2 === '1000000000000001') - ).to.be.equal(true) - - await token.transfer(contract.address, value, { from: user }).should.be.fulfilled - expect(await contract.totalSpentPerDay(token.address, currentDay)).to.be.bignumber.equal( - ether('1.200000000000000100') - ) - expect(await token.balanceOf(contract.address)).to.be.bignumber.equal(ether('1.188000000000000099')) - - const feeEvents = await getEvents(contract, { event: 'FeeDistributed' }) - expect(feeEvents.length).to.be.equal(2) - }) - }) - }) }) diff --git a/test/multi_amb_erc20_to_erc677/home_mediator.test.js b/test/multi_amb_erc20_to_erc677/home_mediator.test.js index 1217b34c5..c8e3f3dce 100644 --- a/test/multi_amb_erc20_to_erc677/home_mediator.test.js +++ b/test/multi_amb_erc20_to_erc677/home_mediator.test.js @@ -51,9 +51,7 @@ contract('HomeMultiAMBErc20ToErc677', async accounts => { [dailyLimit, maxPerTx, minPerTx], [executionDailyLimit, executionMaxPerTx], maxGasPerTx, - owner, - [], - [ZERO, ZERO] + owner ) token = await ERC677BridgeToken.new('TEST', 'TST', 18) tokenImage = await PermittableToken.new('TEST', 'TST', 18, 1337) @@ -137,7 +135,15 @@ contract('HomeMultiAMBErc20ToErc677', async accounts => { expect(events.length).to.be.equal(1) expect(events[0].returnValues.foreignToken).to.be.equal(token.address) const homeToken = await PermittableToken.at(events[0].returnValues.homeToken) - expect(await homeToken.balanceOf(user)).to.be.bignumber.equal(value) + const fee = await contract.getFee(await contract.FOREIGN_TO_HOME_FEE(), ZERO_ADDRESS) + const rewardAccounts = (await contract.rewardAddressCount()).toNumber() + const bridgedValue = + rewardAccounts > 0 + ? toBN(value) + .mul(oneEther.sub(fee)) + .div(oneEther) + : value + expect(await homeToken.balanceOf(user)).to.be.bignumber.equal(bridgedValue) return homeToken } @@ -165,7 +171,9 @@ contract('HomeMultiAMBErc20ToErc677', async accounts => { [executionDailyLimit, executionMaxPerTx], maxGasPerTx, owner, - tokenImage.address + tokenImage.address, + [], + [ZERO, ZERO] ).should.be.rejected // dailyLimit > maxPerTx @@ -176,7 +184,9 @@ contract('HomeMultiAMBErc20ToErc677', async accounts => { [executionDailyLimit, executionMaxPerTx], maxGasPerTx, owner, - tokenImage.address + tokenImage.address, + [], + [ZERO, ZERO] ).should.be.rejected // maxPerTx > minPerTx @@ -187,7 +197,9 @@ contract('HomeMultiAMBErc20ToErc677', async accounts => { [executionDailyLimit, executionMaxPerTx], maxGasPerTx, owner, - tokenImage.address + tokenImage.address, + [], + [ZERO, ZERO] ).should.be.rejected // executionDailyLimit > executionMaxPerTx @@ -198,7 +210,9 @@ contract('HomeMultiAMBErc20ToErc677', async accounts => { [executionDailyLimit, executionDailyLimit], maxGasPerTx, owner, - tokenImage.address + tokenImage.address, + [], + [ZERO, ZERO] ).should.be.rejected // maxGasPerTx > bridge maxGasPerTx @@ -209,7 +223,9 @@ contract('HomeMultiAMBErc20ToErc677', async accounts => { [executionDailyLimit, executionMaxPerTx], twoEthers, owner, - tokenImage.address + tokenImage.address, + [], + [ZERO, ZERO] ).should.be.rejected // not valid owner @@ -220,7 +236,9 @@ contract('HomeMultiAMBErc20ToErc677', async accounts => { [executionDailyLimit, executionMaxPerTx], maxGasPerTx, ZERO_ADDRESS, - tokenImage.address + tokenImage.address, + [], + [ZERO, ZERO] ).should.be.rejected // token image is not a contract @@ -231,7 +249,9 @@ contract('HomeMultiAMBErc20ToErc677', async accounts => { [executionDailyLimit, executionMaxPerTx], maxGasPerTx, owner, - owner + owner, + [], + [ZERO, ZERO] ).should.be.rejected const { logs } = await contract.initialize( @@ -241,7 +261,9 @@ contract('HomeMultiAMBErc20ToErc677', async accounts => { [executionDailyLimit, executionMaxPerTx], maxGasPerTx, owner, - tokenImage.address + tokenImage.address, + [], + [ZERO, ZERO] ).should.be.fulfilled // already initialized @@ -252,7 +274,9 @@ contract('HomeMultiAMBErc20ToErc677', async accounts => { [executionDailyLimit, executionMaxPerTx], maxGasPerTx, owner, - tokenImage.address + tokenImage.address, + [], + [ZERO, ZERO] ).should.be.rejected // Then @@ -297,7 +321,9 @@ contract('HomeMultiAMBErc20ToErc677', async accounts => { [executionDailyLimit, executionMaxPerTx], maxGasPerTx, owner, - tokenImage.address + tokenImage.address, + [user2], + [ether('0.1'), ZERO] ).should.be.fulfilled }) @@ -344,7 +370,9 @@ contract('HomeMultiAMBErc20ToErc677', async accounts => { [executionDailyLimit, executionMaxPerTx], maxGasPerTx, owner, - tokenImage.address + tokenImage.address, + [], + [ZERO, ZERO] ).should.be.fulfilled const initialEvents = await getEvents(ambBridgeContract, { event: 'MockedEvent' }) @@ -430,11 +458,30 @@ contract('HomeMultiAMBErc20ToErc677', async accounts => { token = await bridgeToken(token, '1') expect(await token.decimals()).to.be.bignumber.equal('0') - expect(await contract.dailyLimit(token.address)).to.be.bignumber.equal('3') - expect(await contract.maxPerTx(token.address)).to.be.bignumber.equal('2') + expect(await contract.dailyLimit(token.address)).to.be.bignumber.equal('10000') + expect(await contract.maxPerTx(token.address)).to.be.bignumber.equal('100') expect(await contract.minPerTx(token.address)).to.be.bignumber.equal('1') - expect(await contract.executionDailyLimit(token.address)).to.be.bignumber.equal('3') - expect(await contract.executionMaxPerTx(token.address)).to.be.bignumber.equal('2') + expect(await contract.executionDailyLimit(token.address)).to.be.bignumber.equal('10000') + expect(await contract.executionMaxPerTx(token.address)).to.be.bignumber.equal('100') + }) + + it('should initialize fees', async () => { + const HOME_TO_FOREIGN_FEE = await contract.HOME_TO_FOREIGN_FEE() + const FOREIGN_TO_HOME_FEE = await contract.FOREIGN_TO_HOME_FEE() + await contract.setFee(HOME_TO_FOREIGN_FEE, ZERO_ADDRESS, ether('0.01')) + await contract.setFee(FOREIGN_TO_HOME_FEE, ZERO_ADDRESS, ether('0.02')) + + expect(await contract.getFee(HOME_TO_FOREIGN_FEE, ZERO_ADDRESS)).to.be.bignumber.equal(ether('0.01')) + expect(await contract.getFee(FOREIGN_TO_HOME_FEE, ZERO_ADDRESS)).to.be.bignumber.equal(ether('0.02')) + expect(await contract.getFee(HOME_TO_FOREIGN_FEE, token.address)).to.be.bignumber.equal(ZERO) + expect(await contract.getFee(FOREIGN_TO_HOME_FEE, token.address)).to.be.bignumber.equal(ZERO) + + const homeToken = await bridgeToken(token) + + expect(await contract.getFee(HOME_TO_FOREIGN_FEE, ZERO_ADDRESS)).to.be.bignumber.equal(ether('0.01')) + expect(await contract.getFee(FOREIGN_TO_HOME_FEE, ZERO_ADDRESS)).to.be.bignumber.equal(ether('0.02')) + expect(await contract.getFee(HOME_TO_FOREIGN_FEE, homeToken.address)).to.be.bignumber.equal(ether('0.01')) + expect(await contract.getFee(FOREIGN_TO_HOME_FEE, homeToken.address)).to.be.bignumber.equal(ether('0.02')) }) }) @@ -749,6 +796,22 @@ contract('HomeMultiAMBErc20ToErc677', async accounts => { expect(event[1].returnValues.value).to.be.equal(value.toString()) expect(event[1].returnValues.messageId).to.be.equal(exampleMessageId) }) + + it('should not allow to use unregistered tokens', async () => { + const data = await contract.contract.methods + .handleBridgedTokens(homeToken.address, user, value.toString()) + .encodeABI() + + await ambBridgeContract.executeMessageCall( + contract.address, + otherSideMediator.address, + data, + failedMessageId, + 1000000 + ).should.be.fulfilled + + expect(await ambBridgeContract.messageCallStatus(failedMessageId)).to.be.equal(false) + }) }) describe('requestFailedMessageFix for token registration', () => { @@ -950,4 +1013,349 @@ contract('HomeMultiAMBErc20ToErc677', async accounts => { } }) }) + + describe('fees management', () => { + beforeEach(async () => { + await contract.initialize( + ambBridgeContract.address, + otherSideMediator.address, + [dailyLimit, maxPerTx, minPerTx], + [executionDailyLimit, executionMaxPerTx], + maxGasPerTx, + owner, + tokenImage.address, + [owner], + [ether('0.02'), ether('0.01')] + ).should.be.fulfilled + + const initialEvents = await getEvents(ambBridgeContract, { event: 'MockedEvent' }) + expect(initialEvents.length).to.be.equal(0) + }) + + it('change reward addresses', async () => { + await contract.addRewardAddress(accounts[8], { from: user }).should.be.rejected + await contract.addRewardAddress(owner).should.be.rejected + await contract.addRewardAddress(accounts[8]).should.be.fulfilled + + expect(await contract.rewardAddressList()).to.be.eql([accounts[8], owner]) + expect(await contract.rewardAddressCount()).to.be.bignumber.equal('2') + expect(await contract.isRewardAddress(owner)).to.be.equal(true) + expect(await contract.isRewardAddress(accounts[8])).to.be.equal(true) + + await contract.addRewardAddress(accounts[9]).should.be.fulfilled + expect(await contract.rewardAddressList()).to.be.eql([accounts[9], accounts[8], owner]) + expect(await contract.rewardAddressCount()).to.be.bignumber.equal('3') + + await contract.removeRewardAddress(owner, { from: user }).should.be.rejected + await contract.removeRewardAddress(accounts[7]).should.be.rejected + await contract.removeRewardAddress(accounts[8]).should.be.fulfilled + await contract.removeRewardAddress(accounts[8]).should.be.rejected + + expect(await contract.rewardAddressList()).to.be.eql([accounts[9], owner]) + expect(await contract.rewardAddressCount()).to.be.bignumber.equal('2') + expect(await contract.isRewardAddress(accounts[8])).to.be.equal(false) + + await contract.removeRewardAddress(owner).should.be.fulfilled + expect(await contract.rewardAddressList()).to.be.eql([accounts[9]]) + expect(await contract.rewardAddressCount()).to.be.bignumber.equal('1') + expect(await contract.isRewardAddress(owner)).to.be.equal(false) + + await contract.removeRewardAddress(accounts[9]).should.be.fulfilled + expect(await contract.rewardAddressList()).to.be.eql([]) + expect(await contract.rewardAddressCount()).to.be.bignumber.equal('0') + expect(await contract.isRewardAddress(accounts[9])).to.be.equal(false) + }) + + describe('update fee parameters', () => { + it('should update default fee value', async () => { + const feeType = await contract.HOME_TO_FOREIGN_FEE() + await contract.setFee(feeType, ZERO_ADDRESS, ether('0.1'), { from: user }).should.be.rejected + await contract.setFee(feeType, ZERO_ADDRESS, ether('1.1'), { from: owner }).should.be.rejected + const { logs } = await contract.setFee(feeType, ZERO_ADDRESS, ether('0.1'), { from: owner }).should.be.fulfilled + + expectEventInLogs(logs, 'FeeUpdated') + expect(await contract.getFee(feeType, ZERO_ADDRESS)).to.be.bignumber.equal(ether('0.1')) + expect(await contract.getFee(await contract.FOREIGN_TO_HOME_FEE(), ZERO_ADDRESS)).to.be.bignumber.equal( + ether('0.01') + ) + }) + + it('should update default opposite direction fee value', async () => { + const feeType = await contract.FOREIGN_TO_HOME_FEE() + await contract.setFee(feeType, ZERO_ADDRESS, ether('0.1'), { from: user }).should.be.rejected + await contract.setFee(feeType, ZERO_ADDRESS, ether('1.1'), { from: owner }).should.be.rejected + const { logs } = await contract.setFee(feeType, ZERO_ADDRESS, ether('0.1'), { from: owner }).should.be.fulfilled + + expectEventInLogs(logs, 'FeeUpdated') + expect(await contract.getFee(feeType, ZERO_ADDRESS)).to.be.bignumber.equal(ether('0.1')) + expect(await contract.getFee(await contract.HOME_TO_FOREIGN_FEE(), ZERO_ADDRESS)).to.be.bignumber.equal( + ether('0.02') + ) + }) + + it('should update fee value for registered token', async () => { + const feeType = await contract.HOME_TO_FOREIGN_FEE() + await token.mint(user, twoEthers, { from: owner }).should.be.fulfilled + + await contract.setFee(feeType, token.address, ether('0.1'), { from: user }).should.be.rejected + await contract.setFee(feeType, token.address, ether('1.1'), { from: owner }).should.be.rejected + await contract.setFee(feeType, token.address, ether('0.1'), { from: owner }).should.be.rejected + + token = await bridgeToken(token) + + await contract.setFee(feeType, token.address, ether('0.1'), { from: user }).should.be.rejected + await contract.setFee(feeType, token.address, ether('1.1'), { from: owner }).should.be.rejected + const { logs } = await contract.setFee(feeType, token.address, ether('0.1'), { from: owner }).should.be + .fulfilled + + expectEventInLogs(logs, 'FeeUpdated') + expect(await contract.getFee(feeType, token.address)).to.be.bignumber.equal(ether('0.1')) + expect(await contract.getFee(await contract.FOREIGN_TO_HOME_FEE(), token.address)).to.be.bignumber.equal( + ether('0.01') + ) + }) + + it('should update opposite direction fee value for registered token', async () => { + const feeType = await contract.FOREIGN_TO_HOME_FEE() + await token.mint(user, twoEthers, { from: owner }).should.be.fulfilled + + await contract.setFee(feeType, token.address, ether('0.1'), { from: user }).should.be.rejected + await contract.setFee(feeType, token.address, ether('1.1'), { from: owner }).should.be.rejected + await contract.setFee(feeType, token.address, ether('0.1'), { from: owner }).should.be.rejected + token = await bridgeToken(token) + await contract.setFee(feeType, token.address, ether('0.1'), { from: user }).should.be.rejected + await contract.setFee(feeType, token.address, ether('1.1'), { from: owner }).should.be.rejected + const { logs } = await contract.setFee(feeType, token.address, ether('0.1'), { from: owner }).should.be + .fulfilled + + expectEventInLogs(logs, 'FeeUpdated') + expect(await contract.getFee(feeType, token.address)).to.be.bignumber.equal(ether('0.1')) + expect(await contract.getFee(await contract.HOME_TO_FOREIGN_FEE(), token.address)).to.be.bignumber.equal( + ether('0.02') + ) + }) + }) + + describe('distribute fee for foreign => home direction', async () => { + beforeEach(async () => { + await token.mint(user, twoEthers, { from: owner }).should.be.fulfilled + expect(await token.balanceOf(user)).to.be.bignumber.equal(twoEthers) + }) + + it('should collect and distribute 0% fee', async () => { + await contract.setFee(await contract.FOREIGN_TO_HOME_FEE(), ZERO_ADDRESS, ZERO).should.be.fulfilled + const homeToken = await bridgeToken(token) + + let event = await getEvents(contract, { event: 'TokensBridged' }) + expect(event.length).to.be.equal(1) + expect(event[0].returnValues.token).to.be.equal(homeToken.address) + expect(event[0].returnValues.recipient).to.be.equal(user) + expect(event[0].returnValues.value).to.be.equal(value.toString()) + expect(event[0].returnValues.messageId).to.be.equal(deployMessageId) + + let feeEvents = await getEvents(contract, { event: 'FeeDistributed' }) + expect(feeEvents.length).to.be.equal(0) + + const data = await contract.contract.methods + .handleBridgedTokens(token.address, user, value.toString()) + .encodeABI() + + await ambBridgeContract.executeMessageCall( + contract.address, + otherSideMediator.address, + data, + exampleMessageId, + 1000000 + ).should.be.fulfilled + + expect(await ambBridgeContract.messageCallStatus(exampleMessageId)).to.be.equal(true) + expect(await contract.totalExecutedPerDay(homeToken.address, currentDay)).to.be.bignumber.equal(twoEthers) + + event = await getEvents(contract, { event: 'TokensBridged' }) + expect(event.length).to.be.equal(2) + expect(event[1].returnValues.token).to.be.equal(homeToken.address) + expect(event[1].returnValues.recipient).to.be.equal(user) + expect(event[1].returnValues.value).to.be.equal(value.toString()) + expect(event[1].returnValues.messageId).to.be.equal(exampleMessageId) + + feeEvents = await getEvents(contract, { event: 'FeeDistributed' }) + expect(feeEvents.length).to.be.equal(0) + + expect(await homeToken.balanceOf(user)).to.be.bignumber.equal(twoEthers) + expect(await homeToken.balanceOf(contract.address)).to.be.bignumber.equal(ZERO) + expect(await homeToken.balanceOf(owner)).to.be.bignumber.equal(ZERO) + }) + + it('should collect and distribute 1% fee', async () => { + const homeToken = await bridgeToken(token) + + let event = await getEvents(contract, { event: 'TokensBridged' }) + expect(event.length).to.be.equal(1) + expect(event[0].returnValues.token).to.be.equal(homeToken.address) + expect(event[0].returnValues.recipient).to.be.equal(user) + expect(event[0].returnValues.value).to.be.equal(ether('0.99').toString()) + expect(event[0].returnValues.messageId).to.be.equal(deployMessageId) + + let feeEvents = await getEvents(contract, { event: 'FeeDistributed' }) + expect(feeEvents.length).to.be.equal(1) + + expect(await homeToken.balanceOf(user)).to.be.bignumber.equal(ether('0.99')) + expect(await homeToken.balanceOf(contract.address)).to.be.bignumber.equal(ZERO) + expect(await homeToken.balanceOf(owner)).to.be.bignumber.equal(ether('0.01')) + + const data = await contract.contract.methods + .handleBridgedTokens(token.address, user, value.toString()) + .encodeABI() + + await ambBridgeContract.executeMessageCall( + contract.address, + otherSideMediator.address, + data, + exampleMessageId, + 1000000 + ).should.be.fulfilled + + expect(await ambBridgeContract.messageCallStatus(exampleMessageId)).to.be.equal(true) + expect(await contract.totalExecutedPerDay(homeToken.address, currentDay)).to.be.bignumber.equal(twoEthers) + + event = await getEvents(contract, { event: 'TokensBridged' }) + expect(event.length).to.be.equal(2) + expect(event[1].returnValues.token).to.be.equal(homeToken.address) + expect(event[1].returnValues.recipient).to.be.equal(user) + expect(event[1].returnValues.value).to.be.equal(ether('0.99').toString()) + expect(event[1].returnValues.messageId).to.be.equal(exampleMessageId) + + feeEvents = await getEvents(contract, { event: 'FeeDistributed' }) + expect(feeEvents.length).to.be.equal(2) + + expect(await homeToken.balanceOf(user)).to.be.bignumber.equal(ether('1.98')) + expect(await homeToken.balanceOf(contract.address)).to.be.bignumber.equal(ZERO) + expect(await homeToken.balanceOf(owner)).to.be.bignumber.equal(ether('0.02')) + }) + + it('should collect and distribute 1% fee between two reward addresses', async () => { + await contract.addRewardAddress(accounts[9]).should.be.fulfilled + expect(await contract.rewardAddressCount()).to.be.bignumber.equal('2') + const homeToken = await bridgeToken(token, ether('0.200000000000000100')) + + let event = await getEvents(contract, { event: 'TokensBridged' }) + expect(event.length).to.be.equal(1) + + let feeEvents = await getEvents(contract, { event: 'FeeDistributed' }) + expect(feeEvents.length).to.be.equal(1) + + expect(await homeToken.balanceOf(user)).to.be.bignumber.equal(ether('0.198000000000000099')) + expect(await homeToken.balanceOf(contract.address)).to.be.bignumber.equal(ZERO) + const balance1 = (await homeToken.balanceOf(owner)).toString() + const balance2 = (await homeToken.balanceOf(accounts[9])).toString() + expect( + (balance1 === '1000000000000001' && balance2 === '1000000000000000') || + (balance1 === '1000000000000000' && balance2 === '1000000000000001') + ).to.be.equal(true) + + const data = await contract.contract.methods + .handleBridgedTokens(token.address, user, ether('0.200000000000000100').toString(10)) + .encodeABI() + + await ambBridgeContract.executeMessageCall( + contract.address, + otherSideMediator.address, + data, + exampleMessageId, + 1000000 + ).should.be.fulfilled + + expect(await ambBridgeContract.messageCallStatus(exampleMessageId)).to.be.equal(true) + expect(await contract.totalExecutedPerDay(homeToken.address, currentDay)).to.be.bignumber.equal( + ether('0.400000000000000200') + ) + + event = await getEvents(contract, { event: 'TokensBridged' }) + expect(event.length).to.be.equal(2) + + feeEvents = await getEvents(contract, { event: 'FeeDistributed' }) + expect(feeEvents.length).to.be.equal(2) + }) + }) + + describe('distribute fee for home => foreign direction', async () => { + beforeEach(async () => { + await contract.setFee(await contract.FOREIGN_TO_HOME_FEE(), ZERO_ADDRESS, ZERO).should.be.fulfilled + const homeToken = await bridgeToken(token) + + const data = await contract.contract.methods + .handleBridgedTokens(token.address, user, value.toString(10)) + .encodeABI() + + await ambBridgeContract.executeMessageCall( + contract.address, + otherSideMediator.address, + data, + exampleMessageId, + 1000000 + ).should.be.fulfilled + expect(await ambBridgeContract.messageCallStatus(exampleMessageId)).to.be.equal(true) + + token = homeToken + }) + + it('should collect and distribute 0% fee', async () => { + await contract.setFee(await contract.HOME_TO_FOREIGN_FEE(), token.address, ZERO).should.be.fulfilled + + expect(await contract.totalSpentPerDay(token.address, currentDay)).to.be.bignumber.equal(ZERO) + await token.transfer(contract.address, value, { from: user }) + expect(await contract.totalSpentPerDay(token.address, currentDay)).to.be.bignumber.equal(value) + expect(await token.balanceOf(contract.address)).to.be.bignumber.equal(ZERO) + await token.transfer(contract.address, value, { from: user }) + expect(await contract.totalSpentPerDay(token.address, currentDay)).to.be.bignumber.equal(twoEthers) + expect(await token.balanceOf(contract.address)).to.be.bignumber.equal(ZERO) + + const feeEvents = await getEvents(contract, { event: 'FeeDistributed' }) + expect(feeEvents.length).to.be.equal(0) + }) + + it('should collect and distribute 2% fee', async () => { + expect(await contract.totalSpentPerDay(token.address, currentDay)).to.be.bignumber.equal(ZERO) + await token.transfer(contract.address, value, { from: user }) + expect(await contract.totalSpentPerDay(token.address, currentDay)).to.be.bignumber.equal(value) + expect(await token.balanceOf(contract.address)).to.be.bignumber.equal(ZERO) + expect(await token.balanceOf(owner)).to.be.bignumber.equal(ether('0.02')) + await token.transfer(contract.address, value, { from: user }) + expect(await contract.totalSpentPerDay(token.address, currentDay)).to.be.bignumber.equal(twoEthers) + expect(await token.balanceOf(contract.address)).to.be.bignumber.equal(ZERO) + expect(await token.balanceOf(owner)).to.be.bignumber.equal(ether('0.04')) + + const feeEvents = await getEvents(contract, { event: 'FeeDistributed' }) + expect(feeEvents.length).to.be.equal(2) + }) + + it('should collect and distribute 2% fee between two reward addresses', async () => { + await contract.addRewardAddress(accounts[9]).should.be.fulfilled + expect(await contract.rewardAddressCount()).to.be.bignumber.equal('2') + + expect(await contract.totalSpentPerDay(token.address, currentDay)).to.be.bignumber.equal(ZERO) + await token.transfer(contract.address, ether('0.100000000000000050'), { from: user }) + expect(await contract.totalSpentPerDay(token.address, currentDay)).to.be.bignumber.equal( + ether('0.100000000000000050') + ) + expect(await token.balanceOf(contract.address)).to.be.bignumber.equal(ZERO) + + const balance1 = (await token.balanceOf(owner)).toString() + const balance2 = (await token.balanceOf(accounts[9])).toString() + expect( + (balance1 === '1000000000000001' && balance2 === '1000000000000000') || + (balance1 === '1000000000000000' && balance2 === '1000000000000001') + ).to.be.equal(true) + + await token.transfer(contract.address, value, { from: user }).should.be.fulfilled + expect(await contract.totalSpentPerDay(token.address, currentDay)).to.be.bignumber.equal( + ether('1.100000000000000050') + ) + expect(await token.balanceOf(contract.address)).to.be.bignumber.equal(ZERO) + + const feeEvents = await getEvents(contract, { event: 'FeeDistributed' }) + expect(feeEvents.length).to.be.equal(2) + }) + }) + }) }) diff --git a/test/poa20_test.js b/test/poa20_test.js index e8d3ec016..3f8ec2fdb 100644 --- a/test/poa20_test.js +++ b/test/poa20_test.js @@ -7,6 +7,8 @@ const StakingTest = artifacts.require('Staking.sol') const HomeErcToErcBridge = artifacts.require('HomeBridgeErcToErc.sol') const ForeignNativeToErcBridge = artifacts.require('ForeignBridgeNativeToErc.sol') const BridgeValidators = artifacts.require('BridgeValidators.sol') +const TokenProxy = artifacts.require('TokenProxy.sol') +const PermittableTokenMock = artifacts.require('PermittableTokenMock.sol') const { expect } = require('chai') const ethUtil = require('ethereumjs-util') @@ -25,11 +27,10 @@ const executionMaxPerTx = halfEther const ZERO = new BN(0) const decimalShiftZero = 0 -async function testERC677BridgeToken(accounts, rewardable) { +async function testERC677BridgeToken(accounts, rewardable, permittable, createToken) { let token const owner = accounts[0] const user = accounts[1] - const tokenContract = rewardable ? POA20RewardableMock : POA20 async function addBridge(token, bridge, options = { from: owner }) { if (rewardable) { @@ -47,10 +48,10 @@ async function testERC677BridgeToken(accounts, rewardable) { beforeEach(async () => { const args = ['POA ERC20 Foundation', 'POA20', 18] - if (rewardable) { + if (permittable) { args.push(100) } - token = await tokenContract.new(...args) + token = await createToken(args) }) it('default values', async () => { expect(await token.symbol()).to.be.equal('POA20') @@ -554,10 +555,10 @@ async function testERC677BridgeToken(accounts, rewardable) { const halfEther = ether('0.5') const args = ['Roman Token', 'RST', 18] - if (rewardable) { + if (permittable) { args.push(100) } - const tokenSecond = await tokenContract.new(...args) + const tokenSecond = await createToken(args) await tokenSecond.mint(accounts[0], halfEther).should.be.fulfilled halfEther.should.be.bignumber.equal(await tokenSecond.balanceOf(accounts[0])) @@ -606,10 +607,10 @@ async function testERC677BridgeToken(accounts, rewardable) { }) it('if transfer called on contract, still works even if onTokenTransfer doesnot exist', async () => { const args = ['Some', 'Token', 18] - if (rewardable) { + if (permittable) { args.push(100) } - const someContract = await tokenContract.new(...args) + const someContract = await createToken(args) await token.mint(user, '2', { from: owner }).should.be.fulfilled const tokenTransfer = await token.transfer(someContract.address, '1', { from: user }).should.be.fulfilled const tokenTransfer2 = await token.transfer(accounts[0], '1', { from: user }).should.be.fulfilled @@ -625,7 +626,7 @@ async function testERC677BridgeToken(accounts, rewardable) { await token.renounceOwnership().should.be.rejectedWith(ERROR_MSG) }) }) - if (rewardable) { + if (permittable) { describe('permit', () => { const privateKey = '0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501210' const infinite = new BN('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16) @@ -690,7 +691,7 @@ async function testERC677BridgeToken(accounts, rewardable) { ;(await token.allowance.call(holder, spender)).should.be.bignumber.equal(infinite) // The caller of `permit` can't spend holder's funds - await token.transferFrom(holder, accounts[9], '10000').should.be.rejectedWith(ERROR_MSG_OPCODE) + await token.transferFrom(holder, accounts[9], '10000').should.be.rejected ;(await token.balanceOf.call(holder)).should.be.bignumber.equal(new BN('10000')) // Spender can transfer all holder's funds @@ -823,9 +824,7 @@ async function testERC677BridgeToken(accounts, rewardable) { ;(await token.expirations.call(holder, spender)).should.be.bignumber.equal(new BN('0')) // Spender can't transfer the remaining holder's funds because of zero allowance - await token - .transferFrom(holder, accounts[9], '4000', { from: spender }) - .should.be.rejectedWith(ERROR_MSG_OPCODE) + await token.transferFrom(holder, accounts[9], '4000', { from: spender }).should.be.rejected }) it('should fail when invalid signature or parameters', async () => { let signature = permitSign( @@ -912,9 +911,36 @@ async function testERC677BridgeToken(accounts, rewardable) { } contract('ERC677BridgeToken', async accounts => { - await testERC677BridgeToken(accounts, false) + await testERC677BridgeToken(accounts, false, false, args => POA20.new(...args)) }) contract('ERC677BridgeTokenRewardable', async accounts => { - await testERC677BridgeToken(accounts, true) + await testERC677BridgeToken(accounts, true, true, args => POA20RewardableMock.new(...args)) +}) + +contract('TokenProxy', async accounts => { + const createToken = async args => { + const impl = await PermittableTokenMock.new(...args) + const proxy = await TokenProxy.new(impl.address, ...args) + return PermittableTokenMock.at(proxy.address) + } + + await testERC677BridgeToken(accounts, false, true, createToken) + + describe('constants', () => { + let token + beforeEach(async () => { + token = await createToken(['POA ERC20 Foundation', 'POA20', 18, 100]) + }) + + it('should return version', async () => { + expect(await token.version()).to.be.equal('1') + }) + + it('should return PERMIT_TYPEHASH', async () => { + expect(await token.PERMIT_TYPEHASH()).to.be.equal( + '0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb' + ) + }) + }) }) From dcb1cb94029230842a2748b5ab6e1b0e888283bc Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Mon, 3 Aug 2020 22:01:58 +0700 Subject: [PATCH 3/4] Deinit token in fixFailedMessage (#463) --- .../BasicMultiAMBErc20ToErc677.sol | 2 +- .../ForeignMultiAMBErc20ToErc677.sol | 40 +++++++++++++++++++ .../MultiTokenBridgeMediator.sol | 2 +- .../multi_amb_erc20_to_erc677/callflows.md | 18 +++++---- .../foreign_mediator.test.js | 10 +++++ 5 files changed, 63 insertions(+), 9 deletions(-) diff --git a/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/BasicMultiAMBErc20ToErc677.sol b/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/BasicMultiAMBErc20ToErc677.sol index 13a82bf55..18d897386 100644 --- a/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/BasicMultiAMBErc20ToErc677.sol +++ b/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/BasicMultiAMBErc20ToErc677.sol @@ -71,7 +71,7 @@ contract BasicMultiAMBErc20ToErc677 is * @return patch value of the version */ function getBridgeInterfacesVersion() external pure returns (uint64 major, uint64 minor, uint64 patch) { - return (1, 0, 0); + return (1, 0, 1); } /** diff --git a/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/ForeignMultiAMBErc20ToErc677.sol b/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/ForeignMultiAMBErc20ToErc677.sol index 5bf104c7d..8717ded34 100644 --- a/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/ForeignMultiAMBErc20ToErc677.sol +++ b/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/ForeignMultiAMBErc20ToErc677.sol @@ -158,6 +158,28 @@ contract ForeignMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677 { setMessageToken(_messageId, _token); setMessageValue(_messageId, _value); setMessageRecipient(_messageId, _from); + + if (!isKnownToken) { + _setTokenRegistrationMessageId(_token, _messageId); + } + } + + /** + * @dev Handles the request to fix transferred assets which bridged message execution failed on the other network. + * It uses the information stored by passMessage method when the assets were initially transferred + * @param _messageId id of the message which execution failed on the other network. + */ + function fixFailedMessage(bytes32 _messageId) public { + super.fixFailedMessage(_messageId); + address token = messageToken(_messageId); + if (_messageId == tokenRegistrationMessageId(token)) { + delete uintStorage[keccak256(abi.encodePacked("dailyLimit", token))]; + delete uintStorage[keccak256(abi.encodePacked("maxPerTx", token))]; + delete uintStorage[keccak256(abi.encodePacked("minPerTx", token))]; + delete uintStorage[keccak256(abi.encodePacked("executionDailyLimit", token))]; + delete uintStorage[keccak256(abi.encodePacked("executionMaxPerTx", token))]; + _setTokenRegistrationMessageId(token, bytes32(0)); + } } /** @@ -213,6 +235,15 @@ contract ForeignMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677 { return uintStorage[keccak256(abi.encodePacked("mediatorBalance", _token))]; } + /** + * @dev Returns message id where specified token was first seen and deploy on the other side was requested. + * @param _token address of token contract. + * @return message id of the send message. + */ + function tokenRegistrationMessageId(address _token) public view returns (bytes32) { + return bytes32(uintStorage[keccak256(abi.encodePacked("tokenRegistrationMessageId", _token))]); + } + /** * @dev Updates expected token balance of the contract. * @param _token address of token contract. @@ -221,4 +252,13 @@ contract ForeignMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677 { function _setMediatorBalance(address _token, uint256 _balance) internal { uintStorage[keccak256(abi.encodePacked("mediatorBalance", _token))] = _balance; } + + /** + * @dev Updates message id where specified token was first seen and deploy on the other side was requested. + * @param _token address of token contract. + * @param _messageId message id of the send message. + */ + function _setTokenRegistrationMessageId(address _token, bytes32 _messageId) internal { + uintStorage[keccak256(abi.encodePacked("tokenRegistrationMessageId", _token))] = uint256(_messageId); + } } diff --git a/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/MultiTokenBridgeMediator.sol b/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/MultiTokenBridgeMediator.sol index ab3be5a32..d41939b0a 100644 --- a/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/MultiTokenBridgeMediator.sol +++ b/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/MultiTokenBridgeMediator.sol @@ -72,7 +72,7 @@ contract MultiTokenBridgeMediator is * It uses the information stored by passMessage method when the assets were initially transferred * @param _messageId id of the message which execution failed on the other network. */ - function fixFailedMessage(bytes32 _messageId) external onlyMediator { + function fixFailedMessage(bytes32 _messageId) public onlyMediator { require(!messageFixed(_messageId)); address token = messageToken(_messageId); diff --git a/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/callflows.md b/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/callflows.md index 106662ab4..80238e594 100644 --- a/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/callflows.md +++ b/contracts/upgradeable_contracts/multi_amb_erc20_to_erc677/callflows.md @@ -32,6 +32,7 @@ ForeignMultiAMBErc20ToErc677::onTokenTransfer/relayTokens ....MultiTokenBridgeMediator::setMessageToken ....TransferInfoStorage::setMessageValue ....TransferInfoStorage::setMessageRecipient +....ForeignMultiAMBErc20ToErc677::_setTokenRegistrationMessageId >>Bridge ....MessageDelivery::requireToPassMessage ......ForeignAMB::emitEventOnMessageRequest @@ -308,15 +309,18 @@ BasicForeignAMB::executeSignatures ........MessageProcessor::setMessageSender ........MessageProcessor::setMessageId >>Mediator -........MultiTokenBridgeMediator::fixFailedMessage -..........MultiTokenBridgeMediator::messageToken -..........MultiTokenBridgeMediator::messageRecipient -..........MultiTokenBridgeMediator::messageValue -..........ForeignMultiAMBErc20ToErc677::executeActionOnFixedTokens +........HomeMultiAMBErc20ToErc677::fixFailedMessage +..........MultiTokenBridgeMediator::fixFailedMessage +............MultiTokenBridgeMediator::messageToken +............MultiTokenBridgeMediator::messageRecipient +............MultiTokenBridgeMediator::messageValue +............ForeignMultiAMBErc20ToErc677::executeActionOnFixedTokens +............ForeignMultiAMBErc20ToErc677::tokenRegistrationMessageId +............[ForeignMultiAMBErc20ToErc677::_setTokenRegistrationMessageId] >>Token -............ERC20::transfer +..............ERC20::transfer >>Mediator -..........emit FailedMessageFixed +............emit FailedMessageFixed >>Bridge ......MessageProcessor::setMessageCallStatus ......ForeignAMB::emitEventOnMessageProcessed diff --git a/test/multi_amb_erc20_to_erc677/foreign_mediator.test.js b/test/multi_amb_erc20_to_erc677/foreign_mediator.test.js index 3270d168f..77835c489 100644 --- a/test/multi_amb_erc20_to_erc677/foreign_mediator.test.js +++ b/test/multi_amb_erc20_to_erc677/foreign_mediator.test.js @@ -547,6 +547,7 @@ contract('ForeignMultiAMBErc20ToErc677', async accounts => { let events = await getEvents(ambBridgeContract, { event: 'MockedEvent' }) expect(events.length).to.be.equal(1) let encodedData = strip0x(events[0].returnValues.encodedData) + const { messageId } = events[0].returnValues let calldata = encodedData.slice(2 * (4 + 20 + 8 + 20 + 20 + 4 + 1 + 1 + 1 + 2 + 2)) // remove AMB header expect(calldata.slice(0, 8)).to.be.equal('2ae87cdd') let args = web3.eth.abi.decodeParameters( @@ -559,6 +560,7 @@ contract('ForeignMultiAMBErc20ToErc677', async accounts => { expect(args[3]).to.be.equal((await token.decimals()).toString()) expect(args[4]).to.be.equal(receiver) expect(args[5]).to.be.equal(value.toString()) + expect(await contract.tokenRegistrationMessageId(token.address)).to.be.equal(messageId) await send() @@ -835,6 +837,14 @@ contract('ForeignMultiAMBErc20ToErc677', async accounts => { expect(await token.balanceOf(user)).to.be.bignumber.equal(twoEthers) expect(await contract.mediatorBalance(token.address)).to.be.bignumber.equal(ZERO) expect(await contract.messageFixed(transferMessageId)).to.be.equal(true) + expect(await contract.tokenRegistrationMessageId(token.address)).to.be.equal( + '0x0000000000000000000000000000000000000000000000000000000000000000' + ) + expect(await contract.minPerTx(token.address)).to.be.bignumber.equal('0') + expect(await contract.maxPerTx(token.address)).to.be.bignumber.equal('0') + expect(await contract.dailyLimit(token.address)).to.be.bignumber.equal('0') + expect(await contract.executionMaxPerTx(token.address)).to.be.bignumber.equal('0') + expect(await contract.executionDailyLimit(token.address)).to.be.bignumber.equal('0') const event = await getEvents(contract, { event: 'FailedMessageFixed' }) expect(event.length).to.be.equal(1) From 336df6b9d28e53cad1c3f9eea8355b942c3ebd6b Mon Sep 17 00:00:00 2001 From: Alexander Kolotov Date: Mon, 3 Aug 2020 18:20:50 +0300 Subject: [PATCH 4/4] Bump package version to 5.3.1 (#464) --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5408cf13e..15d0f3a8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "token-bridge-contracts", - "version": "5.3.0", + "version": "5.3.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 0ec2f1542..40bd21227 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "token-bridge-contracts", - "version": "5.3.0", + "version": "5.3.1", "description": "Bridge", "main": "index.js", "scripts": {