diff --git a/contracts/CrossChainUpgradeableProxy.sol b/contracts/CrossChainUpgradeableProxy.sol index 07c8cc2..782b1f9 100644 --- a/contracts/CrossChainUpgradeableProxy.sol +++ b/contracts/CrossChainUpgradeableProxy.sol @@ -3,17 +3,21 @@ pragma solidity ^0.7.0; import "@openzeppelin/contracts/contracts/proxy/TransparentUpgradeableProxy.sol"; -// https://github.com/ethereum-optimism/optimism/blob/c7bc85deee999b8edfbe187b302d0ea262638ca9/packages/contracts/contracts/optimistic-ethereum/iOVM/bridge/messaging/iOVM_CrossDomainMessenger.sol -interface iOVM_CrossDomainMessenger { - function xDomainMessageSender() external view returns (address); +// https://docs.tokenbridge.net/amb-bridge/development-of-a-cross-chain-application/how-to-develop-xchain-apps-by-amb#call-a-method-in-another-chain-using-the-amb-bridge + +interface IAMB { + function messageSender() external view returns (address); +} + +interface IOmniBridge { + function bridgeContract() external view returns (IAMB); } /** * @dev TransparentUpgradeableProxy where admin acts from a different chain. */ contract CrossChainUpgradeableProxy is TransparentUpgradeableProxy { - // https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/deployments/README.md - iOVM_CrossDomainMessenger public immutable messenger; + IOmniBridge public immutable omniBridge; /** * @dev Initializes an upgradeable proxy backed by the implementation at `_logic`. @@ -22,16 +26,16 @@ contract CrossChainUpgradeableProxy is TransparentUpgradeableProxy { address _logic, address _admin, bytes memory _data, - iOVM_CrossDomainMessenger _messenger + IOmniBridge _omniBridge ) TransparentUpgradeableProxy(_logic, _admin, _data) { - messenger = _messenger; + omniBridge = _omniBridge; } /** * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the cross chain admin. */ modifier ifAdmin() override { - if (msg.sender == address(messenger) && messenger.xDomainMessageSender() == _admin()) { + if (msg.sender == address(omniBridge) && omniBridge.bridgeContract().messageSender() == _admin()) { _; } else { _fallback(); diff --git a/contracts/Mocks/ERC677.sol b/contracts/Mocks/ERC677.sol new file mode 100644 index 0000000..59fe335 --- /dev/null +++ b/contracts/Mocks/ERC677.sol @@ -0,0 +1,856 @@ +// File: openzeppelin-solidity/contracts/token/ERC20/ERC20Basic.sol + +pragma solidity ^0.4.24; + +/** + * @title ERC20Basic + * @dev Simpler version of ERC20 interface + * See https://github.com/ethereum/EIPs/issues/179 + */ +contract ERC20Basic { + function totalSupply() public view returns (uint256); + + function balanceOf(address _who) public view returns (uint256); + + function transfer(address _to, uint256 _value) public returns (bool); + + event Transfer(address indexed from, address indexed to, uint256 value); +} + +// File: openzeppelin-solidity/contracts/math/SafeMath.sol + +pragma solidity ^0.4.24; + +/** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ +library SafeMath { + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 _a, uint256 _b) internal pure returns (uint256 c) { + // Gas optimization: this is cheaper than asserting 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 + if (_a == 0) { + return 0; + } + + c = _a * _b; + assert(c / _a == _b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 _a, uint256 _b) internal pure returns (uint256) { + // assert(_b > 0); // Solidity automatically throws when dividing by 0 + // uint256 c = _a / _b; + // assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold + return _a / _b; + } + + /** + * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 _a, uint256 _b) internal pure returns (uint256) { + assert(_b <= _a); + return _a - _b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 _a, uint256 _b) internal pure returns (uint256 c) { + c = _a + _b; + assert(c >= _a); + return c; + } +} + +// File: openzeppelin-solidity/contracts/token/ERC20/BasicToken.sol + +pragma solidity ^0.4.24; + +/** + * @title Basic token + * @dev Basic version of StandardToken, with no allowances. + */ +contract BasicToken is ERC20Basic { + using SafeMath for uint256; + + mapping(address => uint256) internal balances; + + uint256 internal totalSupply_; + + /** + * @dev Total number of tokens in existence + */ + function totalSupply() public view returns (uint256) { + return totalSupply_; + } + + /** + * @dev Transfer token for a specified address + * @param _to The address to transfer to. + * @param _value The amount to be transferred. + */ + function transfer(address _to, uint256 _value) public returns (bool) { + require(_value <= balances[msg.sender]); + require(_to != address(0)); + + balances[msg.sender] = balances[msg.sender].sub(_value); + balances[_to] = balances[_to].add(_value); + emit Transfer(msg.sender, _to, _value); + return true; + } + + /** + * @dev Gets the balance of the specified address. + * @param _owner The address to query the the balance of. + * @return An uint256 representing the amount owned by the passed address. + */ + function balanceOf(address _owner) public view returns (uint256) { + return balances[_owner]; + } +} + +// File: openzeppelin-solidity/contracts/token/ERC20/BurnableToken.sol + +pragma solidity ^0.4.24; + +/** + * @title Burnable Token + * @dev Token that can be irreversibly burned (destroyed). + */ +contract BurnableToken is BasicToken { + event Burn(address indexed burner, uint256 value); + + /** + * @dev Burns a specific amount of tokens. + * @param _value The amount of token to be burned. + */ + function burn(uint256 _value) public { + _burn(msg.sender, _value); + } + + function _burn(address _who, uint256 _value) internal { + require(_value <= balances[_who]); + // no need to require value <= totalSupply, since that would imply the + // sender's balance is greater than the totalSupply, which *should* be an assertion failure + + balances[_who] = balances[_who].sub(_value); + totalSupply_ = totalSupply_.sub(_value); + emit Burn(_who, _value); + emit Transfer(_who, address(0), _value); + } +} + +// File: openzeppelin-solidity/contracts/token/ERC20/ERC20.sol + +pragma solidity ^0.4.24; + +/** + * @title ERC20 interface + * @dev see https://github.com/ethereum/EIPs/issues/20 + */ +contract ERC20 is ERC20Basic { + function allowance(address _owner, address _spender) public view returns (uint256); + + function transferFrom( + address _from, + address _to, + uint256 _value + ) public returns (bool); + + function approve(address _spender, uint256 _value) public returns (bool); + + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +// File: openzeppelin-solidity/contracts/token/ERC20/StandardToken.sol + +pragma solidity ^0.4.24; + +/** + * @title Standard ERC20 token + * + * @dev Implementation of the basic standard token. + * https://github.com/ethereum/EIPs/issues/20 + * Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol + */ +contract StandardToken is ERC20, BasicToken { + mapping(address => mapping(address => uint256)) internal allowed; + + /** + * @dev Transfer tokens from one address to another + * @param _from address The address which you want to send tokens from + * @param _to address The address which you want to transfer to + * @param _value uint256 the amount of tokens to be transferred + */ + function transferFrom( + address _from, + address _to, + uint256 _value + ) public returns (bool) { + require(_value <= balances[_from]); + require(_value <= allowed[_from][msg.sender]); + require(_to != address(0)); + + balances[_from] = balances[_from].sub(_value); + balances[_to] = balances[_to].add(_value); + allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); + emit Transfer(_from, _to, _value); + return true; + } + + /** + * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. + * Beware that changing an allowance with this method brings the risk that someone may use both the old + * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this + * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * @param _spender The address which will spend the funds. + * @param _value The amount of tokens to be spent. + */ + function approve(address _spender, uint256 _value) public returns (bool) { + allowed[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); + return true; + } + + /** + * @dev Function to check the amount of tokens that an owner allowed to a spender. + * @param _owner address The address which owns the funds. + * @param _spender address The address which will spend the funds. + * @return A uint256 specifying the amount of tokens still available for the spender. + */ + function allowance(address _owner, address _spender) public view returns (uint256) { + return allowed[_owner][_spender]; + } + + /** + * @dev Increase the amount of tokens that an owner allowed to a spender. + * approve should be called when allowed[_spender] == 0. To increment + * allowed value is better to use this function to avoid 2 calls (and wait until + * the first transaction is mined) + * From MonolithDAO Token.sol + * @param _spender The address which will spend the funds. + * @param _addedValue The amount of tokens to increase the allowance by. + */ + function increaseApproval(address _spender, uint256 _addedValue) public returns (bool) { + allowed[msg.sender][_spender] = (allowed[msg.sender][_spender].add(_addedValue)); + emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); + return true; + } + + /** + * @dev Decrease the amount of tokens that an owner allowed to a spender. + * approve should be called when allowed[_spender] == 0. To decrement + * allowed value is better to use this function to avoid 2 calls (and wait until + * the first transaction is mined) + * From MonolithDAO Token.sol + * @param _spender The address which will spend the funds. + * @param _subtractedValue The amount of tokens to decrease the allowance by. + */ + function decreaseApproval(address _spender, uint256 _subtractedValue) public returns (bool) { + uint256 oldValue = allowed[msg.sender][_spender]; + if (_subtractedValue >= oldValue) { + allowed[msg.sender][_spender] = 0; + } else { + allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue); + } + emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); + return true; + } +} + +// File: openzeppelin-solidity/contracts/ownership/Ownable.sol + +pragma solidity ^0.4.24; + +/** + * @title Ownable + * @dev The Ownable contract has an owner address, and provides basic authorization control + * functions, this simplifies the implementation of "user permissions". + */ +contract Ownable { + address public owner; + + event OwnershipRenounced(address indexed previousOwner); + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev The Ownable constructor sets the original `owner` of the contract to the sender + * account. + */ + constructor() public { + owner = msg.sender; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(msg.sender == owner); + _; + } + + /** + * @dev Allows the current owner to relinquish control of the contract. + * @notice Renouncing to ownership will leave the contract without an owner. + * It will not be possible to call the functions with the `onlyOwner` + * modifier anymore. + */ + function renounceOwnership() public onlyOwner { + emit OwnershipRenounced(owner); + owner = address(0); + } + + /** + * @dev Allows the current owner to transfer control of the contract to a newOwner. + * @param _newOwner The address to transfer ownership to. + */ + function transferOwnership(address _newOwner) public onlyOwner { + _transferOwnership(_newOwner); + } + + /** + * @dev Transfers control of the contract to a newOwner. + * @param _newOwner The address to transfer ownership to. + */ + function _transferOwnership(address _newOwner) internal { + require(_newOwner != address(0)); + emit OwnershipTransferred(owner, _newOwner); + owner = _newOwner; + } +} + +// File: openzeppelin-solidity/contracts/token/ERC20/MintableToken.sol + +pragma solidity ^0.4.24; + +/** + * @title Mintable token + * @dev Simple ERC20 Token example, with mintable token creation + * Based on code by TokenMarketNet: https://github.com/TokenMarketNet/ico/blob/master/contracts/MintableToken.sol + */ +contract MintableToken is StandardToken, Ownable { + event Mint(address indexed to, uint256 amount); + event MintFinished(); + + bool public mintingFinished = false; + + modifier canMint() { + require(!mintingFinished); + _; + } + + modifier hasMintPermission() { + require(msg.sender == owner); + _; + } + + /** + * @dev Function to mint tokens + * @param _to The address that will receive the minted tokens. + * @param _amount The amount of tokens to mint. + * @return A boolean that indicates if the operation was successful. + */ + function mint(address _to, uint256 _amount) public hasMintPermission canMint returns (bool) { + totalSupply_ = totalSupply_.add(_amount); + balances[_to] = balances[_to].add(_amount); + emit Mint(_to, _amount); + emit Transfer(address(0), _to, _amount); + return true; + } + + /** + * @dev Function to stop minting new tokens. + * @return True if the operation was successful. + */ + function finishMinting() public onlyOwner canMint returns (bool) { + mintingFinished = true; + emit MintFinished(); + return true; + } +} + +// File: openzeppelin-solidity/contracts/token/ERC20/DetailedERC20.sol + +pragma solidity ^0.4.24; + +/** + * @title DetailedERC20 token + * @dev The decimals are only for visualization purposes. + * All the operations are done using the smallest and indivisible token unit, + * just as on Ethereum all the operations are done in wei. + */ +contract DetailedERC20 is ERC20 { + string public name; + string public symbol; + uint8 public decimals; + + constructor( + string _name, + string _symbol, + uint8 _decimals + ) public { + name = _name; + symbol = _symbol; + decimals = _decimals; + } +} + +// File: openzeppelin-solidity/contracts/AddressUtils.sol + +pragma solidity ^0.4.24; + +/** + * Utility library of inline functions on addresses + */ +library AddressUtils { + /** + * Returns whether the target address is a contract + * @dev This function will return false if invoked during the constructor of a contract, + * as the code is not actually created until after the constructor finishes. + * @param _addr address to check + * @return whether the target address is a contract + */ + function isContract(address _addr) internal view returns (bool) { + uint256 size; + // XXX Currently there is no better way to check if there is a contract in an address + // than to check the size of the code at that address. + // See https://ethereum.stackexchange.com/a/14016/36603 + // for more details about how this works. + // TODO Check this again before the Serenity release, because all addresses will be + // contracts then. + // solium-disable-next-line security/no-inline-assembly + assembly { + size := extcodesize(_addr) + } + return size > 0; + } +} + +// File: contracts/interfaces/ERC677.sol + +pragma solidity 0.4.24; + +contract ERC677 is ERC20 { + event Transfer(address indexed from, address indexed to, uint256 value, bytes data); + + function transferAndCall( + address, + uint256, + bytes + ) external returns (bool); + + function increaseAllowance(address spender, uint256 addedValue) public returns (bool); + + function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool); +} + +// File: contracts/interfaces/IBurnableMintableERC677Token.sol + +pragma solidity 0.4.24; + +contract IBurnableMintableERC677Token is ERC677 { + function mint(address _to, uint256 _amount) public returns (bool); + + function burn(uint256 _value) public; + + function claimTokens(address _token, address _to) public; +} + +// File: contracts/upgradeable_contracts/Sacrifice.sol + +pragma solidity 0.4.24; + +contract Sacrifice { + constructor(address _recipient) public payable { + selfdestruct(_recipient); + } +} + +// File: contracts/libraries/Address.sol + +pragma solidity 0.4.24; + +/** + * @title Address + * @dev Helper methods for Address type. + */ +library Address { + /** + * @dev Try to send native tokens to the address. If it fails, it will force the transfer by creating a selfdestruct contract + * @param _receiver address that will receive the native tokens + * @param _value the amount of native tokens to send + */ + function safeSendValue(address _receiver, uint256 _value) internal { + if (!_receiver.send(_value)) { + (new Sacrifice).value(_value)(_receiver); + } + } +} + +// File: contracts/upgradeable_contracts/Claimable.sol + +pragma solidity 0.4.24; + +contract Claimable { + bytes4 internal constant TRANSFER = 0xa9059cbb; // transfer(address,uint256) + + modifier validAddress(address _to) { + require(_to != address(0)); + /* solcov ignore next */ + _; + } + + function claimValues(address _token, address _to) internal { + if (_token == address(0)) { + claimNativeCoins(_to); + } else { + claimErc20Tokens(_token, _to); + } + } + + function claimNativeCoins(address _to) internal { + uint256 value = address(this).balance; + Address.safeSendValue(_to, value); + } + + function claimErc20Tokens(address _token, address _to) internal { + ERC20Basic token = ERC20Basic(_token); + uint256 balance = token.balanceOf(this); + safeTransfer(_token, _to, balance); + } + + function safeTransfer( + address _token, + address _to, + uint256 _value + ) internal { + bytes memory returnData; + bool returnDataResult; + bytes memory callData = abi.encodeWithSelector(TRANSFER, _to, _value); + assembly { + let result := call(gas, _token, 0x0, add(callData, 0x20), mload(callData), 0, 32) + returnData := mload(0) + returnDataResult := mload(0) + + switch result + case 0 { + revert(0, 0) + } + } + + // Return data is optional + if (returnData.length > 0) { + require(returnDataResult); + } + } +} + +// File: contracts/ERC677BridgeToken.sol + +pragma solidity 0.4.24; + +/** + * @title ERC677BridgeToken + * @dev The basic implementation of a bridgeable ERC677-compatible token + */ +contract ERC677BridgeToken is IBurnableMintableERC677Token, DetailedERC20, BurnableToken, MintableToken, Claimable { + bytes4 internal constant ON_TOKEN_TRANSFER = 0xa4c0ed36; // onTokenTransfer(address,uint256,bytes) + + address internal bridgeContractAddr; + + event ContractFallbackCallFailed(address from, address to, uint256 value); + + constructor( + string _name, + string _symbol, + uint8 _decimals + ) public DetailedERC20(_name, _symbol, _decimals) { + // solhint-disable-previous-line no-empty-blocks + } + + function bridgeContract() external view returns (address) { + return bridgeContractAddr; + } + + function setBridgeContract(address _bridgeContract) external onlyOwner { + require(AddressUtils.isContract(_bridgeContract)); + bridgeContractAddr = _bridgeContract; + } + + modifier validRecipient(address _recipient) { + require(_recipient != address(0) && _recipient != address(this)); + /* solcov ignore next */ + _; + } + + function transferAndCall( + address _to, + uint256 _value, + bytes _data + ) external validRecipient(_to) returns (bool) { + require(superTransfer(_to, _value)); + emit Transfer(msg.sender, _to, _value, _data); + + if (AddressUtils.isContract(_to)) { + require(contractFallback(msg.sender, _to, _value, _data)); + } + return true; + } + + function getTokenInterfacesVersion() + external + pure + returns ( + uint64 major, + uint64 minor, + uint64 patch + ) + { + return (2, 2, 0); + } + + function superTransfer(address _to, uint256 _value) internal returns (bool) { + return super.transfer(_to, _value); + } + + function transfer(address _to, uint256 _value) public returns (bool) { + require(superTransfer(_to, _value)); + callAfterTransfer(msg.sender, _to, _value); + return true; + } + + function transferFrom( + address _from, + address _to, + uint256 _value + ) public returns (bool) { + require(super.transferFrom(_from, _to, _value)); + callAfterTransfer(_from, _to, _value); + return true; + } + + function callAfterTransfer( + address _from, + address _to, + uint256 _value + ) internal { + if (AddressUtils.isContract(_to) && !contractFallback(_from, _to, _value, new bytes(0))) { + require(!isBridge(_to)); + emit ContractFallbackCallFailed(_from, _to, _value); + } + } + + function isBridge(address _address) public view returns (bool) { + return _address == bridgeContractAddr; + } + + /** + * @dev call onTokenTransfer fallback on the token recipient contract + * @param _from tokens sender + * @param _to tokens recipient + * @param _value amount of tokens that was sent + * @param _data set of extra bytes that can be passed to the recipient + */ + function contractFallback( + address _from, + address _to, + uint256 _value, + bytes _data + ) private returns (bool) { + return _to.call(abi.encodeWithSelector(ON_TOKEN_TRANSFER, _from, _value, _data)); + } + + function finishMinting() public returns (bool) { + revert(); + } + + function renounceOwnership() public onlyOwner { + revert(); + } + + function claimTokens(address _token, address _to) public onlyOwner validAddress(_to) { + claimValues(_token, _to); + } + + function increaseAllowance(address spender, uint256 addedValue) public returns (bool) { + return super.increaseApproval(spender, addedValue); + } + + function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) { + return super.decreaseApproval(spender, subtractedValue); + } +} + +// File: contracts/PermittableToken.sol + +pragma solidity 0.4.24; + +contract PermittableToken is ERC677BridgeToken { + string public constant version = "1"; + + // EIP712 niceties + bytes32 public DOMAIN_SEPARATOR; + // bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)"); + bytes32 public constant PERMIT_TYPEHASH = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb; + + mapping(address => uint256) public nonces; + mapping(address => mapping(address => uint256)) public expirations; + + constructor( + string memory _name, + string memory _symbol, + uint8 _decimals, + uint256 _chainId + ) public ERC677BridgeToken(_name, _symbol, _decimals) { + require(_chainId != 0); + DOMAIN_SEPARATOR = keccak256( + abi.encode( + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256(bytes(_name)), + keccak256(bytes(version)), + _chainId, + address(this) + ) + ); + } + + /// @dev transferFrom in this contract works in a slightly different form than the generic + /// transferFrom function. This contract allows for "unlimited approval". + /// Should the user approve an address for the maximum uint256 value, + /// then that address will have unlimited approval until told otherwise. + /// @param _sender The address of the sender. + /// @param _recipient The address of the recipient. + /// @param _amount The value to transfer. + /// @return Success status. + function transferFrom( + address _sender, + address _recipient, + uint256 _amount + ) public returns (bool) { + require(_sender != address(0)); + require(_recipient != address(0)); + + balances[_sender] = balances[_sender].sub(_amount); + balances[_recipient] = balances[_recipient].add(_amount); + emit Transfer(_sender, _recipient, _amount); + + if (_sender != msg.sender) { + uint256 allowedAmount = allowance(_sender, msg.sender); + + if (allowedAmount != uint256(-1)) { + // If allowance is limited, adjust it. + // In this case `transferFrom` works like the generic + allowed[_sender][msg.sender] = allowedAmount.sub(_amount); + emit Approval(_sender, msg.sender, allowed[_sender][msg.sender]); + } else { + // If allowance is unlimited by `permit`, `approve`, or `increaseAllowance` + // function, don't adjust it. But the expiration date must be empty or in the future + require(expirations[_sender][msg.sender] == 0 || expirations[_sender][msg.sender] >= _now()); + } + } else { + // If `_sender` is `msg.sender`, + // the function works just like `transfer()` + } + + callAfterTransfer(_sender, _recipient, _amount); + return true; + } + + /// @dev An alias for `transfer` function. + /// @param _to The address of the recipient. + /// @param _amount The value to transfer. + function push(address _to, uint256 _amount) public { + transferFrom(msg.sender, _to, _amount); + } + + /// @dev Makes a request to transfer the specified amount + /// from the specified address to the caller's address. + /// @param _from The address of the holder. + /// @param _amount The value to transfer. + function pull(address _from, uint256 _amount) public { + transferFrom(_from, msg.sender, _amount); + } + + /// @dev An alias for `transferFrom` function. + /// @param _from The address of the sender. + /// @param _to The address of the recipient. + /// @param _amount The value to transfer. + function move( + address _from, + address _to, + uint256 _amount + ) public { + transferFrom(_from, _to, _amount); + } + + /// @dev Allows to spend holder's unlimited amount by the specified spender. + /// The function can be called by anyone, but requires having allowance parameters + /// signed by the holder according to EIP712. + /// @param _holder The holder's address. + /// @param _spender The spender's address. + /// @param _nonce The nonce taken from `nonces(_holder)` public getter. + /// @param _expiry The allowance expiration date (unix timestamp in UTC). + /// Can be zero for no expiration. Forced to zero if `_allowed` is `false`. + /// @param _allowed True to enable unlimited allowance for the spender by the holder. False to disable. + /// @param _v A final byte of signature (ECDSA component). + /// @param _r The first 32 bytes of signature (ECDSA component). + /// @param _s The second 32 bytes of signature (ECDSA component). + function permit( + address _holder, + address _spender, + uint256 _nonce, + uint256 _expiry, + bool _allowed, + uint8 _v, + bytes32 _r, + bytes32 _s + ) external { + require(_holder != address(0)); + require(_spender != address(0)); + require(_expiry == 0 || _now() <= _expiry); + + bytes32 digest = keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + keccak256(abi.encode(PERMIT_TYPEHASH, _holder, _spender, _nonce, _expiry, _allowed)) + ) + ); + + require(_holder == ecrecover(digest, _v, _r, _s)); + require(_nonce == nonces[_holder]++); + + uint256 amount = _allowed ? uint256(-1) : 0; + + allowed[_holder][_spender] = amount; + expirations[_holder][_spender] = _allowed ? _expiry : 0; + + emit Approval(_holder, _spender, amount); + } + + function _now() internal view returns (uint256) { + return now; + } + + /// @dev Version of the token contract. + function getTokenInterfacesVersion() + external + pure + returns ( + uint64 major, + uint64 minor, + uint64 patch + ) + { + return (2, 3, 0); + } +} diff --git a/contracts/Mocks/MockAMB.sol b/contracts/Mocks/MockAMB.sol new file mode 100644 index 0000000..6795f32 --- /dev/null +++ b/contracts/Mocks/MockAMB.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +import { IAMB } from "../CrossChainUpgradeableProxy.sol"; + +contract MockAMB is IAMB { + address public xDomainMessageSender; + + constructor(address _xDomainMessageSender) { + xDomainMessageSender = _xDomainMessageSender; + } + + function setMessageSender(address _sender) external { + xDomainMessageSender = _sender; + } + + function messageSender() external view override returns (address) { + return xDomainMessageSender; + } +} diff --git a/contracts/Mocks/MockOVM_CrossDomainMessenger.sol b/contracts/Mocks/MockOVM_CrossDomainMessenger.sol deleted file mode 100644 index f8ec03c..0000000 --- a/contracts/Mocks/MockOVM_CrossDomainMessenger.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.7.0; - -contract MockOVM_CrossDomainMessenger { - address public xDomainMessageSender; - - constructor(address _xDomainMessageSender) { - xDomainMessageSender = _xDomainMessageSender; - } - - function execute(address _who, bytes calldata _calldata) external returns (bool success, bytes memory result) { - (success, result) = _who.call(_calldata); - } -} diff --git a/contracts/Mocks/MockOmniBridge.sol b/contracts/Mocks/MockOmniBridge.sol new file mode 100644 index 0000000..1ff04b0 --- /dev/null +++ b/contracts/Mocks/MockOmniBridge.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +import { IAMB, IOmniBridge } from "../CrossChainUpgradeableProxy.sol"; + +contract MockOmniBridge is IOmniBridge { + IAMB public AMB; + + constructor(IAMB _AMB) { + AMB = _AMB; + } + + function bridgeContract() external view override returns (IAMB) { + return AMB; + } + + function execute(address _who, bytes calldata _calldata) external returns (bool success, bytes memory result) { + (success, result) = _who.call(_calldata); + } +} diff --git a/contracts/TornadoPool.sol b/contracts/TornadoPool.sol index fe31b5f..20aaf25 100644 --- a/contracts/TornadoPool.sol +++ b/contracts/TornadoPool.sol @@ -12,21 +12,27 @@ pragma solidity ^0.7.0; pragma experimental ABIEncoderV2; + +import "@openzeppelin/contracts/contracts/token/ERC20/IERC20.sol"; import "./MerkleTreeWithHistory.sol"; +interface IERC6777 is IERC20 { + function transferAndCall( + address, + uint256, + bytes calldata + ) external returns (bool); +} + interface IVerifier { function verifyProof(bytes memory _proof, uint256[7] memory _input) external view returns (bool); function verifyProof(bytes memory _proof, uint256[21] memory _input) external view returns (bool); } -interface IERC20 { - function transfer(address to, uint256 value) external returns (bool); -} - interface IERC20Receiver { function onTokenBridged( - IERC20 token, + IERC6777 token, uint256 value, bytes calldata data ) external; @@ -39,15 +45,17 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver { mapping(bytes32 => bool) public nullifierHashes; IVerifier public immutable verifier2; IVerifier public immutable verifier16; - IERC20 public immutable token; + IERC6777 public immutable token; + address public immutable omniBridge; struct ExtData { - address payable recipient; + address recipient; int256 extAmount; - address payable relayer; + address relayer; uint256 fee; bytes encryptedOutput1; bytes encryptedOutput2; + bool isL1Withdraw; } struct Proof { @@ -79,14 +87,25 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver { IVerifier _verifier16, uint32 _levels, address _hasher, - IERC20 _token + IERC6777 _token, + address _omniBridge ) MerkleTreeWithHistory(_levels, _hasher) { verifier2 = _verifier2; verifier16 = _verifier16; token = _token; + omniBridge = _omniBridge; } - function transaction(Proof memory _args, ExtData memory _extData) public payable { + function transact(Proof memory _args, ExtData memory _extData) public { + if (_extData.extAmount > 0) { + // for deposits from L2 + token.transferFrom(msg.sender, address(this), uint256(_extData.extAmount)); + } + + _transact(_args, _extData); + } + + function _transact(Proof memory _args, ExtData memory _extData) internal { require(isKnownRoot(_args.root), "Invalid merkle root"); for (uint256 i = 0; i < _args.inputNullifiers.length; i++) { require(!isSpent(_args.inputNullifiers[i]), "Input is already spent"); @@ -99,14 +118,13 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver { nullifierHashes[_args.inputNullifiers[i]] = true; } - if (_extData.extAmount > 0) { - require(msg.value == uint256(_extData.extAmount), "Incorrect amount of ETH sent on deposit"); - } else if (_extData.extAmount < 0) { - require(msg.value == 0, "Sent ETH amount should be 0 for withdrawal"); + if (_extData.extAmount < 0) { require(_extData.recipient != address(0), "Can't withdraw to zero address"); - token.transfer(_extData.recipient, uint256(-_extData.extAmount)); - } else { - require(msg.value == 0, "Sent ETH amount should be 0 for transaction"); + if (_extData.isL1Withdraw) { + token.transferAndCall(omniBridge, uint256(-_extData.extAmount), abi.encode(_extData.recipient)); + } else { + token.transfer(_extData.recipient, uint256(-_extData.extAmount)); + } } if (_extData.fee > 0) { @@ -190,22 +208,24 @@ contract TornadoPool is MerkleTreeWithHistory, IERC20Receiver { Register memory _registerArgs, Proof memory _proofArgs, ExtData memory _extData - ) public payable { + ) public { register(_registerArgs); - transaction(_proofArgs, _extData); + transact(_proofArgs, _extData); } + /// TOTHINK security. should we track all incoming trasfers so we can to double check the bridge actually sent tokens to this contract? function onTokenBridged( - IERC20 _token, + IERC6777 _token, uint256, bytes calldata _data ) external override { require(_token == token, "provided token is not supported"); + require(msg.sender == omniBridge, "only omni bridge"); // we can also get real msg.sender from L1, but it does not matter + (Register memory _registerArgs, Proof memory _args, ExtData memory _extData) = abi.decode(_data, (Register, Proof, ExtData)); if (_registerArgs.pubKey.length != 0 && _registerArgs.account.length != 0) { - registerAndTransact(_registerArgs, _args, _extData); - } else { - transaction(_args, _extData); + register(_registerArgs); } + _transact(_args, _extData); } } diff --git a/contracts/bridge.sol.tmp b/contracts/bridge.sol.tmp new file mode 100644 index 0000000..bcb9d37 --- /dev/null +++ b/contracts/bridge.sol.tmp @@ -0,0 +1,3615 @@ +// File: @openzeppelin/contracts/math/SafeMath.sol + +pragma solidity ^0.7.0; + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + require(b > 0, errorMessage); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, "SafeMath: modulo by zero"); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts with custom message when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} + +// File: contracts/upgradeability/EternalStorage.sol + +pragma solidity 0.7.5; + +/** + * @title EternalStorage + * @dev This contract holds all the necessary state variables to carry out the storage of any contract. + */ +contract EternalStorage { + mapping(bytes32 => uint256) internal uintStorage; + mapping(bytes32 => string) internal stringStorage; + mapping(bytes32 => address) internal addressStorage; + mapping(bytes32 => bytes) internal bytesStorage; + mapping(bytes32 => bool) internal boolStorage; + mapping(bytes32 => int256) internal intStorage; +} + +// File: contracts/upgradeable_contracts/Initializable.sol + +pragma solidity 0.7.5; + +contract Initializable is EternalStorage { + bytes32 internal constant INITIALIZED = 0x0a6f646cd611241d8073675e00d1a1ff700fbf1b53fcf473de56d1e6e4b714ba; // keccak256(abi.encodePacked("isInitialized")) + + function setInitialize() internal { + boolStorage[INITIALIZED] = true; + } + + function isInitialized() public view returns (bool) { + return boolStorage[INITIALIZED]; + } +} + +// File: contracts/interfaces/IUpgradeabilityOwnerStorage.sol + +pragma solidity 0.7.5; + +interface IUpgradeabilityOwnerStorage { + function upgradeabilityOwner() external view returns (address); +} + +// File: contracts/upgradeable_contracts/Upgradeable.sol + +pragma solidity 0.7.5; + +contract Upgradeable { + // Avoid using onlyUpgradeabilityOwner name to prevent issues with implementation from proxy contract + modifier onlyIfUpgradeabilityOwner() { + require(msg.sender == IUpgradeabilityOwnerStorage(address(this)).upgradeabilityOwner()); + _; + } +} + +// File: @openzeppelin/contracts/token/ERC20/IERC20.sol + +pragma solidity ^0.7.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +// File: @openzeppelin/contracts/utils/Address.sol + +pragma solidity ^0.7.0; + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // According to EIP-1052, 0x0 is the value returned for not-yet created accounts + // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned + // for accounts without code, i.e. `keccak256('')` + bytes32 codehash; + bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + // solhint-disable-next-line no-inline-assembly + assembly { + codehash := extcodehash(account) + } + return (codehash != accountHash && codehash != 0x0); + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-low-level-calls, avoid-call-value + (bool success, ) = recipient.call{ value: amount }(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain`call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return _functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + return _functionCallWithValue(target, data, value, errorMessage); + } + + function _functionCallWithValue( + address target, + bytes memory data, + uint256 weiValue, + string memory errorMessage + ) private returns (bytes memory) { + require(isContract(target), "Address: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.call{ value: weiValue }(data); + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + + // solhint-disable-next-line no-inline-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +// File: @openzeppelin/contracts/token/ERC20/SafeERC20.sol + +pragma solidity ^0.7.0; + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + using SafeMath for uint256; + using Address for address; + + function safeTransfer( + IERC20 token, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom( + IERC20 token, + address from, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + /** + * @dev Deprecated. This function has issues similar to the ones found in + * {IERC20-approve}, and its usage is discouraged. + * + * Whenever possible, use {safeIncreaseAllowance} and + * {safeDecreaseAllowance} instead. + */ + function safeApprove( + IERC20 token, + address spender, + uint256 value + ) internal { + // safeApprove should only be called when setting an initial allowance, + // or when resetting it to zero. To increase and decrease it, use + // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' + // solhint-disable-next-line max-line-length + require( + (value == 0) || (token.allowance(address(this), spender) == 0), + "SafeERC20: approve from non-zero to non-zero allowance" + ); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function safeIncreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { + uint256 newAllowance = token.allowance(address(this), spender).add(value); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + function safeDecreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { + uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); + if (returndata.length > 0) { + // Return data is optional + // solhint-disable-next-line max-line-length + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} + +// File: contracts/upgradeable_contracts/Sacrifice.sol + +pragma solidity 0.7.5; + +contract Sacrifice { + constructor(address payable _recipient) payable { + selfdestruct(_recipient); + } +} + +// File: contracts/libraries/AddressHelper.sol + +pragma solidity 0.7.5; + +/** + * @title AddressHelper + * @dev Helper methods for Address type. + */ +library AddressHelper { + /** + * @dev Try to send native tokens to the address. If it fails, it will force the transfer by creating a selfdestruct contract + * @param _receiver address that will receive the native tokens + * @param _value the amount of native tokens to send + */ + function safeSendValue(address payable _receiver, uint256 _value) internal { + if (!(_receiver).send(_value)) { + new Sacrifice{ value: _value }(_receiver); + } + } +} + +// File: contracts/upgradeable_contracts/Claimable.sol + +pragma solidity 0.7.5; + +/** + * @title Claimable + * @dev Implementation of the claiming utils that can be useful for withdrawing accidentally sent tokens that are not used in bridge operations. + */ +contract Claimable { + using SafeERC20 for IERC20; + + /** + * Throws if a given address is equal to address(0) + */ + modifier validAddress(address _to) { + require(_to != address(0)); + _; + } + + /** + * @dev Withdraws the erc20 tokens or native coins from this contract. + * Caller should additionally check that the claimed token is not a part of bridge operations (i.e. that token != erc20token()). + * @param _token address of the claimed token or address(0) for native coins. + * @param _to address of the tokens/coins receiver. + */ + function claimValues(address _token, address _to) internal validAddress(_to) { + if (_token == address(0)) { + claimNativeCoins(_to); + } else { + claimErc20Tokens(_token, _to); + } + } + + /** + * @dev Internal function for withdrawing all native coins from the contract. + * @param _to address of the coins receiver. + */ + function claimNativeCoins(address _to) internal { + uint256 value = address(this).balance; + AddressHelper.safeSendValue(payable(_to), value); + } + + /** + * @dev Internal function for withdrawing all tokens of some particular ERC20 contract from this contract. + * @param _token address of the claimed ERC20 token. + * @param _to address of the tokens receiver. + */ + function claimErc20Tokens(address _token, address _to) internal { + IERC20 token = IERC20(_token); + uint256 balance = token.balanceOf(address(this)); + token.safeTransfer(_to, balance); + } +} + +// File: contracts/upgradeable_contracts/components/bridged/BridgedTokensRegistry.sol + +pragma solidity 0.7.5; + +/** + * @title BridgedTokensRegistry + * @dev Functionality for keeping track of registered bridged token pairs. + */ +contract BridgedTokensRegistry is EternalStorage { + event NewTokenRegistered(address indexed nativeToken, address indexed bridgedToken); + + /** + * @dev Retrieves address of the bridged token contract associated with a specific native token contract on the other side. + * @param _nativeToken address of the native token contract on the other side. + * @return address of the deployed bridged token contract. + */ + function bridgedTokenAddress(address _nativeToken) public view returns (address) { + return addressStorage[keccak256(abi.encodePacked("homeTokenAddress", _nativeToken))]; + } + + /** + * @dev Retrieves address of the native token contract associated with a specific bridged token contract. + * @param _bridgedToken address of the created bridged token contract on this side. + * @return address of the native token contract on the other side of the bridge. + */ + function nativeTokenAddress(address _bridgedToken) public view returns (address) { + return addressStorage[keccak256(abi.encodePacked("foreignTokenAddress", _bridgedToken))]; + } + + /** + * @dev Internal function for updating a pair of addresses for the bridged token. + * @param _nativeToken address of the native token contract on the other side. + * @param _bridgedToken address of the created bridged token contract on this side. + */ + function _setTokenAddressPair(address _nativeToken, address _bridgedToken) internal { + addressStorage[keccak256(abi.encodePacked("homeTokenAddress", _nativeToken))] = _bridgedToken; + addressStorage[keccak256(abi.encodePacked("foreignTokenAddress", _bridgedToken))] = _nativeToken; + + emit NewTokenRegistered(_nativeToken, _bridgedToken); + } +} + +// File: contracts/upgradeable_contracts/components/native/NativeTokensRegistry.sol + +pragma solidity 0.7.5; + +/** + * @title NativeTokensRegistry + * @dev Functionality for keeping track of registered native tokens. + */ +contract NativeTokensRegistry is EternalStorage { + /** + * @dev Checks if for a given native token, the deployment of its bridged alternative was already acknowledged. + * @param _token address of native token contract. + * @return true, if bridged token was already deployed. + */ + function isBridgedTokenDeployAcknowledged(address _token) public view returns (bool) { + return boolStorage[keccak256(abi.encodePacked("ackDeploy", _token))]; + } + + /** + * @dev Acknowledges the deployment of bridged token contract on the other side. + * @param _token address of native token contract. + */ + function _ackBridgedTokenDeploy(address _token) internal { + if (!boolStorage[keccak256(abi.encodePacked("ackDeploy", _token))]) { + boolStorage[keccak256(abi.encodePacked("ackDeploy", _token))] = true; + } + } +} + +// File: contracts/upgradeable_contracts/components/native/MediatorBalanceStorage.sol + +pragma solidity 0.7.5; + +/** + * @title MediatorBalanceStorage + * @dev Functionality for storing expected mediator balance for native tokens. + */ +contract MediatorBalanceStorage is EternalStorage { + /** + * @dev Tells the expected token balance of the contract. + * @param _token address of token contract. + * @return the current tracked token balance of the contract. + */ + function mediatorBalance(address _token) public view returns (uint256) { + return uintStorage[keccak256(abi.encodePacked("mediatorBalance", _token))]; + } + + /** + * @dev Updates expected token balance of the contract. + * @param _token address of token contract. + * @param _balance the new token balance of the contract. + */ + function _setMediatorBalance(address _token, uint256 _balance) internal { + uintStorage[keccak256(abi.encodePacked("mediatorBalance", _token))] = _balance; + } +} + +// File: contracts/interfaces/IERC677.sol + +pragma solidity 0.7.5; + +interface IERC677 is IERC20 { + event Transfer(address indexed from, address indexed to, uint256 value, bytes data); + + function transferAndCall( + address to, + uint256 value, + bytes calldata data + ) external returns (bool); + + function increaseAllowance(address spender, uint256 addedValue) external returns (bool); + + function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); +} + +// File: contracts/libraries/Bytes.sol + +pragma solidity 0.7.5; + +/** + * @title Bytes + * @dev Helper methods to transform bytes to other solidity types. + */ +library Bytes { + /** + * @dev Truncate bytes array if its size is more than 20 bytes. + * NOTE: This function does not perform any checks on the received parameter. + * Make sure that the _bytes argument has a correct length, not less than 20 bytes. + * A case when _bytes has length less than 20 will lead to the undefined behaviour, + * since assembly will read data from memory that is not related to the _bytes argument. + * @param _bytes to be converted to address type + * @return addr address included in the firsts 20 bytes of the bytes array in parameter. + */ + function bytesToAddress(bytes memory _bytes) internal pure returns (address addr) { + assembly { + addr := mload(add(_bytes, 20)) + } + } +} + +// File: contracts/upgradeable_contracts/ReentrancyGuard.sol + +pragma solidity 0.7.5; + +contract ReentrancyGuard { + function lock() internal view returns (bool res) { + assembly { + // Even though this is not the same as boolStorage[keccak256(abi.encodePacked("lock"))], + // since solidity mapping introduces another level of addressing, such slot change is safe + // for temporary variables which are cleared at the end of the call execution. + res := sload(0x6168652c307c1e813ca11cfb3a601f1cf3b22452021a5052d8b05f1f1f8a3e92) // keccak256(abi.encodePacked("lock")) + } + } + + function setLock(bool _lock) internal { + assembly { + // Even though this is not the same as boolStorage[keccak256(abi.encodePacked("lock"))], + // since solidity mapping introduces another level of addressing, such slot change is safe + // for temporary variables which are cleared at the end of the call execution. + sstore(0x6168652c307c1e813ca11cfb3a601f1cf3b22452021a5052d8b05f1f1f8a3e92, _lock) // keccak256(abi.encodePacked("lock")) + } + } +} + +// File: contracts/upgradeable_contracts/Ownable.sol + +pragma solidity 0.7.5; + +/** + * @title Ownable + * @dev This contract has an owner address providing basic authorization control + */ +contract Ownable is EternalStorage { + bytes4 internal constant UPGRADEABILITY_OWNER = 0x6fde8202; // upgradeabilityOwner() + + /** + * @dev Event to show ownership has been transferred + * @param previousOwner representing the address of the previous owner + * @param newOwner representing the address of the new owner + */ + event OwnershipTransferred(address previousOwner, address newOwner); + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + _onlyOwner(); + _; + } + + /** + * @dev Internal function for reducing onlyOwner modifier bytecode overhead. + */ + function _onlyOwner() internal view { + require(msg.sender == owner()); + } + + /** + * @dev Throws if called through proxy by any account other than contract itself or an upgradeability owner. + */ + modifier onlyRelevantSender() { + (bool isProxy, bytes memory returnData) = address(this).staticcall(abi.encodeWithSelector(UPGRADEABILITY_OWNER)); + require( + !isProxy || // covers usage without calling through storage proxy + (returnData.length == 32 && msg.sender == abi.decode(returnData, (address))) || // covers usage through regular proxy calls + msg.sender == address(this) // covers calls through upgradeAndCall proxy method + ); + _; + } + + bytes32 internal constant OWNER = 0x02016836a56b71f0d02689e69e326f4f4c1b9057164ef592671cf0d37c8040c0; // keccak256(abi.encodePacked("owner")) + + /** + * @dev Tells the address of the owner + * @return the address of the owner + */ + function owner() public view returns (address) { + return addressStorage[OWNER]; + } + + /** + * @dev Allows the current owner to transfer control of the contract to a newOwner. + * @param newOwner the address to transfer ownership to. + */ + function transferOwnership(address newOwner) external onlyOwner { + _setOwner(newOwner); + } + + /** + * @dev Sets a new owner address + */ + function _setOwner(address newOwner) internal { + require(newOwner != address(0)); + emit OwnershipTransferred(owner(), newOwner); + addressStorage[OWNER] = newOwner; + } +} + +// File: contracts/interfaces/IAMB.sol + +pragma solidity 0.7.5; + +interface IAMB { + event UserRequestForAffirmation(bytes32 indexed messageId, bytes encodedData); + event UserRequestForSignature(bytes32 indexed messageId, bytes encodedData); + event AffirmationCompleted(address indexed sender, address indexed executor, bytes32 indexed messageId, bool status); + event RelayedMessage(address indexed sender, address indexed executor, bytes32 indexed messageId, bool status); + + function messageSender() external view returns (address); + + function maxGasPerTx() external view returns (uint256); + + function transactionHash() external view returns (bytes32); + + function messageId() external view returns (bytes32); + + function messageSourceChainId() external view returns (bytes32); + + function messageCallStatus(bytes32 _messageId) external view returns (bool); + + function failedMessageDataHash(bytes32 _messageId) external view returns (bytes32); + + function failedMessageReceiver(bytes32 _messageId) external view returns (address); + + function failedMessageSender(bytes32 _messageId) external view returns (address); + + function requireToPassMessage( + address _contract, + bytes calldata _data, + uint256 _gas + ) external returns (bytes32); + + function requireToConfirmMessage( + address _contract, + bytes calldata _data, + uint256 _gas + ) external returns (bytes32); + + function sourceChainId() external view returns (uint256); + + function destinationChainId() external view returns (uint256); +} + +// File: contracts/upgradeable_contracts/BasicAMBMediator.sol + +pragma solidity 0.7.5; + +/** + * @title BasicAMBMediator + * @dev Basic storage and methods needed by mediators to interact with AMB bridge. + */ +abstract contract BasicAMBMediator is Ownable { + bytes32 internal constant BRIDGE_CONTRACT = 0x811bbb11e8899da471f0e69a3ed55090fc90215227fc5fb1cb0d6e962ea7b74f; // keccak256(abi.encodePacked("bridgeContract")) + bytes32 internal constant MEDIATOR_CONTRACT = 0x98aa806e31e94a687a31c65769cb99670064dd7f5a87526da075c5fb4eab9880; // keccak256(abi.encodePacked("mediatorContract")) + + /** + * @dev Throws if caller on the other side is not an associated mediator. + */ + modifier onlyMediator() { + _onlyMediator(); + _; + } + + /** + * @dev Internal function for reducing onlyMediator modifier bytecode overhead. + */ + function _onlyMediator() internal view { + IAMB bridge = bridgeContract(); + require(msg.sender == address(bridge)); + require(bridge.messageSender() == mediatorContractOnOtherSide()); + } + + /** + * @dev Sets the AMB bridge contract address. Only the owner can call this method. + * @param _bridgeContract the address of the bridge contract. + */ + function setBridgeContract(address _bridgeContract) external onlyOwner { + _setBridgeContract(_bridgeContract); + } + + /** + * @dev Sets the mediator contract address from the other network. Only the owner can call this method. + * @param _mediatorContract the address of the mediator contract. + */ + function setMediatorContractOnOtherSide(address _mediatorContract) external onlyOwner { + _setMediatorContractOnOtherSide(_mediatorContract); + } + + /** + * @dev Get the AMB interface for the bridge contract address + * @return AMB interface for the bridge contract address + */ + function bridgeContract() public view returns (IAMB) { + return IAMB(addressStorage[BRIDGE_CONTRACT]); + } + + /** + * @dev Tells the mediator contract address from the other network. + * @return the address of the mediator contract. + */ + function mediatorContractOnOtherSide() public view virtual returns (address) { + return addressStorage[MEDIATOR_CONTRACT]; + } + + /** + * @dev Stores a valid AMB bridge contract address. + * @param _bridgeContract the address of the bridge contract. + */ + function _setBridgeContract(address _bridgeContract) internal { + require(Address.isContract(_bridgeContract)); + addressStorage[BRIDGE_CONTRACT] = _bridgeContract; + } + + /** + * @dev Stores the mediator contract address from the other network. + * @param _mediatorContract the address of the mediator contract. + */ + function _setMediatorContractOnOtherSide(address _mediatorContract) internal { + addressStorage[MEDIATOR_CONTRACT] = _mediatorContract; + } + + /** + * @dev Tells the id of the message originated on the other network. + * @return the id of the message originated on the other network. + */ + function messageId() internal view returns (bytes32) { + return bridgeContract().messageId(); + } + + /** + * @dev Tells the maximum gas limit that a message can use on its execution by the AMB bridge on the other network. + * @return the maximum gas limit value. + */ + function maxGasPerTx() internal view returns (uint256) { + return bridgeContract().maxGasPerTx(); + } + + function _passMessage(bytes memory _data, bool _useOracleLane) internal virtual returns (bytes32); +} + +// File: contracts/upgradeable_contracts/components/common/TokensRelayer.sol + +pragma solidity 0.7.5; + +/** + * @title TokensRelayer + * @dev Functionality for bridging multiple tokens to the other side of the bridge. + */ +abstract contract TokensRelayer is BasicAMBMediator, ReentrancyGuard { + using SafeMath for uint256; + using SafeERC20 for IERC677; + + /** + * @dev ERC677 transfer callback function. + * @param _from address of tokens sender. + * @param _value amount of transferred tokens. + * @param _data additional transfer data, can be used for passing alternative receiver address. + */ + function onTokenTransfer( + address _from, + uint256 _value, + bytes memory _data + ) external returns (bool) { + if (!lock()) { + bytes memory data = new bytes(0); + address receiver = _from; + if (_data.length >= 20) { + receiver = Bytes.bytesToAddress(_data); + if (_data.length > 20) { + assembly { + let size := sub(mload(_data), 20) + data := add(_data, 20) + mstore(data, size) + } + } + } + bridgeSpecificActionsOnTokenTransfer(msg.sender, _from, receiver, _value, data); + } + return true; + } + + /** + * @dev Initiate the bridge operation for some amount of tokens from msg.sender. + * The user should first call Approve method of the ERC677 token. + * @param token bridged token contract address. + * @param _receiver address that will receive the native tokens on the other network. + * @param _value amount of tokens to be transferred to the other network. + */ + function relayTokens( + IERC677 token, + address _receiver, + uint256 _value + ) external { + _relayTokens(token, _receiver, _value, new bytes(0)); + } + + /** + * @dev Initiate the bridge operation for some amount of tokens from msg.sender to msg.sender on the other side. + * The user should first call Approve method of the ERC677 token. + * @param token bridged token contract address. + * @param _value amount of tokens to be transferred to the other network. + */ + function relayTokens(IERC677 token, uint256 _value) external { + _relayTokens(token, msg.sender, _value, new bytes(0)); + } + + /** + * @dev Initiate the bridge operation for some amount of tokens from msg.sender. + * The user should first call Approve method of the ERC677 token. + * @param token bridged token contract address. + * @param _receiver address that will receive the native tokens on the other network. + * @param _value amount of tokens to be transferred to the other network. + * @param _data additional transfer data to be used on the other side. + */ + function relayTokensAndCall( + IERC677 token, + address _receiver, + uint256 _value, + bytes memory _data + ) external { + _relayTokens(token, _receiver, _value, _data); + } + + /** + * @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. + * The user should first call Approve method of the ERC677 token. + * @param token bridge token contract address. + * @param _receiver address that will receive the native tokens on the other network. + * @param _value amount of tokens to be transferred to the other network. + * @param _data additional transfer data to be used on the other side. + */ + function _relayTokens( + IERC677 token, + address _receiver, + uint256 _value, + bytes memory _data + ) internal { + // This lock is to prevent calling passMessage twice if a ERC677 token is used. + // When transferFrom is called, after the transfer, the ERC677 token will call onTokenTransfer from this contract + // which will call passMessage. + require(!lock()); + + uint256 balanceBefore = token.balanceOf(address(this)); + setLock(true); + token.safeTransferFrom(msg.sender, address(this), _value); + setLock(false); + uint256 balanceDiff = token.balanceOf(address(this)).sub(balanceBefore); + require(balanceDiff <= _value); + bridgeSpecificActionsOnTokenTransfer(address(token), msg.sender, _receiver, balanceDiff, _data); + } + + function bridgeSpecificActionsOnTokenTransfer( + address _token, + address _from, + address _receiver, + uint256 _value, + bytes memory _data + ) internal virtual; +} + +// File: contracts/upgradeable_contracts/VersionableBridge.sol + +pragma solidity 0.7.5; + +interface VersionableBridge { + function getBridgeInterfacesVersion() + external + pure + returns ( + uint64 major, + uint64 minor, + uint64 patch + ); + + function getBridgeMode() external pure returns (bytes4); +} + +// File: contracts/upgradeable_contracts/components/common/OmnibridgeInfo.sol + +pragma solidity 0.7.5; + +/** + * @title OmnibridgeInfo + * @dev Functionality for versioning Omnibridge mediator. + */ +contract OmnibridgeInfo is VersionableBridge { + event TokensBridgingInitiated(address indexed token, address indexed sender, uint256 value, bytes32 indexed messageId); + event TokensBridged(address indexed token, address indexed recipient, uint256 value, bytes32 indexed messageId); + + /** + * @dev Tells the bridge interface version that this contract supports. + * @return major value of the version + * @return minor value of the version + * @return patch value of the version + */ + function getBridgeInterfacesVersion() + external + pure + override + returns ( + uint64 major, + uint64 minor, + uint64 patch + ) + { + // The patch version increased by 1 to reflect differences from + // the original contract: token minter for STAKE token is disabled, + // _isOracleDrivenLaneAllowed modified + return (3, 0, 3); + } + + /** + * @dev Tells the bridge mode that this contract supports. + * @return _data 4 bytes representing the bridge mode + */ + function getBridgeMode() external pure override returns (bytes4 _data) { + return 0xb1516c26; // bytes4(keccak256(abi.encodePacked("multi-erc-to-erc-amb"))) + } +} + +// File: contracts/upgradeable_contracts/components/common/TokensBridgeLimits.sol + +pragma solidity 0.7.5; + +/** + * @title TokensBridgeLimits + * @dev Functionality for keeping track of bridging limits for multiple tokens. + */ +contract TokensBridgeLimits is EternalStorage, Ownable { + using SafeMath for uint256; + + // token == 0x00..00 represents default limits (assuming decimals == 18) for all newly created tokens + event DailyLimitChanged(address indexed token, uint256 newLimit); + event ExecutionDailyLimitChanged(address indexed token, uint256 newLimit); + + /** + * @dev Checks if specified token was already bridged at least once. + * @param _token address of the token contract. + * @return true, if token address is address(0) or token was already bridged. + */ + function isTokenRegistered(address _token) public view returns (bool) { + return minPerTx(_token) > 0; + } + + /** + * @dev Retrieves the total spent amount for particular token during specific day. + * @param _token address of the token contract. + * @param _day day number for which spent amount if requested. + * @return amount of tokens sent through the bridge to the other side. + */ + function totalSpentPerDay(address _token, uint256 _day) public view returns (uint256) { + return uintStorage[keccak256(abi.encodePacked("totalSpentPerDay", _token, _day))]; + } + + /** + * @dev Retrieves the total executed amount for particular token during specific day. + * @param _token address of the token contract. + * @param _day day number for which spent amount if requested. + * @return amount of tokens received from the bridge from the other side. + */ + function totalExecutedPerDay(address _token, uint256 _day) public view returns (uint256) { + return uintStorage[keccak256(abi.encodePacked("totalExecutedPerDay", _token, _day))]; + } + + /** + * @dev Retrieves current daily limit for a particular token contract. + * @param _token address of the token contract. + * @return daily limit on tokens that can be sent through the bridge per day. + */ + function dailyLimit(address _token) public view returns (uint256) { + return uintStorage[keccak256(abi.encodePacked("dailyLimit", _token))]; + } + + /** + * @dev Retrieves current execution daily limit for a particular token contract. + * @param _token address of the token contract. + * @return daily limit on tokens that can be received from the bridge on the other side per day. + */ + function executionDailyLimit(address _token) public view returns (uint256) { + return uintStorage[keccak256(abi.encodePacked("executionDailyLimit", _token))]; + } + + /** + * @dev Retrieves current maximum amount of tokens per one transfer for a particular token contract. + * @param _token address of the token contract. + * @return maximum amount on tokens that can be sent through the bridge in one transfer. + */ + function maxPerTx(address _token) public view returns (uint256) { + return uintStorage[keccak256(abi.encodePacked("maxPerTx", _token))]; + } + + /** + * @dev Retrieves current maximum execution amount of tokens per one transfer for a particular token contract. + * @param _token address of the token contract. + * @return maximum amount on tokens that can received from the bridge on the other side in one transaction. + */ + function executionMaxPerTx(address _token) public view returns (uint256) { + return uintStorage[keccak256(abi.encodePacked("executionMaxPerTx", _token))]; + } + + /** + * @dev Retrieves current minimum amount of tokens per one transfer for a particular token contract. + * @param _token address of the token contract. + * @return minimum amount on tokens that can be sent through the bridge in one transfer. + */ + function minPerTx(address _token) public view returns (uint256) { + return uintStorage[keccak256(abi.encodePacked("minPerTx", _token))]; + } + + /** + * @dev Checks that bridged amount of tokens conforms to the configured limits. + * @param _token address of the token contract. + * @param _amount amount of bridge tokens. + * @return true, if specified amount can be bridged. + */ + function withinLimit(address _token, uint256 _amount) public view returns (bool) { + uint256 nextLimit = totalSpentPerDay(_token, getCurrentDay()).add(_amount); + return + dailyLimit(address(0)) > 0 && dailyLimit(_token) >= nextLimit && _amount <= maxPerTx(_token) && _amount >= minPerTx(_token); + } + + /** + * @dev Checks that bridged amount of tokens conforms to the configured execution limits. + * @param _token address of the token contract. + * @param _amount amount of bridge tokens. + * @return true, if specified amount can be processed and executed. + */ + function withinExecutionLimit(address _token, uint256 _amount) public view returns (bool) { + uint256 nextLimit = totalExecutedPerDay(_token, getCurrentDay()).add(_amount); + return + executionDailyLimit(address(0)) > 0 && executionDailyLimit(_token) >= nextLimit && _amount <= executionMaxPerTx(_token); + } + + /** + * @dev Returns current day number. + * @return day number. + */ + function getCurrentDay() public view returns (uint256) { + // solhint-disable-next-line not-rely-on-time + return block.timestamp / 1 days; + } + + /** + * @dev Updates daily limit for the particular token. Only owner can call this method. + * @param _token address of the token contract, or address(0) for configuring the efault limit. + * @param _dailyLimit daily allowed amount of bridged tokens, should be greater than maxPerTx. + * 0 value is also allowed, will stop the bridge operations in outgoing direction. + */ + function setDailyLimit(address _token, uint256 _dailyLimit) external onlyOwner { + require(isTokenRegistered(_token)); + require(_dailyLimit > maxPerTx(_token) || _dailyLimit == 0); + uintStorage[keccak256(abi.encodePacked("dailyLimit", _token))] = _dailyLimit; + emit DailyLimitChanged(_token, _dailyLimit); + } + + /** + * @dev Updates execution daily limit for the particular token. Only owner can call this method. + * @param _token address of the token contract, or address(0) for configuring the default limit. + * @param _dailyLimit daily allowed amount of executed tokens, should be greater than executionMaxPerTx. + * 0 value is also allowed, will stop the bridge operations in incoming direction. + */ + function setExecutionDailyLimit(address _token, uint256 _dailyLimit) external onlyOwner { + require(isTokenRegistered(_token)); + require(_dailyLimit > executionMaxPerTx(_token) || _dailyLimit == 0); + uintStorage[keccak256(abi.encodePacked("executionDailyLimit", _token))] = _dailyLimit; + emit ExecutionDailyLimitChanged(_token, _dailyLimit); + } + + /** + * @dev Updates execution maximum per transaction for the particular token. Only owner can call this method. + * @param _token address of the token contract, or address(0) for configuring the default limit. + * @param _maxPerTx maximum amount of executed tokens per one transaction, should be less than executionDailyLimit. + * 0 value is also allowed, will stop the bridge operations in incoming direction. + */ + function setExecutionMaxPerTx(address _token, uint256 _maxPerTx) external onlyOwner { + require(isTokenRegistered(_token)); + require(_maxPerTx == 0 || (_maxPerTx > 0 && _maxPerTx < executionDailyLimit(_token))); + uintStorage[keccak256(abi.encodePacked("executionMaxPerTx", _token))] = _maxPerTx; + } + + /** + * @dev Updates maximum per transaction for the particular token. Only owner can call this method. + * @param _token address of the token contract, or address(0) for configuring the default limit. + * @param _maxPerTx maximum amount of tokens per one transaction, should be less than dailyLimit, greater than minPerTx. + * 0 value is also allowed, will stop the bridge operations in outgoing direction. + */ + function setMaxPerTx(address _token, uint256 _maxPerTx) external onlyOwner { + require(isTokenRegistered(_token)); + require(_maxPerTx == 0 || (_maxPerTx > minPerTx(_token) && _maxPerTx < dailyLimit(_token))); + uintStorage[keccak256(abi.encodePacked("maxPerTx", _token))] = _maxPerTx; + } + + /** + * @dev Updates minimum per transaction for the particular token. Only owner can call this method. + * @param _token address of the token contract, or address(0) for configuring the default limit. + * @param _minPerTx minimum amount of tokens per one transaction, should be less than maxPerTx and dailyLimit. + */ + function setMinPerTx(address _token, uint256 _minPerTx) external onlyOwner { + require(isTokenRegistered(_token)); + require(_minPerTx > 0 && _minPerTx < dailyLimit(_token) && _minPerTx < maxPerTx(_token)); + uintStorage[keccak256(abi.encodePacked("minPerTx", _token))] = _minPerTx; + } + + /** + * @dev Retrieves maximum available bridge amount per one transaction taking into account maxPerTx() and dailyLimit() parameters. + * @param _token address of the token contract, or address(0) for the default limit. + * @return minimum of maxPerTx parameter and remaining daily quota. + */ + function maxAvailablePerTx(address _token) public view returns (uint256) { + uint256 _maxPerTx = maxPerTx(_token); + uint256 _dailyLimit = dailyLimit(_token); + uint256 _spent = totalSpentPerDay(_token, getCurrentDay()); + uint256 _remainingOutOfDaily = _dailyLimit > _spent ? _dailyLimit - _spent : 0; + return _maxPerTx < _remainingOutOfDaily ? _maxPerTx : _remainingOutOfDaily; + } + + /** + * @dev Internal function for adding spent amount for some token. + * @param _token address of the token contract. + * @param _day day number, when tokens are processed. + * @param _value amount of bridge tokens. + */ + function addTotalSpentPerDay( + address _token, + uint256 _day, + uint256 _value + ) internal { + uintStorage[keccak256(abi.encodePacked("totalSpentPerDay", _token, _day))] = totalSpentPerDay(_token, _day).add(_value); + } + + /** + * @dev Internal function for adding executed amount for some token. + * @param _token address of the token contract. + * @param _day day number, when tokens are processed. + * @param _value amount of bridge tokens. + */ + function addTotalExecutedPerDay( + address _token, + uint256 _day, + uint256 _value + ) internal { + uintStorage[keccak256(abi.encodePacked("totalExecutedPerDay", _token, _day))] = totalExecutedPerDay(_token, _day).add(_value); + } + + /** + * @dev Internal function for initializing limits for some token. + * @param _token address of the token contract. + * @param _limits [ 0 = dailyLimit, 1 = maxPerTx, 2 = minPerTx ]. + */ + function _setLimits(address _token, uint256[3] memory _limits) internal { + require( + _limits[2] > 0 && // minPerTx > 0 + _limits[1] > _limits[2] && // maxPerTx > minPerTx + _limits[0] > _limits[1] // dailyLimit > maxPerTx + ); + + uintStorage[keccak256(abi.encodePacked("dailyLimit", _token))] = _limits[0]; + uintStorage[keccak256(abi.encodePacked("maxPerTx", _token))] = _limits[1]; + uintStorage[keccak256(abi.encodePacked("minPerTx", _token))] = _limits[2]; + + emit DailyLimitChanged(_token, _limits[0]); + } + + /** + * @dev Internal function for initializing execution limits for some token. + * @param _token address of the token contract. + * @param _limits [ 0 = executionDailyLimit, 1 = executionMaxPerTx ]. + */ + function _setExecutionLimits(address _token, uint256[2] memory _limits) internal { + require(_limits[1] < _limits[0]); // foreignMaxPerTx < foreignDailyLimit + + uintStorage[keccak256(abi.encodePacked("executionDailyLimit", _token))] = _limits[0]; + uintStorage[keccak256(abi.encodePacked("executionMaxPerTx", _token))] = _limits[1]; + + emit ExecutionDailyLimitChanged(_token, _limits[0]); + } + + /** + * @dev Internal function for initializing limits for some token relative to its decimals parameter. + * @param _token address of the token contract. + * @param _decimals token decimals parameter. + */ + function _initializeTokenBridgeLimits(address _token, uint256 _decimals) internal { + uint256 factor; + if (_decimals < 18) { + factor = 10**(18 - _decimals); + + uint256 _minPerTx = minPerTx(address(0)).div(factor); + uint256 _maxPerTx = maxPerTx(address(0)).div(factor); + 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) { + // Numbers 1, 100, 10000 are chosen in a semi-random way, + // so that any token with small decimals can still be bridged in some amounts. + // It is possible to override limits for the particular token later if needed. + _minPerTx = 1; + if (_maxPerTx <= _minPerTx) { + _maxPerTx = 100; + _executionMaxPerTx = 100; + if (_dailyLimit <= _maxPerTx || _executionDailyLimit <= _executionMaxPerTx) { + _dailyLimit = 10000; + _executionDailyLimit = 10000; + } + } + } + _setLimits(_token, [_dailyLimit, _maxPerTx, _minPerTx]); + _setExecutionLimits(_token, [_executionDailyLimit, _executionMaxPerTx]); + } else { + factor = 10**(_decimals - 18); + _setLimits( + _token, + [dailyLimit(address(0)).mul(factor), maxPerTx(address(0)).mul(factor), minPerTx(address(0)).mul(factor)] + ); + _setExecutionLimits(_token, [executionDailyLimit(address(0)).mul(factor), executionMaxPerTx(address(0)).mul(factor)]); + } + } +} + +// File: contracts/upgradeable_contracts/components/common/BridgeOperationsStorage.sol + +pragma solidity 0.7.5; + +/** + * @title BridgeOperationsStorage + * @dev Functionality for storing processed bridged operations. + */ +abstract contract BridgeOperationsStorage is EternalStorage { + /** + * @dev Stores the bridged token of a message sent to the AMB bridge. + * @param _messageId of the message sent to the bridge. + * @param _token bridged token address. + */ + function setMessageToken(bytes32 _messageId, address _token) internal { + addressStorage[keccak256(abi.encodePacked("messageToken", _messageId))] = _token; + } + + /** + * @dev Tells the bridged token address of a message sent to the AMB bridge. + * @return address of a token contract. + */ + function messageToken(bytes32 _messageId) internal view returns (address) { + return addressStorage[keccak256(abi.encodePacked("messageToken", _messageId))]; + } + + /** + * @dev Stores the value of a message sent to the AMB bridge. + * @param _messageId of the message sent to the bridge. + * @param _value amount of tokens bridged. + */ + function setMessageValue(bytes32 _messageId, uint256 _value) internal { + uintStorage[keccak256(abi.encodePacked("messageValue", _messageId))] = _value; + } + + /** + * @dev Tells the amount of tokens of a message sent to the AMB bridge. + * @return value representing amount of tokens. + */ + function messageValue(bytes32 _messageId) internal view returns (uint256) { + return uintStorage[keccak256(abi.encodePacked("messageValue", _messageId))]; + } + + /** + * @dev Stores the receiver of a message sent to the AMB bridge. + * @param _messageId of the message sent to the bridge. + * @param _recipient receiver of the tokens bridged. + */ + function setMessageRecipient(bytes32 _messageId, address _recipient) internal { + addressStorage[keccak256(abi.encodePacked("messageRecipient", _messageId))] = _recipient; + } + + /** + * @dev Tells the receiver of a message sent to the AMB bridge. + * @return address of the receiver. + */ + function messageRecipient(bytes32 _messageId) internal view returns (address) { + return addressStorage[keccak256(abi.encodePacked("messageRecipient", _messageId))]; + } +} + +// File: contracts/upgradeable_contracts/components/common/FailedMessagesProcessor.sol + +pragma solidity 0.7.5; + +/** + * @title FailedMessagesProcessor + * @dev Functionality for fixing failed bridging operations. + */ +abstract contract FailedMessagesProcessor is BasicAMBMediator, BridgeOperationsStorage { + event FailedMessageFixed(bytes32 indexed messageId, address token, address recipient, uint256 value); + + /** + * @dev Method to be called when a bridged message execution failed. It will generate a new message requesting to + * fix/roll back the transferred assets on the other network. + * @param _messageId id of the message which execution failed. + */ + function requestFailedMessageFix(bytes32 _messageId) external { + IAMB bridge = bridgeContract(); + require(!bridge.messageCallStatus(_messageId)); + require(bridge.failedMessageReceiver(_messageId) == address(this)); + require(bridge.failedMessageSender(_messageId) == mediatorContractOnOtherSide()); + + bytes4 methodSelector = this.fixFailedMessage.selector; + bytes memory data = abi.encodeWithSelector(methodSelector, _messageId); + _passMessage(data, true); + } + + /** + * @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 onlyMediator { + require(!messageFixed(_messageId)); + + address token = messageToken(_messageId); + address recipient = messageRecipient(_messageId); + uint256 value = messageValue(_messageId); + setMessageFixed(_messageId); + executeActionOnFixedTokens(token, recipient, value); + emit FailedMessageFixed(_messageId, token, recipient, value); + } + + /** + * @dev Tells if a message sent to the AMB bridge has been fixed. + * @return bool indicating the status of the message. + */ + function messageFixed(bytes32 _messageId) public view returns (bool) { + return boolStorage[keccak256(abi.encodePacked("messageFixed", _messageId))]; + } + + /** + * @dev Sets that the message sent to the AMB bridge has been fixed. + * @param _messageId of the message sent to the bridge. + */ + function setMessageFixed(bytes32 _messageId) internal { + boolStorage[keccak256(abi.encodePacked("messageFixed", _messageId))] = true; + } + + function executeActionOnFixedTokens( + address _token, + address _recipient, + uint256 _value + ) internal virtual; +} + +// File: contracts/upgradeability/Proxy.sol + +pragma solidity 0.7.5; + +/** + * @title Proxy + * @dev Gives the possibility to delegate any call to a foreign implementation. + */ +abstract contract Proxy { + /** + * @dev Tells the address of the implementation where every call will be delegated. + * @return address of the implementation to which it will be delegated + */ + function implementation() public view virtual returns (address); + + /** + * @dev Fallback function allowing to perform a delegatecall to the given implementation. + * This function will return whatever the implementation call returns + */ + fallback() external payable { + // solhint-disable-previous-line no-complex-fallback + address _impl = implementation(); + require(_impl != address(0)); + assembly { + /* + 0x40 is the "free memory slot", meaning a pointer to next slot of empty memory. mload(0x40) + loads the data in the free memory slot, so `ptr` is a pointer to the next slot of empty + memory. It's needed because we're going to write the return data of delegatecall to the + free memory slot. + */ + let ptr := mload(0x40) + /* + `calldatacopy` is copy calldatasize bytes from calldata + First argument is the destination to which data is copied(ptr) + Second argument specifies the start position of the copied data. + Since calldata is sort of its own unique location in memory, + 0 doesn't refer to 0 in memory or 0 in storage - it just refers to the zeroth byte of calldata. + That's always going to be the zeroth byte of the function selector. + Third argument, calldatasize, specifies how much data will be copied. + calldata is naturally calldatasize bytes long (same thing as msg.data.length) + */ + calldatacopy(ptr, 0, calldatasize()) + /* + delegatecall params explained: + gas: the amount of gas to provide for the call. `gas` is an Opcode that gives + us the amount of gas still available to execution + + _impl: address of the contract to delegate to + + ptr: to pass copied data + + calldatasize: loads the size of `bytes memory data`, same as msg.data.length + + 0, 0: These are for the `out` and `outsize` params. Because the output could be dynamic, + these are set to 0, 0 so the output data will not be written to memory. The output + data will be read using `returndatasize` and `returdatacopy` instead. + + result: This will be 0 if the call fails and 1 if it succeeds + */ + let result := delegatecall(gas(), _impl, ptr, calldatasize(), 0, 0) + /* + + */ + /* + ptr current points to the value stored at 0x40, + because we assigned it like ptr := mload(0x40). + Because we use 0x40 as a free memory pointer, + we want to make sure that the next time we want to allocate memory, + we aren't overwriting anything important. + So, by adding ptr and returndatasize, + we get a memory location beyond the end of the data we will be copying to ptr. + We place this in at 0x40, and any reads from 0x40 will now read from free memory + */ + mstore(0x40, add(ptr, returndatasize())) + /* + `returndatacopy` is an Opcode that copies the last return data to a slot. `ptr` is the + slot it will copy to, 0 means copy from the beginning of the return data, and size is + the amount of data to copy. + `returndatasize` is an Opcode that gives us the size of the last return data. In this case, that is the size of the data returned from delegatecall + */ + returndatacopy(ptr, 0, returndatasize()) + + /* + if `result` is 0, revert. + if `result` is 1, return `size` amount of data from `ptr`. This is the data that was + copied to `ptr` from the delegatecall return data + */ + switch result + case 0 { + revert(ptr, returndatasize()) + } + default { + return(ptr, returndatasize()) + } + } + } +} + +// File: contracts/upgradeable_contracts/modules/factory/TokenProxy.sol + +pragma solidity 0.7.5; + +interface IPermittableTokenVersion { + function version() external pure returns (string memory); +} + +/** + * @title TokenProxy + * @dev Helps to reduces the size of the deployed bytecode for automatically created tokens, by using a proxy contract. + */ +contract TokenProxy is Proxy { + // storage layout is copied from PermittableToken.sol + string internal name; + string internal symbol; + uint8 internal decimals; + mapping(address => uint256) internal balances; + uint256 internal totalSupply; + mapping(address => mapping(address => uint256)) internal allowed; + address internal owner; + bool internal mintingFinished; + address internal bridgeContractAddr; + // string public constant version = "1"; + bytes32 internal DOMAIN_SEPARATOR; + // bytes32 public constant PERMIT_TYPEHASH = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb; + mapping(address => uint256) internal nonces; + mapping(address => mapping(address => uint256)) internal expirations; + + /** + * @dev Creates a non-upgradeable token proxy for PermitableToken.sol, initializes its eternalStorage. + * @param _tokenImage address of the token image used for mirroring all functions. + * @param _name token name. + * @param _symbol token symbol. + * @param _decimals token decimals. + * @param _chainId chain id for current network. + * @param _owner address of the owner for this contract. + */ + constructor( + address _tokenImage, + string memory _name, + string memory _symbol, + uint8 _decimals, + uint256 _chainId, + address _owner + ) { + string memory version = IPermittableTokenVersion(_tokenImage).version(); + + assembly { + // EIP 1967 + // bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) + sstore(0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc, _tokenImage) + } + name = _name; + symbol = _symbol; + decimals = _decimals; + owner = _owner; // _owner == HomeOmnibridge/ForeignOmnibridge mediator + bridgeContractAddr = _owner; + DOMAIN_SEPARATOR = keccak256( + abi.encode( + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256(bytes(_name)), + keccak256(bytes(version)), + _chainId, + address(this) + ) + ); + } + + /** + * @dev Retrieves the implementation contract address, mirrored token image. + * @return impl token image address. + */ + function implementation() public view override returns (address impl) { + assembly { + impl := sload(0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc) + } + } +} + +// File: contracts/upgradeable_contracts/modules/OwnableModule.sol + +pragma solidity 0.7.5; + +/** + * @title OwnableModule + * @dev Common functionality for multi-token extension non-upgradeable module. + */ +contract OwnableModule { + address public owner; + + /** + * @dev Initializes this contract. + * @param _owner address of the owner that is allowed to perform additional actions on the particular module. + */ + constructor(address _owner) { + owner = _owner; + } + + /** + * @dev Throws if sender is not the owner of this contract. + */ + modifier onlyOwner() { + require(msg.sender == owner); + _; + } + + /** + * @dev Changes the owner of this contract. + * @param _newOwner address of the new owner. + */ + function transferOwnership(address _newOwner) external onlyOwner { + owner = _newOwner; + } +} + +// File: contracts/upgradeable_contracts/modules/factory/TokenFactory.sol + +pragma solidity 0.7.5; + +/** + * @title TokenFactory + * @dev Factory contract for deployment of new TokenProxy contracts. + */ +contract TokenFactory is OwnableModule { + address public tokenImage; + + /** + * @dev Initializes this contract + * @param _owner of this factory contract. + * @param _tokenImage address of the token image contract that should be used for creation of new tokens. + */ + constructor(address _owner, address _tokenImage) OwnableModule(_owner) { + tokenImage = _tokenImage; + } + + /** + * @dev Updates the address of the used token image contract. + * Only owner can call this method. + * @param _tokenImage address of the new token image used for further deployments. + */ + function setTokenImage(address _tokenImage) external onlyOwner { + require(Address.isContract(_tokenImage)); + tokenImage = _tokenImage; + } + + /** + * @dev Deploys a new TokenProxy contract, using saved token image contract as a template. + * @param _name deployed token name. + * @param _symbol deployed token symbol. + * @param _decimals deployed token decimals. + * @param _chainId chain id of the current environment. + * @return address of a newly created contract. + */ + function deploy( + string calldata _name, + string calldata _symbol, + uint8 _decimals, + uint256 _chainId + ) external returns (address) { + return address(new TokenProxy(tokenImage, _name, _symbol, _decimals, _chainId, msg.sender)); + } +} + +// File: contracts/upgradeable_contracts/modules/factory/TokenFactoryConnector.sol + +pragma solidity 0.7.5; + +/** + * @title TokenFactoryConnector + * @dev Connectivity functionality for working with TokenFactory contract. + */ +contract TokenFactoryConnector is Ownable { + bytes32 internal constant TOKEN_FACTORY_CONTRACT = 0x269c5905f777ee6391c7a361d17039a7d62f52ba9fffeb98c5ade342705731a3; // keccak256(abi.encodePacked("tokenFactoryContract")) + + /** + * @dev Updates an address of the used TokenFactory contract used for creating new tokens. + * @param _tokenFactory address of TokenFactory contract. + */ + function setTokenFactory(address _tokenFactory) external onlyOwner { + _setTokenFactory(_tokenFactory); + } + + /** + * @dev Retrieves an address of the token factory contract. + * @return address of the TokenFactory contract. + */ + function tokenFactory() public view returns (TokenFactory) { + return TokenFactory(addressStorage[TOKEN_FACTORY_CONTRACT]); + } + + /** + * @dev Internal function for updating an address of the token factory contract. + * @param _tokenFactory address of the deployed TokenFactory contract. + */ + function _setTokenFactory(address _tokenFactory) internal { + require(Address.isContract(_tokenFactory)); + addressStorage[TOKEN_FACTORY_CONTRACT] = _tokenFactory; + } +} + +// File: contracts/interfaces/IBurnableMintableERC677Token.sol + +pragma solidity 0.7.5; + +interface IBurnableMintableERC677Token is IERC677 { + function mint(address _to, uint256 _amount) external returns (bool); + + function burn(uint256 _value) external; + + function claimTokens(address _token, address _to) external; +} + +// File: contracts/interfaces/IERC20Metadata.sol + +pragma solidity 0.7.5; + +interface IERC20Metadata { + function name() external view returns (string memory); + + function symbol() external view returns (string memory); + + function decimals() external view returns (uint8); +} + +// File: contracts/interfaces/IERC20Receiver.sol + +pragma solidity 0.7.5; + +interface IERC20Receiver { + function onTokenBridged( + address token, + uint256 value, + bytes calldata data + ) external; +} + +// File: contracts/libraries/TokenReader.sol + +pragma solidity 0.7.5; + +// solhint-disable +interface ITokenDetails { + function name() external view; + + function NAME() external view; + + function symbol() external view; + + function SYMBOL() external view; + + function decimals() external view; + + function DECIMALS() external view; +} + +// solhint-enable + +/** + * @title TokenReader + * @dev Helper methods for reading name/symbol/decimals parameters from ERC20 token contracts. + */ +library TokenReader { + /** + * @dev Reads the name property of the provided token. + * Either name() or NAME() method is used. + * Both, string and bytes32 types are supported. + * @param _token address of the token contract. + * @return token name as a string or an empty string if none of the methods succeeded. + */ + function readName(address _token) internal view returns (string memory) { + (bool status, bytes memory data) = _token.staticcall(abi.encodeWithSelector(ITokenDetails.name.selector)); + if (!status) { + (status, data) = _token.staticcall(abi.encodeWithSelector(ITokenDetails.NAME.selector)); + if (!status) { + return ""; + } + } + return _convertToString(data); + } + + /** + * @dev Reads the symbol property of the provided token. + * Either symbol() or SYMBOL() method is used. + * Both, string and bytes32 types are supported. + * @param _token address of the token contract. + * @return token symbol as a string or an empty string if none of the methods succeeded. + */ + function readSymbol(address _token) internal view returns (string memory) { + (bool status, bytes memory data) = _token.staticcall(abi.encodeWithSelector(ITokenDetails.symbol.selector)); + if (!status) { + (status, data) = _token.staticcall(abi.encodeWithSelector(ITokenDetails.SYMBOL.selector)); + if (!status) { + return ""; + } + } + return _convertToString(data); + } + + /** + * @dev Reads the decimals property of the provided token. + * Either decimals() or DECIMALS() method is used. + * @param _token address of the token contract. + * @return token decimals or 0 if none of the methods succeeded. + */ + function readDecimals(address _token) internal view returns (uint8) { + (bool status, bytes memory data) = _token.staticcall(abi.encodeWithSelector(ITokenDetails.decimals.selector)); + if (!status) { + (status, data) = _token.staticcall(abi.encodeWithSelector(ITokenDetails.DECIMALS.selector)); + if (!status) { + return 0; + } + } + return abi.decode(data, (uint8)); + } + + /** + * @dev Internal function for converting returned value of name()/symbol() from bytes32/string to string. + * @param returnData data returned by the token contract. + * @return string with value obtained from returnData. + */ + function _convertToString(bytes memory returnData) private pure returns (string memory) { + if (returnData.length > 32) { + return abi.decode(returnData, (string)); + } else if (returnData.length == 32) { + bytes32 data = abi.decode(returnData, (bytes32)); + string memory res = new string(32); + assembly { + let len := 0 + mstore(add(res, 32), data) // save value in result string + + // solhint-disable + for { + + } gt(data, 0) { + len := add(len, 1) + } { + // until string is empty + data := shl(8, data) // shift left by one symbol + } + // solhint-enable + mstore(res, len) // save result string length + } + return res; + } else { + return ""; + } + } +} + +// File: contracts/libraries/SafeMint.sol + +pragma solidity 0.7.5; + +/** + * @title SafeMint + * @dev Wrapper around the mint() function in all mintable tokens that verifies the return value. + */ +library SafeMint { + /** + * @dev Wrapper around IBurnableMintableERC677Token.mint() that verifies that output value is true. + * @param _token token contract. + * @param _to address of the tokens receiver. + * @param _value amount of tokens to mint. + */ + function safeMint( + IBurnableMintableERC677Token _token, + address _to, + uint256 _value + ) internal { + require(_token.mint(_to, _value)); + } +} + +// File: contracts/upgradeable_contracts/BasicOmnibridge.sol + +pragma solidity 0.7.5; + +/** + * @title BasicOmnibridge + * @dev Common functionality for multi-token mediator intended to work on top of AMB bridge. + */ +abstract contract BasicOmnibridge is + Initializable, + Upgradeable, + Claimable, + OmnibridgeInfo, + TokensRelayer, + FailedMessagesProcessor, + BridgedTokensRegistry, + NativeTokensRegistry, + MediatorBalanceStorage, + TokenFactoryConnector, + TokensBridgeLimits +{ + using SafeERC20 for IERC677; + using SafeMint for IBurnableMintableERC677Token; + using SafeMath for uint256; + + // Workaround for storing variable up-to-32 bytes suffix + uint256 private immutable SUFFIX_SIZE; + bytes32 private immutable SUFFIX; + + // Since contract is intended to be deployed under EternalStorageProxy, only constant and immutable variables can be set here + constructor(string memory _suffix) { + require(bytes(_suffix).length <= 32); + bytes32 suffix; + assembly { + suffix := mload(add(_suffix, 32)) + } + SUFFIX = suffix; + SUFFIX_SIZE = bytes(_suffix).length; + } + + /** + * @dev Handles the bridged tokens for the first time, includes deployment of new TokenProxy contract. + * Checks that the value is inside the execution limits and invokes the Mint or Unlock accordingly. + * @param _token address of the native ERC20/ERC677 token on the other side. + * @param _name name of the native token, name suffix will be appended, if empty, symbol will be used instead. + * @param _symbol symbol of the bridged token, if empty, name will be used instead. + * @param _decimals decimals of the bridge foreign token. + * @param _recipient address that will receive the tokens. + * @param _value amount of tokens to be received. + */ + function deployAndHandleBridgedTokens( + address _token, + string calldata _name, + string calldata _symbol, + uint8 _decimals, + address _recipient, + uint256 _value + ) external onlyMediator { + address bridgedToken = _getBridgedTokenOrDeploy(_token, _name, _symbol, _decimals); + + _handleTokens(bridgedToken, false, _recipient, _value); + } + + /** + * @dev Handles the bridged tokens for the first time, includes deployment of new TokenProxy contract. + * Executes a callback on the receiver. + * Checks that the value is inside the execution limits and invokes the Mint accordingly. + * @param _token address of the native ERC20/ERC677 token on the other side. + * @param _name name of the native token, name suffix will be appended, if empty, symbol will be used instead. + * @param _symbol symbol of the bridged token, if empty, name will be used instead. + * @param _decimals decimals of the bridge foreign token. + * @param _recipient address that will receive the tokens. + * @param _value amount of tokens to be received. + * @param _data additional data passed from the other chain. + */ + function deployAndHandleBridgedTokensAndCall( + address _token, + string calldata _name, + string calldata _symbol, + uint8 _decimals, + address _recipient, + uint256 _value, + bytes calldata _data + ) external onlyMediator { + address bridgedToken = _getBridgedTokenOrDeploy(_token, _name, _symbol, _decimals); + + _handleTokens(bridgedToken, false, _recipient, _value); + + _receiverCallback(_recipient, bridgedToken, _value, _data); + } + + /** + * @dev Handles the bridged tokens for the already registered token pair. + * Checks that the value is inside the execution limits and invokes the Mint accordingly. + * @param _token address of the native ERC20/ERC677 token on the other side. + * @param _recipient address that will receive the tokens. + * @param _value amount of tokens to be received. + */ + function handleBridgedTokens( + address _token, + address _recipient, + uint256 _value + ) external onlyMediator { + address token = bridgedTokenAddress(_token); + + require(isTokenRegistered(token)); + + _handleTokens(token, false, _recipient, _value); + } + + /** + * @dev Handles the bridged tokens for the already registered token pair. + * Checks that the value is inside the execution limits and invokes the Unlock accordingly. + * Executes a callback on the receiver. + * @param _token address of the native ERC20/ERC677 token on the other side. + * @param _recipient address that will receive the tokens. + * @param _value amount of tokens to be received. + * @param _data additional transfer data passed from the other side. + */ + function handleBridgedTokensAndCall( + address _token, + address _recipient, + uint256 _value, + bytes memory _data + ) external onlyMediator { + address token = bridgedTokenAddress(_token); + + require(isTokenRegistered(token)); + + _handleTokens(token, false, _recipient, _value); + + _receiverCallback(_recipient, token, _value, _data); + } + + /** + * @dev Handles the bridged tokens that are native to this chain. + * Checks that the value is inside the execution limits and invokes the Unlock accordingly. + * @param _token native ERC20 token. + * @param _recipient address that will receive the tokens. + * @param _value amount of tokens to be received. + */ + function handleNativeTokens( + address _token, + address _recipient, + uint256 _value + ) external onlyMediator { + _ackBridgedTokenDeploy(_token); + + _handleTokens(_token, true, _recipient, _value); + } + + /** + * @dev Handles the bridged tokens that are native to this chain. + * Checks that the value is inside the execution limits and invokes the Unlock accordingly. + * Executes a callback on the receiver. + * @param _token native ERC20 token. + * @param _recipient address that will receive the tokens. + * @param _value amount of tokens to be received. + * @param _data additional transfer data passed from the other side. + */ + function handleNativeTokensAndCall( + address _token, + address _recipient, + uint256 _value, + bytes memory _data + ) external onlyMediator { + _ackBridgedTokenDeploy(_token); + + _handleTokens(_token, true, _recipient, _value); + + _receiverCallback(_recipient, _token, _value, _data); + } + + /** + * @dev Checks if a given token is a bridged token that is native to this side of the bridge. + * @param _token address of token contract. + * @return message id of the send message. + */ + function isRegisteredAsNativeToken(address _token) public view returns (bool) { + return isTokenRegistered(_token) && nativeTokenAddress(_token) == address(0); + } + + /** + * @dev Unlock back the amount of tokens that were bridged to the other network but failed. + * @param _token address that bridged token contract. + * @param _recipient address that will receive the tokens. + * @param _value amount of tokens to be received. + */ + function executeActionOnFixedTokens( + address _token, + address _recipient, + uint256 _value + ) internal override { + _releaseTokens(nativeTokenAddress(_token) == address(0), _token, _recipient, _value, _value); + } + + /** + * @dev Allows to pre-set the bridged token contract for not-yet bridged token. + * Only the owner can call this method. + * @param _nativeToken address of the token contract on the other side that was not yet bridged. + * @param _bridgedToken address of the bridged token contract. + */ + function setCustomTokenAddressPair(address _nativeToken, address _bridgedToken) external onlyOwner { + require(!isTokenRegistered(_bridgedToken)); + require(nativeTokenAddress(_bridgedToken) == address(0)); + require(bridgedTokenAddress(_nativeToken) == address(0)); + // Unfortunately, there is no simple way to verify that the _nativeToken address + // does not belong to the bridged token on the other side, + // since information about bridged tokens addresses is not transferred back. + // Therefore, owner account calling this function SHOULD manually verify on the other side of the bridge that + // nativeTokenAddress(_nativeToken) == address(0) && isTokenRegistered(_nativeToken) == false. + + IBurnableMintableERC677Token(_bridgedToken).safeMint(address(this), 1); + IBurnableMintableERC677Token(_bridgedToken).burn(1); + + _setTokenAddressPair(_nativeToken, _bridgedToken); + } + + /** + * @dev Allows to send to the other network the amount of locked tokens that can be forced into the contract + * without the invocation of the required methods. (e. g. regular transfer without a call to onTokenTransfer) + * @param _token address of the token contract. + * Before calling this method, it must be carefully investigated how imbalance happened + * in order to avoid an attempt to steal the funds from a token with double addresses + * (e.g. TUSD is accessible at both 0x8dd5fbCe2F6a956C3022bA3663759011Dd51e73E and 0x0000000000085d4780B73119b644AE5ecd22b376) + * @param _receiver the address that will receive the tokens on the other network. + */ + function fixMediatorBalance(address _token, address _receiver) external onlyIfUpgradeabilityOwner validAddress(_receiver) { + require(isRegisteredAsNativeToken(_token)); + + uint256 balance = IERC677(_token).balanceOf(address(this)); + uint256 expectedBalance = mediatorBalance(_token); + require(balance > expectedBalance); + uint256 diff = balance - expectedBalance; + uint256 available = maxAvailablePerTx(_token); + require(available > 0); + if (diff > available) { + diff = available; + } + addTotalSpentPerDay(_token, getCurrentDay(), diff); + + bytes memory data = _prepareMessage(address(0), _token, _receiver, diff, new bytes(0)); + bytes32 _messageId = _passMessage(data, true); + _recordBridgeOperation(_messageId, _token, _receiver, diff); + } + + /** + * @dev Claims stuck tokens. Only unsupported tokens can be claimed. + * When dealing with already supported tokens, fixMediatorBalance can be used instead. + * @param _token address of claimed token, address(0) for native + * @param _to address of tokens receiver + */ + function claimTokens(address _token, address _to) external onlyIfUpgradeabilityOwner { + // Only unregistered tokens and native coins are allowed to be claimed with the use of this function + require(_token == address(0) || !isTokenRegistered(_token)); + claimValues(_token, _to); + } + + /** + * @dev Withdraws erc20 tokens or native coins from the bridged token contract. + * Only the proxy owner is allowed to call this method. + * @param _bridgedToken address of the bridged token contract. + * @param _token address of the claimed token or address(0) for native coins. + * @param _to address of the tokens/coins receiver. + */ + function claimTokensFromTokenContract( + address _bridgedToken, + address _token, + address _to + ) external onlyIfUpgradeabilityOwner { + IBurnableMintableERC677Token(_bridgedToken).claimTokens(_token, _to); + } + + /** + * @dev Internal function for recording bridge operation for further usage. + * Recorded information is used for fixing failed requests on the other side. + * @param _messageId id of the sent message. + * @param _token bridged token address. + * @param _sender address of the tokens sender. + * @param _value bridged value. + */ + function _recordBridgeOperation( + bytes32 _messageId, + address _token, + address _sender, + uint256 _value + ) internal { + setMessageToken(_messageId, _token); + setMessageRecipient(_messageId, _sender); + setMessageValue(_messageId, _value); + + emit TokensBridgingInitiated(_token, _sender, _value, _messageId); + } + + /** + * @dev Constructs the message to be sent to the other side. Burns/locks bridged amount of tokens. + * @param _nativeToken address of the native token contract. + * @param _token bridged token address. + * @param _receiver address of the tokens receiver on the other side. + * @param _value bridged value. + * @param _data additional transfer data passed from the other side. + */ + function _prepareMessage( + address _nativeToken, + address _token, + address _receiver, + uint256 _value, + bytes memory _data + ) internal returns (bytes memory) { + bool withData = _data.length > 0 || msg.sig == this.relayTokensAndCall.selector; + + // process token is native with respect to this side of the bridge + if (_nativeToken == address(0)) { + _setMediatorBalance(_token, mediatorBalance(_token).add(_value)); + + // process token which bridged alternative was already ACKed to be deployed + if (isBridgedTokenDeployAcknowledged(_token)) { + return + withData + ? abi.encodeWithSelector(this.handleBridgedTokensAndCall.selector, _token, _receiver, _value, _data) + : abi.encodeWithSelector(this.handleBridgedTokens.selector, _token, _receiver, _value); + } + + uint8 decimals = TokenReader.readDecimals(_token); + string memory name = TokenReader.readName(_token); + string memory symbol = TokenReader.readSymbol(_token); + + require(bytes(name).length > 0 || bytes(symbol).length > 0); + + return + withData + ? abi.encodeWithSelector( + this.deployAndHandleBridgedTokensAndCall.selector, + _token, + name, + symbol, + decimals, + _receiver, + _value, + _data + ) + : abi.encodeWithSelector(this.deployAndHandleBridgedTokens.selector, _token, name, symbol, decimals, _receiver, _value); + } + + // process already known token that is bridged from other chain + IBurnableMintableERC677Token(_token).burn(_value); + return + withData + ? abi.encodeWithSelector(this.handleNativeTokensAndCall.selector, _nativeToken, _receiver, _value, _data) + : abi.encodeWithSelector(this.handleNativeTokens.selector, _nativeToken, _receiver, _value); + } + + /** + * @dev Internal function for getting minter proxy address. + * @param _token address of the token to mint. + * @return address of the minter contract that should be used for calling mint(address,uint256) + */ + function _getMinterFor(address _token) internal pure virtual returns (IBurnableMintableERC677Token) { + return IBurnableMintableERC677Token(_token); + } + + /** + * Internal function for unlocking some amount of tokens. + * @param _isNative true, if token is native w.r.t. to this side of the bridge. + * @param _token address of the token contract. + * @param _recipient address of the tokens receiver. + * @param _value amount of tokens to unlock. + * @param _balanceChange amount of balance to subtract from the mediator balance. + */ + function _releaseTokens( + bool _isNative, + address _token, + address _recipient, + uint256 _value, + uint256 _balanceChange + ) internal virtual { + if (_isNative) { + IERC677(_token).safeTransfer(_recipient, _value); + _setMediatorBalance(_token, mediatorBalance(_token).sub(_balanceChange)); + } else { + _getMinterFor(_token).safeMint(_recipient, _value); + } + } + + /** + * Internal function for getting address of the bridged token. Deploys new token if necessary. + * @param _token address of the token contract on the other side of the bridge. + * @param _name name of the native token, name suffix will be appended, if empty, symbol will be used instead. + * @param _symbol symbol of the bridged token, if empty, name will be used instead. + * @param _decimals decimals of the bridge foreign token. + */ + function _getBridgedTokenOrDeploy( + address _token, + string calldata _name, + string calldata _symbol, + uint8 _decimals + ) internal returns (address) { + address bridgedToken = bridgedTokenAddress(_token); + if (bridgedToken == address(0)) { + string memory name = _name; + string memory symbol = _symbol; + require(bytes(name).length > 0 || bytes(symbol).length > 0); + if (bytes(name).length == 0) { + name = symbol; + } else if (bytes(symbol).length == 0) { + symbol = name; + } + name = _transformName(name); + bridgedToken = tokenFactory().deploy(name, symbol, _decimals, bridgeContract().sourceChainId()); + _setTokenAddressPair(_token, bridgedToken); + _initializeTokenBridgeLimits(bridgedToken, _decimals); + } else if (!isTokenRegistered(bridgedToken)) { + require(IERC20Metadata(bridgedToken).decimals() == _decimals); + _initializeTokenBridgeLimits(bridgedToken, _decimals); + } + return bridgedToken; + } + + /** + * Notifies receiving contract about the completed bridging operation. + * @param _recipient address of the tokens receiver. + * @param _token address of the bridged token. + * @param _value amount of tokens transferred. + * @param _data additional data passed to the callback. + */ + function _receiverCallback( + address _recipient, + address _token, + uint256 _value, + bytes memory _data + ) internal { + if (Address.isContract(_recipient)) { + _recipient.call(abi.encodeWithSelector(IERC20Receiver.onTokenBridged.selector, _token, _value, _data)); + } + } + + /** + * @dev Internal function for transforming the bridged token name. Appends a side-specific suffix. + * @param _name bridged token from the other side. + * @return token name for this side of the bridge. + */ + function _transformName(string memory _name) internal view returns (string memory) { + string memory result = string(abi.encodePacked(_name, SUFFIX)); + uint256 size = SUFFIX_SIZE; + assembly { + mstore(result, add(mload(_name), size)) + } + return result; + } + + function _handleTokens( + address _token, + bool _isNative, + address _recipient, + uint256 _value + ) internal virtual; +} + +// File: contracts/upgradeable_contracts/modules/forwarding_rules/MultiTokenForwardingRulesManager.sol + +pragma solidity 0.7.5; + +/** + * @title MultiTokenForwardingRulesManager + * @dev Multi token mediator functionality for managing destination AMB lanes permissions. + */ +contract MultiTokenForwardingRulesManager is OwnableModule { + address internal constant ANY_ADDRESS = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF; + + // Forwarding rules mapping + // token => sender => receiver => destination lane + mapping(address => mapping(address => mapping(address => int256))) public forwardingRule; + + event ForwardingRuleUpdated(address token, address sender, address receiver, int256 lane); + + constructor(address _owner) OwnableModule(_owner) {} + + /** + * @dev Tells the destination lane for a particular bridge operation by checking several wildcard forwarding rules. + * @param _token address of the token contract on the foreign side of the bridge. + * @param _sender address of the tokens sender on the home side of the bridge. + * @param _receiver address of the tokens receiver on the foreign side of the bridge. + * @return destination lane identifier, where the message should be forwarded to. + * 1 - oracle-driven-lane should be used. + * 0 - default behaviour should be applied. + * -1 - manual lane should be used. + */ + function destinationLane( + address _token, + address _sender, + address _receiver + ) public view returns (int256) { + int256 defaultLane = forwardingRule[_token][ANY_ADDRESS][ANY_ADDRESS]; // specific token for all senders and receivers + int256 lane; + if (defaultLane < 0) { + lane = forwardingRule[_token][_sender][ANY_ADDRESS]; // specific token for specific sender + if (lane != 0) return lane; + lane = forwardingRule[_token][ANY_ADDRESS][_receiver]; // specific token for specific receiver + if (lane != 0) return lane; + return defaultLane; + } + lane = forwardingRule[ANY_ADDRESS][_sender][ANY_ADDRESS]; // all tokens for specific sender + if (lane != 0) return lane; + return forwardingRule[ANY_ADDRESS][ANY_ADDRESS][_receiver]; // all tokens for specific receiver + } + + /** + * Updates the forwarding rule for bridging specific token. + * Only owner can call this method. + * @param _token address of the token contract on the foreign side. + * @param _enable true, if bridge operations for a given token should be forwarded to the manual lane. + */ + function setTokenForwardingRule(address _token, bool _enable) external { + require(_token != ANY_ADDRESS); + _setForwardingRule(_token, ANY_ADDRESS, ANY_ADDRESS, _enable ? int256(-1) : int256(0)); + } + + /** + * Allows a particular address to send bridge requests to the oracle-driven lane for a particular token. + * Only owner can call this method. + * @param _token address of the token contract on the foreign side. + * @param _sender address of the tokens sender on the home side of the bridge. + * @param _enable true, if bridge operations for a given token and sender should be forwarded to the oracle-driven lane. + */ + function setSenderExceptionForTokenForwardingRule( + address _token, + address _sender, + bool _enable + ) external { + require(_token != ANY_ADDRESS); + require(_sender != ANY_ADDRESS); + _setForwardingRule(_token, _sender, ANY_ADDRESS, _enable ? int256(1) : int256(0)); + } + + /** + * Allows a particular address to receive bridged tokens from the oracle-driven lane for a particular token. + * Only owner can call this method. + * @param _token address of the token contract on the foreign side. + * @param _receiver address of the tokens receiver on the foreign side of the bridge. + * @param _enable true, if bridge operations for a given token and receiver should be forwarded to the oracle-driven lane. + */ + function setReceiverExceptionForTokenForwardingRule( + address _token, + address _receiver, + bool _enable + ) external { + require(_token != ANY_ADDRESS); + require(_receiver != ANY_ADDRESS); + _setForwardingRule(_token, ANY_ADDRESS, _receiver, _enable ? int256(1) : int256(0)); + } + + /** + * Updates the forwarding rule for the specific sender. + * Only owner can call this method. + * @param _sender address of the tokens sender on the home side. + * @param _enable true, if all bridge operations from a given sender should be forwarded to the manual lane. + */ + function setSenderForwardingRule(address _sender, bool _enable) external { + require(_sender != ANY_ADDRESS); + _setForwardingRule(ANY_ADDRESS, _sender, ANY_ADDRESS, _enable ? int256(-1) : int256(0)); + } + + /** + * Updates the forwarding rule for the specific receiver. + * Only owner can call this method. + * @param _receiver address of the tokens receiver on the foreign side. + * @param _enable true, if all bridge operations to a given receiver should be forwarded to the manual lane. + */ + function setReceiverForwardingRule(address _receiver, bool _enable) external { + require(_receiver != ANY_ADDRESS); + _setForwardingRule(ANY_ADDRESS, ANY_ADDRESS, _receiver, _enable ? int256(-1) : int256(0)); + } + + /** + * @dev Internal function for updating the preferred destination lane for the specific wildcard pattern. + * Only owner can call this method. + * Examples: + * _setForwardingRule(tokenA, ANY_ADDRESS, ANY_ADDRESS, -1) - forward all operations on tokenA to the manual lane + * _setForwardingRule(tokenA, Alice, ANY_ADDRESS, 1) - allow Alice to use the oracle-driven lane for bridging tokenA + * _setForwardingRule(tokenA, ANY_ADDRESS, Bob, 1) - forward all tokenA bridge operations, where Bob is the receiver, to the oracle-driven lane + * _setForwardingRule(ANY_ADDRESS, Mallory, ANY_ADDRESS, -1) - forward all bridge operations from Mallory to the manual lane + * @param _token address of the token contract on the foreign side of the bridge. + * @param _sender address of the tokens sender on the home side of the bridge. + * @param _receiver address of the tokens receiver on the foreign side of the bridge. + * @param _lane preferred destination lane for the particular sender. + * 1 - forward to the oracle-driven lane. + * 0 - behaviour is unset, proceed by checking other less-specific rules. + * -1 - manual lane should be used. + */ + function _setForwardingRule( + address _token, + address _sender, + address _receiver, + int256 _lane + ) internal onlyOwner { + forwardingRule[_token][_sender][_receiver] = _lane; + + emit ForwardingRuleUpdated(_token, _sender, _receiver, _lane); + } +} + +// File: contracts/upgradeable_contracts/modules/forwarding_rules/MultiTokenForwardingRulesConnector.sol + +pragma solidity 0.7.5; + +/** + * @title MultiTokenForwardingRulesConnector + * @dev Connectivity functionality that is required for using forwarding rules manager. + */ +contract MultiTokenForwardingRulesConnector is Ownable { + bytes32 internal constant FORWARDING_RULES_MANAGER_CONTRACT = + 0x5f86f226cd489cc09187d5f5e0adfb94308af0d4ceac482dd8a8adea9d80daf4; // keccak256(abi.encodePacked("forwardingRulesManagerContract")) + + /** + * @dev Updates an address of the used forwarding rules manager contract. + * @param _manager address of forwarding rules manager contract. + */ + function setForwardingRulesManager(address _manager) external onlyOwner { + _setForwardingRulesManager(_manager); + } + + /** + * @dev Retrieves an address of the forwarding rules manager contract. + * @return address of the forwarding rules manager contract. + */ + function forwardingRulesManager() public view returns (MultiTokenForwardingRulesManager) { + return MultiTokenForwardingRulesManager(addressStorage[FORWARDING_RULES_MANAGER_CONTRACT]); + } + + /** + * @dev Internal function for updating an address of the used forwarding rules manager contract. + * @param _manager address of forwarding rules manager contract. + */ + function _setForwardingRulesManager(address _manager) internal { + require(_manager == address(0) || Address.isContract(_manager)); + addressStorage[FORWARDING_RULES_MANAGER_CONTRACT] = _manager; + } + + /** + * @dev Checks if bridge operation is allowed to use oracle driven lane. + * @param _token address of the token contract on the foreign side of the bridge. + * @param _sender address of the tokens sender on the home side of the bridge. + * @param _receiver address of the tokens receiver on the foreign side of the bridge. + * @return true, if message can be forwarded to the oracle-driven lane. + */ + function _isOracleDrivenLaneAllowed( + address _token, + address _sender, + address _receiver + ) internal view returns (bool) { + MultiTokenForwardingRulesManager manager = forwardingRulesManager(); + // Default lane is manual, since oracle-driven line is requred only for very small + // number of exceptions + return address(manager) == address(0) || manager.destinationLane(_token, _sender, _receiver) > 0; + } +} + +// File: contracts/upgradeable_contracts/modules/MediatorOwnableModule.sol + +pragma solidity 0.7.5; + +/** + * @title MediatorOwnableModule + * @dev Common functionality for non-upgradeable Omnibridge extension module. + */ +contract MediatorOwnableModule is OwnableModule { + address public mediator; + + /** + * @dev Initializes this contract. + * @param _mediator address of the deployed Omnibridge extension for which this module is deployed. + * @param _owner address of the owner that is allowed to perform additional actions on the particular module. + */ + constructor(address _mediator, address _owner) OwnableModule(_owner) { + require(Address.isContract(_mediator)); + mediator = _mediator; + } + + /** + * @dev Throws if sender is not the Omnibridge extension. + */ + modifier onlyMediator() { + require(msg.sender == mediator); + _; + } +} + +// File: contracts/upgradeable_contracts/modules/fee_manager/OmnibridgeFeeManager.sol + +pragma solidity 0.7.5; + +/** + * @title OmnibridgeFeeManager + * @dev Implements the logic to distribute fees from the Omnibridge mediator contract operations. + * The fees are distributed in the form of ERC20/ERC677 tokens to the list of reward addresses. + */ +contract OmnibridgeFeeManager is MediatorOwnableModule { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + // This is not a real fee value but a relative value used to calculate the fee percentage. + // 1 ether = 100% of the value. + uint256 internal constant MAX_FEE = 1 ether; + uint256 internal constant MAX_REWARD_ACCOUNTS = 50; + + bytes32 public constant HOME_TO_FOREIGN_FEE = 0x741ede137d0537e88e0ea0ff25b1f22d837903dbbee8980b4a06e8523247ee26; // keccak256(abi.encodePacked("homeToForeignFee")) + bytes32 public constant FOREIGN_TO_HOME_FEE = 0x03be2b2875cb41e0e77355e802a16769bb8dfcf825061cde185c73bf94f12625; // keccak256(abi.encodePacked("foreignToHomeFee")) + + // mapping feeType => token address => fee percentage + mapping(bytes32 => mapping(address => uint256)) internal fees; + address[] internal rewardAddresses; + + event FeeUpdated(bytes32 feeType, address indexed token, uint256 fee); + + /** + * @dev Stores the initial parameters of the fee manager. + * @param _mediator address of the mediator contract used together with this fee manager. + * @param _owner address of the contract owner. + * @param _rewardAddresses list of unique initial reward addresses, between whom fees will be distributed + * @param _fees array with initial fees for both bridge directions. + * [ 0 = homeToForeignFee, 1 = foreignToHomeFee ] + */ + constructor( + address _mediator, + address _owner, + address[] memory _rewardAddresses, + uint256[2] memory _fees + ) MediatorOwnableModule(_mediator, _owner) { + require(_rewardAddresses.length <= MAX_REWARD_ACCOUNTS); + _setFee(HOME_TO_FOREIGN_FEE, address(0), _fees[0]); + _setFee(FOREIGN_TO_HOME_FEE, address(0), _fees[1]); + + for (uint256 i = 0; i < _rewardAddresses.length; i++) { + require(_isValidAddress(_rewardAddresses[i])); + for (uint256 j = 0; j < i; j++) { + require(_rewardAddresses[j] != _rewardAddresses[i]); + } + } + rewardAddresses = _rewardAddresses; + } + + /** + * @dev Throws if given fee amount is invalid. + */ + modifier validFee(uint256 _fee) { + require(_fee < MAX_FEE); + /* solcov ignore next */ + _; + } + + /** + * @dev Throws if given fee type is unknown. + */ + modifier validFeeType(bytes32 _feeType) { + require(_feeType == HOME_TO_FOREIGN_FEE || _feeType == FOREIGN_TO_HOME_FEE); + /* solcov ignore next */ + _; + } + + /** + * @dev Updates the value for the particular fee type. + * Only the owner can call this method. + * @param _feeType type of the updated fee, can be one of [HOME_TO_FOREIGN_FEE, FOREIGN_TO_HOME_FEE]. + * @param _token address of the token contract for which fee should apply, 0x00..00 describes the initial fee for newly created tokens. + * @param _fee new fee value, in percentage (1 ether == 10**18 == 100%). + */ + function setFee( + bytes32 _feeType, + address _token, + uint256 _fee + ) external validFeeType(_feeType) onlyOwner { + _setFee(_feeType, _token, _fee); + } + + /** + * @dev Retrieves the value for the particular fee type. + * @param _feeType type of the updated fee, can be one of [HOME_TO_FOREIGN_FEE, FOREIGN_TO_HOME_FEE]. + * @param _token address of the token contract for which fee should apply, 0x00..00 describes the initial fee for newly created tokens. + * @return fee value associated with the requested fee type. + */ + function getFee(bytes32 _feeType, address _token) public view validFeeType(_feeType) returns (uint256) { + // use token-specific fee if one is registered + uint256 _tokenFee = fees[_feeType][_token]; + if (_tokenFee > 0) { + return _tokenFee - 1; + } + // use default fee otherwise + return fees[_feeType][address(0)] - 1; + } + + /** + * @dev Calculates the amount of fee to pay for the value of the particular fee type. + * @param _feeType type of the updated fee, can be one of [HOME_TO_FOREIGN_FEE, FOREIGN_TO_HOME_FEE]. + * @param _token address of the token contract for which fee should apply, 0x00..00 describes the initial fee for newly created tokens. + * @param _value bridged value, for which fee should be evaluated. + * @return amount of fee to be subtracted from the transferred value. + */ + function calculateFee( + bytes32 _feeType, + address _token, + uint256 _value + ) public view returns (uint256) { + if (rewardAddresses.length == 0) { + return 0; + } + uint256 _fee = getFee(_feeType, _token); + return _value.mul(_fee).div(MAX_FEE); + } + + /** + * @dev Adds a new address to the list of accounts to receive rewards for the operations. + * Only the owner can call this method. + * @param _addr new reward address. + */ + function addRewardAddress(address _addr) external onlyOwner { + require(_isValidAddress(_addr)); + require(!isRewardAddress(_addr)); + require(rewardAddresses.length < MAX_REWARD_ACCOUNTS); + rewardAddresses.push(_addr); + } + + /** + * @dev Removes an address from the list of accounts to receive rewards for the operations. + * Only the owner can call this method. + * finds the element, swaps it with the last element, and then deletes it; + * @param _addr to be removed. + * return boolean whether the element was found and deleted + */ + function removeRewardAddress(address _addr) external onlyOwner { + uint256 numOfAccounts = rewardAddresses.length; + for (uint256 i = 0; i < numOfAccounts; i++) { + if (rewardAddresses[i] == _addr) { + rewardAddresses[i] = rewardAddresses[numOfAccounts - 1]; + delete rewardAddresses[numOfAccounts - 1]; + rewardAddresses.pop(); + return; + } + } + // If account is not found and removed, the transactions is reverted + revert(); + } + + /** + * @dev Tells the number of registered reward receivers. + * @return amount of addresses. + */ + function rewardAddressCount() external view returns (uint256) { + return rewardAddresses.length; + } + + /** + * @dev Tells the list of registered reward receivers. + * @return list with all registered reward receivers. + */ + function rewardAddressList() external view returns (address[] memory) { + return rewardAddresses; + } + + /** + * @dev Tells if a given address is part of the reward address list. + * @param _addr address to check if it is part of the list. + * @return true if the given address is in the list + */ + function isRewardAddress(address _addr) public view returns (bool) { + for (uint256 i = 0; i < rewardAddresses.length; i++) { + if (rewardAddresses[i] == _addr) { + return true; + } + } + return false; + } + + /** + * @dev Distributes the fee proportionally between registered reward addresses. + * @param _token address of the token contract for which fee should be distributed. + */ + function distributeFee(address _token) external onlyMediator { + uint256 numOfAccounts = rewardAddresses.length; + uint256 fee = IERC20(_token).balanceOf(address(this)); + uint256 feePerAccount = fee.div(numOfAccounts); + uint256 randomAccountIndex; + uint256 diff = fee.sub(feePerAccount.mul(numOfAccounts)); + if (diff > 0) { + randomAccountIndex = random(numOfAccounts); + } + + for (uint256 i = 0; i < numOfAccounts; i++) { + uint256 feeToDistribute = feePerAccount; + if (diff > 0 && randomAccountIndex == i) { + feeToDistribute = feeToDistribute.add(diff); + } + IERC20(_token).safeTransfer(rewardAddresses[i], feeToDistribute); + } + } + + /** + * @dev Calculates a random number based on the block number. + * @param _count the max value for the random number. + * @return a number between 0 and _count. + */ + function random(uint256 _count) internal view returns (uint256) { + return uint256(blockhash(block.number.sub(1))) % _count; + } + + /** + * @dev Internal function for updating the fee value for the given fee type. + * @param _feeType type of the updated fee, can be one of [HOME_TO_FOREIGN_FEE, FOREIGN_TO_HOME_FEE]. + * @param _token address of the token contract for which fee should apply, 0x00..00 describes the initial fee for newly created tokens. + * @param _fee new fee value, in percentage (1 ether == 10**18 == 100%). + */ + function _setFee( + bytes32 _feeType, + address _token, + uint256 _fee + ) internal validFee(_fee) { + fees[_feeType][_token] = 1 + _fee; + emit FeeUpdated(_feeType, _token, _fee); + } + + /** + * @dev Checks if a given address can be a reward receiver. + * @param _addr address of the proposed reward receiver. + * @return true, if address is valid. + */ + function _isValidAddress(address _addr) internal view returns (bool) { + return _addr != address(0) && _addr != address(mediator); + } +} + +// File: contracts/upgradeable_contracts/modules/fee_manager/OmnibridgeFeeManagerConnector.sol + +pragma solidity 0.7.5; + +/** + * @title OmnibridgeFeeManagerConnector + * @dev Connectivity functionality for working with OmnibridgeFeeManager contract. + */ +abstract contract OmnibridgeFeeManagerConnector is Ownable { + using SafeERC20 for IERC20; + using SafeMint for IBurnableMintableERC677Token; + + bytes32 internal constant FEE_MANAGER_CONTRACT = 0x779a349c5bee7817f04c960f525ee3e2f2516078c38c68a3149787976ee837e5; // keccak256(abi.encodePacked("feeManagerContract")) + bytes32 internal constant HOME_TO_FOREIGN_FEE = 0x741ede137d0537e88e0ea0ff25b1f22d837903dbbee8980b4a06e8523247ee26; // keccak256(abi.encodePacked("homeToForeignFee")) + bytes32 internal constant FOREIGN_TO_HOME_FEE = 0x03be2b2875cb41e0e77355e802a16769bb8dfcf825061cde185c73bf94f12625; // keccak256(abi.encodePacked("foreignToHomeFee")) + + event FeeDistributed(uint256 fee, address indexed token, bytes32 indexed messageId); + event FeeDistributionFailed(address indexed token, uint256 fee); + + /** + * @dev Updates an address of the used fee manager contract used for calculating and distributing fees. + * @param _feeManager address of fee manager contract. + */ + function setFeeManager(address _feeManager) external onlyOwner { + _setFeeManager(_feeManager); + } + + /** + * @dev Retrieves an address of the fee manager contract. + * @return address of the fee manager contract. + */ + function feeManager() public view returns (OmnibridgeFeeManager) { + return OmnibridgeFeeManager(addressStorage[FEE_MANAGER_CONTRACT]); + } + + /** + * @dev Internal function for updating an address of the used fee manager contract. + * @param _feeManager address of fee manager contract. + */ + function _setFeeManager(address _feeManager) internal { + require(_feeManager == address(0) || Address.isContract(_feeManager)); + addressStorage[FEE_MANAGER_CONTRACT] = _feeManager; + } + + /** + * @dev Internal function for calculating and distributing fee through the separate fee manager contract. + * @param _feeType type of the fee, can be one of [HOME_TO_FOREIGN_FEE, FOREIGN_TO_HOME_FEE]. + * @param _isNative true, if distributed token is native to this side of the bridge. + * @param _from address of the tokens sender, needed only if _feeType is HOME_TO_FOREIGN_FEE. + * @param _token address of the token contract, for which fee should be processed. + * @param _value amount of tokens bridged. + * @return total amount of fee distributed. + */ + function _distributeFee( + bytes32 _feeType, + bool _isNative, + address _from, + address _token, + uint256 _value + ) internal returns (uint256) { + OmnibridgeFeeManager manager = feeManager(); + if (address(manager) != address(0)) { + // Next line disables fee collection in case sender is one of the reward addresses. + // It is needed to allow a 100% withdrawal of tokens from the home side. + // If fees are not disabled for reward receivers, small fraction of tokens will always + // be redistributed between the same set of reward addresses, which is not the desired behaviour. + if (_feeType == HOME_TO_FOREIGN_FEE && manager.isRewardAddress(_from)) { + return 0; + } + uint256 fee = manager.calculateFee(_feeType, _token, _value); + if (fee > 0) { + if (_feeType == HOME_TO_FOREIGN_FEE) { + // for home -> foreign direction, fee is collected using transfer(address,uint256) method + // if transfer to the manager contract fails, the transaction is reverted + IERC20(_token).safeTransfer(address(manager), fee); + } else { + // for foreign -> home direction, + // fee is collected using transfer(address,uint256) method for native tokens, + // and using mint(address,uint256) method for bridged tokens. + // if transfer/mint to the manager contract fails, the message still will be processed, but without fees + bytes4 selector = _isNative ? IERC20.transfer.selector : IBurnableMintableERC677Token.mint.selector; + (bool status, bytes memory returnData) = _token.call(abi.encodeWithSelector(selector, manager, fee)); + if (!status) { + emit FeeDistributionFailed(_token, fee); + return 0; + } + require(returnData.length == 0 || abi.decode(returnData, (bool))); + } + manager.distributeFee(_token); + } + return fee; + } + return 0; + } + + function _getMinterFor(address _token) internal pure virtual returns (IBurnableMintableERC677Token); +} + +// File: contracts/upgradeable_contracts/modules/gas_limit/SelectorTokenGasLimitManager.sol + +pragma solidity 0.7.5; + +/** + * @title SelectorTokenGasLimitManager + * @dev Multi token mediator functionality for managing request gas limits. + */ +contract SelectorTokenGasLimitManager is OwnableModule { + IAMB public immutable bridge; + + uint256 internal defaultGasLimit; + mapping(bytes4 => uint256) internal selectorGasLimit; + mapping(bytes4 => mapping(address => uint256)) internal selectorTokenGasLimit; + + constructor( + IAMB _bridge, + address _owner, + uint256 _gasLimit + ) OwnableModule(_owner) { + require(_gasLimit <= _bridge.maxGasPerTx()); + bridge = _bridge; + defaultGasLimit = _gasLimit; + } + + /** + * @dev Throws if provided gas limit is greater then the maximum allowed gas limit in the AMB contract. + * @param _gasLimit gas limit value to check. + */ + modifier validGasLimit(uint256 _gasLimit) { + require(_gasLimit <= bridge.maxGasPerTx()); + _; + } + + /** + * @dev Throws if one of the provided gas limits is greater then the maximum allowed gas limit in the AMB contract. + * @param _length expected length of the _gasLimits array. + * @param _gasLimits array of gas limit values to check, should contain exactly _length elements. + */ + modifier validGasLimits(uint256 _length, uint256[] calldata _gasLimits) { + require(_gasLimits.length == _length); + uint256 maxGasLimit = bridge.maxGasPerTx(); + for (uint256 i = 0; i < _length; i++) { + require(_gasLimits[i] <= maxGasLimit); + } + _; + } + + /** + * @dev Sets the default gas limit to be used in the message execution by the AMB bridge on the other network. + * This value can't exceed the parameter maxGasPerTx defined on the AMB bridge. + * Only the owner can call this method. + * @param _gasLimit the gas limit for the message execution. + */ + function setRequestGasLimit(uint256 _gasLimit) external onlyOwner validGasLimit(_gasLimit) { + defaultGasLimit = _gasLimit; + } + + /** + * @dev Sets the selector-specific gas limit to be used in the message execution by the AMB bridge on the other network. + * This value can't exceed the parameter maxGasPerTx defined on the AMB bridge. + * Only the owner can call this method. + * @param _selector method selector of the outgoing message payload. + * @param _gasLimit the gas limit for the message execution. + */ + function setRequestGasLimit(bytes4 _selector, uint256 _gasLimit) external onlyOwner validGasLimit(_gasLimit) { + selectorGasLimit[_selector] = _gasLimit; + } + + /** + * @dev Sets the token-specific gas limit to be used in the message execution by the AMB bridge on the other network. + * This value can't exceed the parameter maxGasPerTx defined on the AMB bridge. + * Only the owner can call this method. + * @param _selector method selector of the outgoing message payload. + * @param _token address of the native token that is used in the first argument of handleBridgedTokens/handleNativeTokens. + * @param _gasLimit the gas limit for the message execution. + */ + function setRequestGasLimit( + bytes4 _selector, + address _token, + uint256 _gasLimit + ) external onlyOwner validGasLimit(_gasLimit) { + selectorTokenGasLimit[_selector][_token] = _gasLimit; + } + + /** + * @dev Tells the default gas limit to be used in the message execution by the AMB bridge on the other network. + * @return the gas limit for the message execution. + */ + function requestGasLimit() public view returns (uint256) { + return defaultGasLimit; + } + + /** + * @dev Tells the selector-specific gas limit to be used in the message execution by the AMB bridge on the other network. + * @param _selector method selector for the passed message. + * @return the gas limit for the message execution. + */ + function requestGasLimit(bytes4 _selector) public view returns (uint256) { + return selectorGasLimit[_selector]; + } + + /** + * @dev Tells the token-specific gas limit to be used in the message execution by the AMB bridge on the other network. + * @param _selector method selector for the passed message. + * @param _token address of the native token that is used in the first argument of handleBridgedTokens/handleNativeTokens. + * @return the gas limit for the message execution. + */ + function requestGasLimit(bytes4 _selector, address _token) public view returns (uint256) { + return selectorTokenGasLimit[_selector][_token]; + } + + /** + * @dev Tells the gas limit to use for the message execution by the AMB bridge on the other network. + * @param _data calldata to be used on the other side of the bridge, when execution a message. + * @return the gas limit for the message execution. + */ + function requestGasLimit(bytes memory _data) external view returns (uint256) { + bytes4 selector; + address token; + assembly { + // first 4 bytes of _data contain the selector of the function to be called on the other side of the bridge. + // mload(add(_data, 4)) loads selector to the 28-31 bytes of the word. + // shl(28 * 8, x) then used to correct the padding of the selector, putting it to 0-3 bytes of the word. + selector := shl(224, mload(add(_data, 4))) + // handleBridgedTokens/handleNativeTokens/... passes bridged token address as the first parameter. + // it is located in the 4-35 bytes of the calldata. + // 36 = bytes length padding (32) + selector length (4) + token := mload(add(_data, 36)) + } + uint256 gasLimit = selectorTokenGasLimit[selector][token]; + if (gasLimit == 0) { + gasLimit = selectorGasLimit[selector]; + if (gasLimit == 0) { + gasLimit = defaultGasLimit; + } + } + return gasLimit; + } + + /** + * @dev Sets the default values for different Omnibridge selectors. + * @param _gasLimits array with 7 gas limits for the following selectors of the outgoing messages: + * - deployAndHandleBridgedTokens, deployAndHandleBridgedTokensAndCall + * - handleBridgedTokens, handleBridgedTokensAndCall + * - handleNativeTokens, handleNativeTokensAndCall + * - fixFailedMessage + * Only the owner can call this method. + */ + function setCommonRequestGasLimits(uint256[] calldata _gasLimits) external onlyOwner validGasLimits(7, _gasLimits) { + require(_gasLimits[1] >= _gasLimits[0]); + require(_gasLimits[3] >= _gasLimits[2]); + require(_gasLimits[5] >= _gasLimits[4]); + require(_gasLimits[0] >= _gasLimits[2]); + require(_gasLimits[1] >= _gasLimits[3]); + selectorGasLimit[BasicOmnibridge.deployAndHandleBridgedTokens.selector] = _gasLimits[0]; + selectorGasLimit[BasicOmnibridge.deployAndHandleBridgedTokensAndCall.selector] = _gasLimits[1]; + selectorGasLimit[BasicOmnibridge.handleBridgedTokens.selector] = _gasLimits[2]; + selectorGasLimit[BasicOmnibridge.handleBridgedTokensAndCall.selector] = _gasLimits[3]; + selectorGasLimit[BasicOmnibridge.handleNativeTokens.selector] = _gasLimits[4]; + selectorGasLimit[BasicOmnibridge.handleNativeTokensAndCall.selector] = _gasLimits[5]; + selectorGasLimit[FailedMessagesProcessor.fixFailedMessage.selector] = _gasLimits[6]; + } + + /** + * @dev Sets the request gas limits for some specific token bridged from Foreign side of the bridge. + * @param _token address of the native token contract on the Foreign side. + * @param _gasLimits array with 2 gas limits for the following selectors of the outgoing messages: + * - handleNativeTokens, handleNativeTokensAndCall + * Only the owner can call this method. + */ + function setBridgedTokenRequestGasLimits(address _token, uint256[] calldata _gasLimits) + external + onlyOwner + validGasLimits(2, _gasLimits) + { + require(_gasLimits[1] >= _gasLimits[0]); + selectorTokenGasLimit[BasicOmnibridge.handleNativeTokens.selector][_token] = _gasLimits[0]; + selectorTokenGasLimit[BasicOmnibridge.handleNativeTokensAndCall.selector][_token] = _gasLimits[1]; + } + + /** + * @dev Sets the request gas limits for some specific token native to the Home side of the bridge. + * @param _token address of the native token contract on the Home side. + * @param _gasLimits array with 4 gas limits for the following selectors of the outgoing messages: + * - deployAndHandleBridgedTokens, deployAndHandleBridgedTokensAndCall + * - handleBridgedTokens, handleBridgedTokensAndCall + * Only the owner can call this method. + */ + function setNativeTokenRequestGasLimits(address _token, uint256[] calldata _gasLimits) + external + onlyOwner + validGasLimits(4, _gasLimits) + { + require(_gasLimits[1] >= _gasLimits[0]); + require(_gasLimits[3] >= _gasLimits[2]); + require(_gasLimits[0] >= _gasLimits[2]); + require(_gasLimits[1] >= _gasLimits[3]); + selectorTokenGasLimit[BasicOmnibridge.deployAndHandleBridgedTokens.selector][_token] = _gasLimits[0]; + selectorTokenGasLimit[BasicOmnibridge.deployAndHandleBridgedTokensAndCall.selector][_token] = _gasLimits[1]; + selectorTokenGasLimit[BasicOmnibridge.handleBridgedTokens.selector][_token] = _gasLimits[2]; + selectorTokenGasLimit[BasicOmnibridge.handleBridgedTokensAndCall.selector][_token] = _gasLimits[3]; + } +} + +// File: contracts/upgradeable_contracts/modules/gas_limit/SelectorTokenGasLimitConnector.sol + +pragma solidity 0.7.5; + +/** + * @title SelectorTokenGasLimitConnector + * @dev Connectivity functionality that is required for using gas limit manager. + */ +abstract contract SelectorTokenGasLimitConnector is Ownable, BasicAMBMediator { + bytes32 internal constant GAS_LIMIT_MANAGER_CONTRACT = 0x5f5bc4e0b888be22a35f2166061a04607296c26861006b9b8e089a172696a822; // keccak256(abi.encodePacked("gasLimitManagerContract")) + + /** + * @dev Updates an address of the used gas limit manager contract. + * @param _manager address of gas limit manager contract. + */ + function setGasLimitManager(address _manager) external onlyOwner { + _setGasLimitManager(_manager); + } + + /** + * @dev Retrieves an address of the gas limit manager contract. + * @return address of the gas limit manager contract. + */ + function gasLimitManager() public view returns (SelectorTokenGasLimitManager) { + return SelectorTokenGasLimitManager(addressStorage[GAS_LIMIT_MANAGER_CONTRACT]); + } + + /** + * @dev Internal function for updating an address of the used gas limit manager contract. + * @param _manager address of gas limit manager contract. + */ + function _setGasLimitManager(address _manager) internal { + require(_manager == address(0) || Address.isContract(_manager)); + addressStorage[GAS_LIMIT_MANAGER_CONTRACT] = _manager; + } + + /** + * @dev Tells the gas limit to use for the message execution by the AMB bridge on the other network. + * @param _data calldata to be used on the other side of the bridge, when execution a message. + * @return the gas limit for the message execution. + */ + function _chooseRequestGasLimit(bytes memory _data) internal view returns (uint256) { + SelectorTokenGasLimitManager manager = gasLimitManager(); + if (address(manager) == address(0)) { + return bridgeContract().maxGasPerTx(); + } else { + return manager.requestGasLimit(_data); + } + } +} + +// File: contracts/upgradeable_contracts/HomeOmnibridge.sol + +pragma solidity 0.7.5; + +/** + * @title HomeOmnibridge + * @dev Home side implementation for multi-token mediator intended to work on top of AMB bridge. + * It is designed to be used as an implementation contract of EternalStorageProxy contract. + */ +contract HomeOmnibridge is + BasicOmnibridge, + SelectorTokenGasLimitConnector, + OmnibridgeFeeManagerConnector, + MultiTokenForwardingRulesConnector +{ + using SafeMath for uint256; + using SafeERC20 for IERC677; + + constructor(string memory _suffix) BasicOmnibridge(_suffix) {} + + /** + * @dev Stores the initial parameters of the mediator. + * @param _bridgeContract the address of the AMB bridge contract. + * @param _mediatorContract the address of the mediator contract on the other network. + * @param _dailyLimitMaxPerTxMinPerTxArray array with limit values for the assets to be bridged to the other network. + * [ 0 = dailyLimit, 1 = maxPerTx, 2 = minPerTx ] + * @param _executionDailyLimitExecutionMaxPerTxArray array with limit values for the assets bridged from the other network. + * [ 0 = executionDailyLimit, 1 = executionMaxPerTx ] + * @param _gasLimitManager the gas limit manager contract address. + * @param _owner address of the owner of the mediator contract. + * @param _tokenFactory address of the TokenFactory contract that will be used for the deployment of new tokens. + * @param _feeManager address of the OmnibridgeFeeManager contract that will be used for fee distribution. + * @param _forwardingRulesManager address of the MultiTokenForwardingRulesManager contract that will be used for managing lane permissions. + */ + function initialize( + address _bridgeContract, + address _mediatorContract, + uint256[3] calldata _dailyLimitMaxPerTxMinPerTxArray, // [ 0 = _dailyLimit, 1 = _maxPerTx, 2 = _minPerTx ] + uint256[2] calldata _executionDailyLimitExecutionMaxPerTxArray, // [ 0 = _executionDailyLimit, 1 = _executionMaxPerTx ] + address _gasLimitManager, + address _owner, + address _tokenFactory, + address _feeManager, + address _forwardingRulesManager + ) external onlyRelevantSender returns (bool) { + require(!isInitialized()); + + _setBridgeContract(_bridgeContract); + _setMediatorContractOnOtherSide(_mediatorContract); + _setLimits(address(0), _dailyLimitMaxPerTxMinPerTxArray); + _setExecutionLimits(address(0), _executionDailyLimitExecutionMaxPerTxArray); + _setGasLimitManager(_gasLimitManager); + _setOwner(_owner); + _setTokenFactory(_tokenFactory); + _setFeeManager(_feeManager); + _setForwardingRulesManager(_forwardingRulesManager); + + setInitialize(); + + return isInitialized(); + } + + /** + * One-time function to be used together with upgradeToAndCall method. + * Sets the token factory contract. Resumes token bridging in the home to foreign direction. + * @param _tokenFactory address of the deployed TokenFactory contract. + * @param _forwardingRulesManager address of the deployed MultiTokenForwardingRulesManager contract. + * @param _gasLimitManager address of the deployed SelectorTokenGasLimitManager contract. + * @param _dailyLimit default daily limits used before stopping the bridge operation. + */ + function upgradeToReverseMode( + address _tokenFactory, + address _forwardingRulesManager, + address _gasLimitManager, + uint256 _dailyLimit + ) external { + require(msg.sender == address(this)); + + _setTokenFactory(_tokenFactory); + _setForwardingRulesManager(_forwardingRulesManager); + _setGasLimitManager(_gasLimitManager); + + uintStorage[keccak256(abi.encodePacked("dailyLimit", address(0)))] = _dailyLimit; + emit DailyLimitChanged(address(0), _dailyLimit); + } + + /** + * @dev Alias for bridgedTokenAddress for interface compatibility with the prior version of the Home mediator. + * @param _foreignToken address of the native token contract on the other side. + * @return address of the deployed bridged token contract. + */ + function homeTokenAddress(address _foreignToken) public view returns (address) { + return bridgedTokenAddress(_foreignToken); + } + + /** + * @dev Alias for nativeTokenAddress for interface compatibility with the prior version of the Home mediator. + * @param _homeToken address of the created bridged token contract on this side. + * @return address of the native token contract on the other side of the bridge. + */ + function foreignTokenAddress(address _homeToken) public view returns (address) { + return nativeTokenAddress(_homeToken); + } + + /** + * @dev Handles the bridged tokens. + * Checks that the value is inside the execution limits and invokes the Mint or Unlock accordingly. + * @param _token token contract address on this side of the bridge. + * @param _isNative true, if given token is native to this chain and Unlock should be used. + * @param _recipient address that will receive the tokens. + * @param _value amount of tokens to be received. + */ + function _handleTokens( + address _token, + bool _isNative, + address _recipient, + uint256 _value + ) internal override { + // prohibit withdrawal of tokens during other bridge operations (e.g. relayTokens) + // such reentrant withdrawal can lead to an incorrect balanceDiff calculation + require(!lock()); + + require(withinExecutionLimit(_token, _value)); + addTotalExecutedPerDay(_token, getCurrentDay(), _value); + + uint256 valueToBridge = _value; + uint256 fee = _distributeFee(FOREIGN_TO_HOME_FEE, _isNative, address(0), _token, valueToBridge); + bytes32 _messageId = messageId(); + if (fee > 0) { + emit FeeDistributed(fee, _token, _messageId); + valueToBridge = valueToBridge.sub(fee); + } + + _releaseTokens(_isNative, _token, _recipient, valueToBridge, _value); + + emit TokensBridged(_token, _recipient, valueToBridge, _messageId); + } + + /** + * @dev Executes action on deposit of bridged tokens + * @param _token address of the token contract + * @param _from address of tokens sender + * @param _receiver address of tokens receiver on the other side + * @param _value requested amount of bridged tokens + * @param _data additional transfer data to be used on the other side + */ + function bridgeSpecificActionsOnTokenTransfer( + address _token, + address _from, + address _receiver, + uint256 _value, + bytes memory _data + ) internal override { + require(_receiver != address(0) && _receiver != mediatorContractOnOtherSide()); + + // native unbridged token + if (!isTokenRegistered(_token)) { + uint8 decimals = TokenReader.readDecimals(_token); + _initializeTokenBridgeLimits(_token, decimals); + } + + require(withinLimit(_token, _value)); + addTotalSpentPerDay(_token, getCurrentDay(), _value); + + address nativeToken = nativeTokenAddress(_token); + uint256 fee = _distributeFee(HOME_TO_FOREIGN_FEE, nativeToken == address(0), _from, _token, _value); + uint256 valueToBridge = _value.sub(fee); + + bytes memory data = _prepareMessage(nativeToken, _token, _receiver, valueToBridge, _data); + + // Address of the home token is used here for determining lane permissions. + bytes32 _messageId = _passMessage(data, _isOracleDrivenLaneAllowed(_token, _from, _receiver)); + _recordBridgeOperation(_messageId, _token, _from, valueToBridge); + if (fee > 0) { + emit FeeDistributed(fee, _token, _messageId); + } + } + + /** + * @dev Internal function for sending an AMB message to the mediator on the other side. + * @param _data data to be sent to the other side of the bridge. + * @param _useOracleLane true, if the message should be sent to the oracle driven lane. + * @return id of the sent message. + */ + function _passMessage(bytes memory _data, bool _useOracleLane) internal override returns (bytes32) { + address executor = mediatorContractOnOtherSide(); + uint256 gasLimit = _chooseRequestGasLimit(_data); + IAMB bridge = bridgeContract(); + + return + _useOracleLane + ? bridge.requireToPassMessage(executor, _data, gasLimit) + : bridge.requireToConfirmMessage(executor, _data, gasLimit); + } + + /** + * @dev Internal function for getting minter proxy address. + * Returns the token address itself, expect for the case with bridged STAKE token. + * For bridged STAKE token, returns the hardcoded TokenMinter contract address. + * @param _token address of the token to mint. + * @return address of the minter contract that should be used for calling mint(address,uint256) + */ + function _getMinterFor(address _token) + internal + pure + override(BasicOmnibridge, OmnibridgeFeeManagerConnector) + returns (IBurnableMintableERC677Token) + { + // It is possible to hardcode different token minter contracts here during compile time. + // For example, the dedicated TokenMinter (0x857DD07866C1e19eb2CDFceF7aE655cE7f9E560d) is used for + // bridged STAKE token (0xb7D311E2Eb55F2f68a9440da38e7989210b9A05e). + // if (_token == address(0xb7D311E2Eb55F2f68a9440da38e7989210b9A05e)) { + // // hardcoded address of the TokenMinter address + // return IBurnableMintableERC677Token(0xb7D311E2Eb55F2f68a9440da38e7989210b9A05e); + // } + return IBurnableMintableERC677Token(_token); + } +} diff --git a/hardhat.config.js b/hardhat.config.js index 7978e67..28c27d3 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -2,21 +2,30 @@ require('@typechain/hardhat') require('@nomiclabs/hardhat-ethers') require('@nomiclabs/hardhat-waffle') -require('@eth-optimism/hardhat-ovm') require('dotenv').config() const config = { solidity: { - version: '0.7.6', - settings: { - optimizer: { - enabled: true, - runs: 200, + compilers: [ + { + version: '0.4.24', + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, }, - }, - }, - ovm: { - solcVersion: '0.7.6+commit.3b061308', + { + version: '0.7.6', + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + ], }, networks: { // goerli: { @@ -27,19 +36,6 @@ const config = { // mnemonic: 'test test test test test test test test test test test junk', // }, // }, - optimism: { - url: process.env.ETH_RPC || 'https://mainnet.optimism.io', - accounts: process.env.PRIVATE_KEY - ? [process.env.PRIVATE_KEY] - : { - mnemonic: 'test test test test test test test test test test test junk', - }, - // This sets the gas price to 0 for all transactions on L2. We do this - // because account balances are not automatically initiated with an ETH - // balance (yet, sorry!). - gasPrice: 15000000, - ovm: true, // This sets the network as using the ovm and ensure contract will be compiled against that. - }, }, mocha: { timeout: 600000000, diff --git a/package.json b/package.json index 7d84049..aac14eb 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,6 @@ "typechain": "^5.1.2" }, "devDependencies": { - "@eth-optimism/hardhat-ovm": "^0.2.2", "babel-eslint": "^10.1.0", "eslint": "^7.28.0", "eslint-config-prettier": "^8.3.0", diff --git a/src/index.js b/src/index.js index b2523f1..41c69e7 100644 --- a/src/index.js +++ b/src/index.js @@ -16,7 +16,7 @@ async function buildMerkleTree({ tornadoPool }) { return new MerkleTree(MERKLE_TREE_HEIGHT, leaves, { hashFunction: poseidonHash2 }) } -async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, relayer }) { +async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, relayer, isL1Withdrawal }) { inputs = shuffle(inputs) outputs = shuffle(outputs) @@ -53,6 +53,7 @@ async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, rela fee: toFixedHex(fee), encryptedOutput1: outputs[0].encrypt(), encryptedOutput2: outputs[1].encrypt(), + isL1Withdrawal, } const extDataHash = getExtDataHash(extData) @@ -103,6 +104,7 @@ async function prepareTransaction({ fee = 0, recipient = 0, relayer = 0, + isL1Withdrawal = false, }) { if (inputs.length > 16 || outputs.length > 2) { throw new Error('Incorrect inputs/outputs count') @@ -118,8 +120,6 @@ async function prepareTransaction({ .add(outputs.reduce((sum, x) => sum.add(x.amount), BigNumber.from(0))) .sub(inputs.reduce((sum, x) => sum.add(x.amount), BigNumber.from(0))) - const amount = extAmount > 0 ? extAmount : 0 - const { args, extData } = await getProof({ inputs, outputs, @@ -128,30 +128,29 @@ async function prepareTransaction({ fee, recipient, relayer, + isL1Withdrawal, }) return { args, extData, - amount, } } async function transaction({ tornadoPool, ...rest }) { - const { args, extData, amount } = await prepareTransaction({ + const { args, extData } = await prepareTransaction({ tornadoPool, ...rest, }) - const receipt = await tornadoPool.transaction(args, extData, { - value: amount, + const receipt = await tornadoPool.transact(args, extData, { gasLimit: 1e6, }) await receipt.wait() } async function registerAndTransact({ tornadoPool, packedPrivateKeyData, poolAddress, ...rest }) { - const { args, extData, amount } = await prepareTransaction({ + const { args, extData } = await prepareTransaction({ tornadoPool, ...rest, }) @@ -162,7 +161,6 @@ async function registerAndTransact({ tornadoPool, packedPrivateKeyData, poolAddr } const receipt = await tornadoPool.registerAndTransact(params, args, extData, { - value: amount, gasLimit: 2e6, }) await receipt.wait() diff --git a/src/utils.js b/src/utils.js index c9ac1fe..422622f 100644 --- a/src/utils.js +++ b/src/utils.js @@ -14,12 +14,20 @@ const FIELD_SIZE = BigNumber.from( /** Generate random number of specified byte length */ const randomBN = (nbytes = 31) => BigNumber.from(crypto.randomBytes(nbytes)) -function getExtDataHash({ recipient, extAmount, relayer, fee, encryptedOutput1, encryptedOutput2 }) { +function getExtDataHash({ + recipient, + extAmount, + relayer, + fee, + encryptedOutput1, + encryptedOutput2, + isL1Withdrawal, +}) { const abi = new ethers.utils.AbiCoder() const encodedData = abi.encode( [ - 'tuple(address recipient,int256 extAmount,address relayer,uint256 fee,bytes encryptedOutput1,bytes encryptedOutput2)', + 'tuple(address recipient,int256 extAmount,address relayer,uint256 fee,bytes encryptedOutput1,bytes encryptedOutput2,bool isL1Withdrawal)', ], [ { @@ -29,6 +37,7 @@ function getExtDataHash({ recipient, extAmount, relayer, fee, encryptedOutput1, fee: toFixedHex(fee), encryptedOutput1: encryptedOutput1, encryptedOutput2: encryptedOutput2, + isL1Withdrawal: isL1Withdrawal, }, ], ) diff --git a/test/full.test.js b/test/full.test.js index cfe82bd..5b90fa6 100644 --- a/test/full.test.js +++ b/test/full.test.js @@ -2,8 +2,8 @@ const hre = require('hardhat') const { ethers, waffle } = hre const { loadFixture } = waffle const { expect } = require('chai') +const { utils } = ethers -const { toFixedHex } = require('../src/utils') const Utxo = require('../src/utxo') const { transaction, registerAndTransact } = require('../src/index') const { Keypair } = require('../src/keypair') @@ -21,9 +21,17 @@ describe('TornadoPool', function () { async function fixture() { require('../scripts/compileHasher') + const [sender, gov] = await ethers.getSigners() const verifier2 = await deploy('Verifier2') const verifier16 = await deploy('Verifier16') const hasher = await deploy('Hasher') + + const token = await deploy('PermittableToken', 'Wrapped ETH', 'WETH', 18, 1) + await token.mint(sender.address, utils.parseEther('10000')) + + const amb = await deploy('MockAMB', gov.address) + const omniBridge = await deploy('MockOmniBridge', amb.address) + /** @type {TornadoPool} */ const tornadoPool = await deploy( 'TornadoPool', @@ -31,36 +39,39 @@ describe('TornadoPool', function () { verifier16.address, MERKLE_TREE_HEIGHT, hasher.address, + token.address, + omniBridge.address, ) await tornadoPool.initialize() - return { tornadoPool } + + await token.approve(tornadoPool.address, utils.parseEther('10000')) + return { tornadoPool, token, omniBridge, amb } } async function fixtureUpgradeable() { - const { tornadoPool } = await loadFixture(fixture) + const { tornadoPool, omniBridge } = await loadFixture(fixture) const [, gov] = await ethers.getSigners() - const messenger = await deploy('MockOVM_CrossDomainMessenger', gov.address) const proxy = await deploy( 'CrossChainUpgradeableProxy', tornadoPool.address, gov.address, [], - messenger.address, + omniBridge.address, ) - const TornadoPool = await ethers.getContractFactory('TornadoPool') /** @type {TornadoPool} */ + const TornadoPool = await ethers.getContractFactory('TornadoPool') const tornadoPoolProxied = TornadoPool.attach(proxy.address) await tornadoPoolProxied.initialize() - return { tornadoPool: tornadoPoolProxied, proxy, gov, messenger } + return { tornadoPool: tornadoPoolProxied, proxy, gov, omniBridge } } describe('Upgradeability tests', () => { it('admin should be gov', async () => { - const { proxy, messenger, gov } = await loadFixture(fixtureUpgradeable) + const { proxy, omniBridge, gov } = await loadFixture(fixtureUpgradeable) const { data } = await proxy.populateTransaction.admin() - const { result } = await messenger.callStatic.execute(proxy.address, data) + const { result } = await omniBridge.callStatic.execute(proxy.address, data) expect('0x' + result.slice(26)).to.be.equal(gov.address.toLowerCase()) }) @@ -152,7 +163,7 @@ describe('TornadoPool', function () { }) it('should deposit, transact and withdraw', async function () { - const { tornadoPool } = await loadFixture(fixture) + const { tornadoPool, token } = await loadFixture(fixture) // Alice deposits into tornado pool const aliceDepositAmount = 1e7 @@ -196,7 +207,7 @@ describe('TornadoPool', function () { recipient: bobEthAddress, }) - const bobBalance = await ethers.provider.getBalance(bobEthAddress) + const bobBalance = await token.balanceOf(bobEthAddress) expect(bobBalance).to.be.equal(bobWithdrawAmount) }) diff --git a/yarn.lock b/yarn.lock index 663d722..30abbc6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -137,13 +137,6 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@eth-optimism/hardhat-ovm@^0.2.2": - version "0.2.2" - resolved "https://registry.yarnpkg.com/@eth-optimism/hardhat-ovm/-/hardhat-ovm-0.2.2.tgz#55fafaa6b8277447abaf132602c1c6d14a2a18a2" - integrity sha512-QLzqawYCzC/m6K/Oaj/tCZQlu6kZTgnleg1cJad8kVYA5E+JWZQ6ZJrcStoJoJrco9RIroPUjAFEhFM8YiCc7Q== - dependencies: - node-fetch "^2.6.1" - "@ethereum-waffle/chai@^3.4.0": version "3.4.0" resolved "https://registry.yarnpkg.com/@ethereum-waffle/chai/-/chai-3.4.0.tgz#2477877410a96bf370edd64df905b04fb9aba9d5"