Move fee manager into home mediator (#462)

This commit is contained in:
Kirill Fedoseev 2020-08-03 19:36:12 +07:00 committed by GitHub
parent ab3529e776
commit a1ff878b1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 680 additions and 513 deletions

View File

@ -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;
}
}

View File

@ -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.

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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);
}

View File

@ -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));

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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 = {

View File

@ -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
}
})

View File

@ -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
}
})

View File

@ -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)
})
})
})
})

View File

@ -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)
})
})
})
})

View File

@ -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'
)
})
})
})