├── .husky ├── .gitignore └── pre-commit ├── .npmignore ├── contracts ├── v0.5 │ ├── references │ │ ├── ERC20 │ │ │ ├── IERC20Mintable.sol │ │ │ ├── IERC20Detailed.sol │ │ │ ├── ERC20Detailed.sol │ │ │ ├── ERC20Mintable.sol │ │ │ ├── IERC20.sol │ │ │ └── ERC20.sol │ │ ├── ERC721 │ │ │ ├── IERC721Mintable.sol │ │ │ └── IERC721.sol │ │ ├── ProxyStorage.sol │ │ ├── AddressUtils.sol │ │ ├── Pausable.sol │ │ ├── HasAdmin.sol │ │ ├── SafeMath.sol │ │ ├── HasOperators.sol │ │ ├── HasMinters.sol │ │ ├── ECVerify.sol │ │ └── Proxy.sol │ └── chain │ │ ├── sidechain │ │ ├── RoninWETH.sol │ │ ├── SidechainGatewayProxy.sol │ │ ├── WhitelistDeployer.sol │ │ ├── Blacklist.sol │ │ ├── SidechainValidator.sol │ │ ├── SidechainGatewayStorage.sol │ │ ├── Acknowledgement.sol │ │ └── SidechainGatewayManager.sol │ │ ├── mainchain │ │ ├── MainchainGatewayProxy.sol │ │ ├── PausableAdmin.sol │ │ ├── WETH.sol │ │ ├── MainchainValidator.sol │ │ ├── MainchainGatewayStorage.sol │ │ └── MainchainGatewayManager.sol │ │ └── common │ │ ├── IValidator.sol │ │ ├── Validator.sol │ │ └── Registry.sol └── mocks │ └── MockERC721.sol ├── tsconfig.json ├── .gitignore ├── .prettierrc.json ├── tsconfig.build.json ├── package.json ├── test └── chain │ ├── common │ └── Registry.test.ts │ ├── mainchain │ └── MainchainGateway.test.ts │ └── sidechain │ └── SidechainGateway.test.ts ├── src └── utils.ts ├── hardhat.config.ts ├── README.md └── bug-bounty.md /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .npmrc 3 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | set -ex 5 | 6 | yarn lint-staged 7 | yarn compile 8 | -------------------------------------------------------------------------------- /contracts/v0.5/references/ERC20/IERC20Mintable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.2; 3 | 4 | interface IERC20Mintable { 5 | function mint(address _to, uint256 _value) external returns (bool _success); 6 | } 7 | -------------------------------------------------------------------------------- /contracts/v0.5/references/ERC721/IERC721Mintable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.2; 3 | 4 | interface IERC721Mintable { 5 | function mint(address _to, uint256 _tokenId) external returns (bool); 6 | 7 | function mintNew(address _to) external returns (uint256 _tokenId); 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "outDir": "dist", 8 | "composite": true, 9 | "skipLibCheck": true 10 | }, 11 | "include": ["./src", "./test"], 12 | "files": ["./hardhat.config.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /contracts/v0.5/references/ProxyStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.2; 3 | 4 | import "./HasAdmin.sol"; 5 | 6 | /** 7 | * @title ProxyStorage 8 | * @dev Store the address of logic contact that the proxy should forward to. 9 | */ 10 | contract ProxyStorage is HasAdmin { 11 | address internal _proxyTo; 12 | } 13 | -------------------------------------------------------------------------------- /contracts/v0.5/chain/sidechain/RoninWETH.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.17; 3 | 4 | import "../../references/ERC20/ERC20Detailed.sol"; 5 | import "../../references/ERC20/ERC20Mintable.sol"; 6 | 7 | contract RoninWETH is ERC20Detailed, ERC20Mintable { 8 | constructor() public ERC20Detailed("Ronin Wrapped Ether", "WETH", 18) {} 9 | } 10 | -------------------------------------------------------------------------------- /contracts/v0.5/references/ERC20/IERC20Detailed.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.2; 3 | 4 | interface IERC20Detailed { 5 | function name() external view returns (string memory _name); 6 | 7 | function symbol() external view returns (string memory _symbol); 8 | 9 | function decimals() external view returns (uint8 _decimals); 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS cache files 2 | .DS_Store 3 | 4 | # Dependencies 5 | node_modules/ 6 | 7 | # Build directories 8 | dist/ 9 | tmp/ 10 | .next/ 11 | .nuxt/ 12 | 13 | # To maintain directory structure 14 | !.keep 15 | 16 | packages/document-parser/credentials/access_token.json 17 | 18 | # Error files 19 | yarn-error.log 20 | 21 | artifacts/ 22 | .npmrc 23 | .idea 24 | cache/ 25 | src/types/ 26 | yarn-error.log 27 | .env 28 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "bracketSpacing": true, 6 | "explicitTypes": "always", 7 | "overrides": [ 8 | { 9 | "files": "*.sol", 10 | "options": { 11 | "singleQuote": false 12 | } 13 | }, 14 | { 15 | "files": "*.{js,ts}", 16 | "options": { 17 | "singleQuote": true 18 | } 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /contracts/v0.5/chain/mainchain/MainchainGatewayProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.17; 3 | 4 | import "../../references/Proxy.sol"; 5 | import "../common/Validator.sol"; 6 | import "../common/Registry.sol"; 7 | import "./MainchainGatewayStorage.sol"; 8 | 9 | contract MainchainGatewayProxy is Proxy, MainchainGatewayStorage { 10 | constructor(address _proxyTo, address _registry) public Proxy(_proxyTo) { 11 | registry = Registry(_registry); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "downlevelIteration": true, 5 | "lib": ["dom", "es2015"], 6 | "module": "commonjs", 7 | "noImplicitReturns": true, 8 | "strict": true, 9 | "target": "es5", 10 | "outDir": "dist", 11 | "rootDir": ".", 12 | "composite": true, 13 | "declaration": true, 14 | "declarationMap": true, 15 | "sourceMap": true, 16 | "skipLibCheck": true 17 | }, 18 | "include": ["./src/types"], 19 | "files": [] 20 | } 21 | -------------------------------------------------------------------------------- /contracts/v0.5/references/ERC20/ERC20Detailed.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.2; 3 | 4 | import "./ERC20.sol"; 5 | import "./IERC20Detailed.sol"; 6 | 7 | contract ERC20Detailed is ERC20, IERC20Detailed { 8 | string public name; 9 | string public symbol; 10 | uint8 public decimals; 11 | 12 | constructor( 13 | string memory _name, 14 | string memory _symbol, 15 | uint8 _decimals 16 | ) public { 17 | name = _name; 18 | symbol = _symbol; 19 | decimals = _decimals; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /contracts/v0.5/chain/mainchain/PausableAdmin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.17; 3 | 4 | import "../../references/HasAdmin.sol"; 5 | import "../../references/Pausable.sol"; 6 | 7 | contract PausableAdmin is HasAdmin { 8 | Pausable public gateway; 9 | 10 | constructor(Pausable _gateway) public { 11 | gateway = _gateway; 12 | } 13 | 14 | function pauseGateway() external onlyAdmin { 15 | gateway.pause(); 16 | } 17 | 18 | function unpauseGateway() external onlyAdmin { 19 | gateway.unpause(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /contracts/v0.5/references/AddressUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.2; 3 | 4 | library AddressUtils { 5 | function toPayable(address _address) internal pure returns (address payable _payable) { 6 | return address(uint160(_address)); 7 | } 8 | 9 | function isContract(address _address) internal view returns (bool _correct) { 10 | uint256 _size; 11 | // solium-disable-next-line security/no-inline-assembly 12 | assembly { 13 | _size := extcodesize(_address) 14 | } 15 | return _size > 0; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /contracts/v0.5/chain/sidechain/SidechainGatewayProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.17; 3 | 4 | import "../../references/Proxy.sol"; 5 | import "../common/Validator.sol"; 6 | import "../common/Registry.sol"; 7 | import "./SidechainGatewayStorage.sol"; 8 | 9 | contract SidechainGatewayProxy is Proxy, SidechainGatewayStorage { 10 | constructor( 11 | address _proxyTo, 12 | address _registry, 13 | uint256 _maxPendingWithdrawal 14 | ) public Proxy(_proxyTo) { 15 | registry = Registry(_registry); 16 | maxPendingWithdrawal = _maxPendingWithdrawal; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contracts/v0.5/references/ERC20/ERC20Mintable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.2; 3 | 4 | import "../HasMinters.sol"; 5 | import "./ERC20.sol"; 6 | 7 | contract ERC20Mintable is HasMinters, ERC20 { 8 | function mint(address _to, uint256 _value) public onlyMinter returns (bool _success) { 9 | return _mint(_to, _value); 10 | } 11 | 12 | function _mint(address _to, uint256 _value) internal returns (bool success) { 13 | totalSupply = totalSupply.add(_value); 14 | balanceOf[_to] = balanceOf[_to].add(_value); 15 | emit Transfer(address(0), _to, _value); 16 | return true; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contracts/v0.5/references/Pausable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.2; 3 | 4 | import "./HasAdmin.sol"; 5 | 6 | contract Pausable is HasAdmin { 7 | event Paused(); 8 | event Unpaused(); 9 | 10 | bool public paused; 11 | 12 | modifier whenNotPaused() { 13 | require(!paused); 14 | _; 15 | } 16 | 17 | modifier whenPaused() { 18 | require(paused); 19 | _; 20 | } 21 | 22 | function pause() public onlyAdmin whenNotPaused { 23 | paused = true; 24 | emit Paused(); 25 | } 26 | 27 | function unpause() public onlyAdmin whenPaused { 28 | paused = false; 29 | emit Unpaused(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /contracts/mocks/MockERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol"; 5 | 6 | contract MockERC721 is ERC721PresetMinterPauserAutoId { 7 | constructor( 8 | string memory name, 9 | string memory symbol, 10 | string memory baseTokenURI 11 | ) ERC721PresetMinterPauserAutoId(name, symbol, baseTokenURI) {} 12 | 13 | function mint(address _to, uint256 _id) public virtual returns (bool) { 14 | require(hasRole(MINTER_ROLE, _msgSender()), "MockERC721: must have minter role to mint"); 15 | _mint(_to, _id); 16 | return true; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contracts/v0.5/chain/common/IValidator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.17; 3 | 4 | contract IValidator { 5 | event ValidatorAdded(uint256 indexed _id, address indexed _validator); 6 | event ValidatorRemoved(uint256 indexed _id, address indexed _validator); 7 | event ThresholdUpdated( 8 | uint256 indexed _id, 9 | uint256 indexed _numerator, 10 | uint256 indexed _denominator, 11 | uint256 _previousNumerator, 12 | uint256 _previousDenominator 13 | ); 14 | 15 | function isValidator(address _addr) public view returns (bool); 16 | 17 | function getValidators() public view returns (address[] memory _validators); 18 | 19 | function checkThreshold(uint256 _voteCount) public view returns (bool); 20 | } 21 | -------------------------------------------------------------------------------- /contracts/v0.5/references/HasAdmin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.2; 3 | 4 | contract HasAdmin { 5 | event AdminChanged(address indexed _oldAdmin, address indexed _newAdmin); 6 | event AdminRemoved(address indexed _oldAdmin); 7 | 8 | address public admin; 9 | 10 | modifier onlyAdmin() { 11 | require(msg.sender == admin); 12 | _; 13 | } 14 | 15 | constructor() internal { 16 | admin = msg.sender; 17 | emit AdminChanged(address(0), admin); 18 | } 19 | 20 | function changeAdmin(address _newAdmin) external onlyAdmin { 21 | require(_newAdmin != address(0)); 22 | emit AdminChanged(admin, _newAdmin); 23 | admin = _newAdmin; 24 | } 25 | 26 | function removeAdmin() external onlyAdmin { 27 | emit AdminRemoved(admin); 28 | admin = address(0); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /contracts/v0.5/chain/mainchain/WETH.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.17; 3 | 4 | import "../../references/ERC20/ERC20Detailed.sol"; 5 | import "../../references/ERC20/ERC20Mintable.sol"; 6 | 7 | contract WETH is ERC20Detailed { 8 | event Deposit(address _sender, uint256 _value); 9 | 10 | event Withdrawal(address _sender, uint256 _value); 11 | 12 | constructor() public ERC20Detailed("Wrapped Ether", "WETH", 18) {} 13 | 14 | function deposit() external payable { 15 | balanceOf[msg.sender] += msg.value; 16 | 17 | emit Deposit(msg.sender, msg.value); 18 | } 19 | 20 | function withdraw(uint256 _wad) external { 21 | require(balanceOf[msg.sender] >= _wad); 22 | balanceOf[msg.sender] -= _wad; 23 | msg.sender.transfer(_wad); 24 | 25 | emit Withdrawal(msg.sender, _wad); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /contracts/v0.5/references/ERC20/IERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.2; 3 | 4 | interface IERC20 { 5 | event Transfer(address indexed _from, address indexed _to, uint256 _value); 6 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); 7 | 8 | function totalSupply() external view returns (uint256 _supply); 9 | 10 | function balanceOf(address _owner) external view returns (uint256 _balance); 11 | 12 | function approve(address _spender, uint256 _value) external returns (bool _success); 13 | 14 | function allowance(address _owner, address _spender) external view returns (uint256 _value); 15 | 16 | function transfer(address _to, uint256 _value) external returns (bool _success); 17 | 18 | function transferFrom( 19 | address _from, 20 | address _to, 21 | uint256 _value 22 | ) external returns (bool _success); 23 | } 24 | -------------------------------------------------------------------------------- /contracts/v0.5/chain/sidechain/WhitelistDeployer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.17; 3 | 4 | import "../../references/HasAdmin.sol"; 5 | 6 | contract WhitelistDeployer is HasAdmin { 7 | event AddressWhitelisted(address indexed _address, bool indexed _status); 8 | event WhitelistAllChange(bool indexed _status); 9 | 10 | mapping(address => bool) public whitelisted; 11 | bool public whitelistAll; 12 | 13 | constructor() public {} 14 | 15 | function whitelist(address _address, bool _status) external onlyAdmin { 16 | whitelisted[_address] = _status; 17 | emit AddressWhitelisted(_address, _status); 18 | } 19 | 20 | function whitelistAllAddresses(bool _status) external onlyAdmin { 21 | whitelistAll = _status; 22 | emit WhitelistAllChange(_status); 23 | } 24 | 25 | function isWhitelisted(address _address) external view returns (bool) { 26 | return whitelistAll || whitelisted[_address]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /contracts/v0.5/chain/mainchain/MainchainValidator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.17; 3 | 4 | import "../../references/HasAdmin.sol"; 5 | import "../common/Validator.sol"; 6 | 7 | /** 8 | * @title Validator 9 | * @dev Simple validator contract 10 | */ 11 | contract MainchainValidator is Validator, HasAdmin { 12 | uint256 nonce; 13 | 14 | constructor( 15 | address[] memory _validators, 16 | uint256 _num, 17 | uint256 _denom 18 | ) public Validator(_validators, _num, _denom) {} 19 | 20 | function addValidators(address[] calldata _validators) external onlyAdmin { 21 | for (uint256 _i; _i < _validators.length; ++_i) { 22 | _addValidator(nonce++, _validators[_i]); 23 | } 24 | } 25 | 26 | function removeValidator(address _validator) external onlyAdmin { 27 | _removeValidator(nonce++, _validator); 28 | } 29 | 30 | function updateQuorum(uint256 _numerator, uint256 _denominator) external onlyAdmin { 31 | _updateQuorum(nonce++, _numerator, _denominator); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/v0.5/chain/sidechain/Blacklist.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.17; 3 | 4 | import "../../references/HasAdmin.sol"; 5 | 6 | contract Blacklist is HasAdmin { 7 | event ContractDisabled(bool indexed _status); 8 | 9 | event AddressesBlacklisted(address[] _addresses, bool indexed _status); 10 | 11 | mapping(address => bool) internal _blacklisted; 12 | 13 | // Returns whether the contract is still valid or not 14 | bool public disabled; 15 | 16 | constructor() public {} 17 | 18 | function blacklists(address[] calldata _addresses, bool _status) external onlyAdmin { 19 | address _addr; 20 | for (uint256 _i; _i < _addresses.length; _i++) { 21 | _addr = _addresses[_i]; 22 | _blacklisted[_addr] = _status; 23 | assert(_addr != address(this) && _addr != admin); // cannot blacklist this contract or admin 24 | } 25 | emit AddressesBlacklisted(_addresses, _status); 26 | } 27 | 28 | function setDisabled(bool _status) external onlyAdmin { 29 | disabled = _status; 30 | emit ContractDisabled(_status); 31 | } 32 | 33 | function blacklisted(address _address) external view returns (bool) { 34 | return !disabled && _blacklisted[_address]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /contracts/v0.5/references/ERC721/IERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.2; 3 | 4 | interface IERC721 { 5 | event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); 6 | event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); 7 | event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); 8 | 9 | function balanceOf(address _owner) external view returns (uint256 _balance); 10 | 11 | function ownerOf(uint256 _tokenId) external view returns (address _owner); 12 | 13 | function approve(address _to, uint256 _tokenId) external; 14 | 15 | function getApproved(uint256 _tokenId) external view returns (address _operator); 16 | 17 | function setApprovalForAll(address _operator, bool _approved) external; 18 | 19 | function isApprovedForAll(address _owner, address _operator) external view returns (bool _approved); 20 | 21 | function transferFrom( 22 | address _from, 23 | address _to, 24 | uint256 _tokenId 25 | ) external; 26 | 27 | function safeTransferFrom( 28 | address _from, 29 | address _to, 30 | uint256 _tokenId 31 | ) external; 32 | 33 | function safeTransferFrom( 34 | address _from, 35 | address _to, 36 | uint256 _tokenId, 37 | bytes calldata _data 38 | ) external; 39 | } 40 | -------------------------------------------------------------------------------- /contracts/v0.5/references/ERC20/ERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.2; 3 | 4 | import "../SafeMath.sol"; 5 | import "./IERC20.sol"; 6 | 7 | contract ERC20 is IERC20 { 8 | using SafeMath for uint256; 9 | 10 | uint256 public totalSupply; 11 | mapping(address => uint256) public balanceOf; 12 | mapping(address => mapping(address => uint256)) public allowance; 13 | 14 | function approve(address _spender, uint256 _value) public returns (bool _success) { 15 | allowance[msg.sender][_spender] = _value; 16 | emit Approval(msg.sender, _spender, _value); 17 | return true; 18 | } 19 | 20 | function transfer(address _to, uint256 _value) public returns (bool _success) { 21 | require(_to != address(0)); 22 | balanceOf[msg.sender] = balanceOf[msg.sender].sub(_value); 23 | balanceOf[_to] = balanceOf[_to].add(_value); 24 | emit Transfer(msg.sender, _to, _value); 25 | return true; 26 | } 27 | 28 | function transferFrom( 29 | address _from, 30 | address _to, 31 | uint256 _value 32 | ) public returns (bool _success) { 33 | require(_to != address(0)); 34 | balanceOf[_from] = balanceOf[_from].sub(_value); 35 | balanceOf[_to] = balanceOf[_to].add(_value); 36 | allowance[_from][msg.sender] = allowance[_from][msg.sender].sub(_value); 37 | emit Transfer(_from, _to, _value); 38 | return true; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contracts/v0.5/references/SafeMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.2; 3 | 4 | library SafeMath { 5 | function add(uint256 a, uint256 b) internal pure returns (uint256 c) { 6 | c = a + b; 7 | require(c >= a); 8 | } 9 | 10 | function sub(uint256 a, uint256 b) internal pure returns (uint256 c) { 11 | require(b <= a); 12 | return a - b; 13 | } 14 | 15 | function mul(uint256 a, uint256 b) internal pure returns (uint256 c) { 16 | if (a == 0) { 17 | return 0; 18 | } 19 | 20 | c = a * b; 21 | require(c / a == b); 22 | } 23 | 24 | function div(uint256 a, uint256 b) internal pure returns (uint256 c) { 25 | // Since Solidity automatically asserts when dividing by 0, 26 | // but we only need it to revert. 27 | require(b > 0); 28 | return a / b; 29 | } 30 | 31 | function mod(uint256 a, uint256 b) internal pure returns (uint256 c) { 32 | // Same reason as `div`. 33 | require(b > 0); 34 | return a % b; 35 | } 36 | 37 | function ceilingDiv(uint256 a, uint256 b) internal pure returns (uint256 c) { 38 | return add(div(a, b), mod(a, b) > 0 ? 1 : 0); 39 | } 40 | 41 | function subU64(uint64 a, uint64 b) internal pure returns (uint64 c) { 42 | require(b <= a); 43 | return a - b; 44 | } 45 | 46 | function addU8(uint8 a, uint8 b) internal pure returns (uint8 c) { 47 | c = a + b; 48 | require(c >= a); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /contracts/v0.5/chain/mainchain/MainchainGatewayStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.17; 3 | 4 | import "../../references/ProxyStorage.sol"; 5 | import "../../references/Pausable.sol"; 6 | import "../common/Validator.sol"; 7 | import "../common/Registry.sol"; 8 | import "./MainchainValidator.sol"; 9 | 10 | /** 11 | * @title GatewayStorage 12 | * @dev Storage of deposit and withdraw information. 13 | */ 14 | contract MainchainGatewayStorage is ProxyStorage, Pausable { 15 | event TokenDeposited( 16 | uint256 indexed _depositId, 17 | address indexed _owner, 18 | address indexed _tokenAddress, 19 | address _sidechainAddress, 20 | uint32 _standard, 21 | uint256 _tokenNumber // ERC-20 amount or ERC721 tokenId 22 | ); 23 | 24 | event TokenWithdrew( 25 | uint256 indexed _withdrawId, 26 | address indexed _owner, 27 | address indexed _tokenAddress, 28 | uint256 _tokenNumber 29 | ); 30 | 31 | struct DepositEntry { 32 | address owner; 33 | address tokenAddress; 34 | address sidechainAddress; 35 | uint32 standard; 36 | uint256 tokenNumber; 37 | } 38 | 39 | struct WithdrawalEntry { 40 | address owner; 41 | address tokenAddress; 42 | uint256 tokenNumber; 43 | } 44 | 45 | Registry public registry; 46 | 47 | uint256 public depositCount; 48 | DepositEntry[] public deposits; 49 | mapping(uint256 => WithdrawalEntry) public withdrawals; 50 | 51 | function updateRegistry(address _registry) external onlyAdmin { 52 | registry = Registry(_registry); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /contracts/v0.5/references/HasOperators.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.2; 3 | 4 | import "./HasAdmin.sol"; 5 | 6 | contract HasOperators is HasAdmin { 7 | event OperatorAdded(address indexed _operator); 8 | event OperatorRemoved(address indexed _operator); 9 | 10 | address[] public operators; 11 | mapping(address => bool) public operator; 12 | 13 | modifier onlyOperator() { 14 | require(operator[msg.sender]); 15 | _; 16 | } 17 | 18 | function addOperators(address[] memory _addedOperators) public onlyAdmin { 19 | address _operator; 20 | 21 | for (uint256 i = 0; i < _addedOperators.length; i++) { 22 | _operator = _addedOperators[i]; 23 | 24 | if (!operator[_operator]) { 25 | operators.push(_operator); 26 | operator[_operator] = true; 27 | emit OperatorAdded(_operator); 28 | } 29 | } 30 | } 31 | 32 | function removeOperators(address[] memory _removedOperators) public onlyAdmin { 33 | address _operator; 34 | 35 | for (uint256 i = 0; i < _removedOperators.length; i++) { 36 | _operator = _removedOperators[i]; 37 | 38 | if (operator[_operator]) { 39 | operator[_operator] = false; 40 | emit OperatorRemoved(_operator); 41 | } 42 | } 43 | 44 | uint256 i = 0; 45 | 46 | while (i < operators.length) { 47 | _operator = operators[i]; 48 | 49 | if (!operator[_operator]) { 50 | operators[i] = operators[operators.length - 1]; 51 | delete operators[operators.length - 1]; 52 | operators.length--; 53 | } else { 54 | i++; 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /contracts/v0.5/references/HasMinters.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.2; 3 | 4 | import "./HasAdmin.sol"; 5 | 6 | contract HasMinters is HasAdmin { 7 | event MinterAdded(address indexed _minter); 8 | event MinterRemoved(address indexed _minter); 9 | 10 | address[] public minters; 11 | mapping(address => bool) public minter; 12 | 13 | modifier onlyMinter() { 14 | require(minter[msg.sender]); 15 | _; 16 | } 17 | 18 | function addMinters(address[] memory _addedMinters) public onlyAdmin { 19 | address _minter; 20 | 21 | for (uint256 i = 0; i < _addedMinters.length; i++) { 22 | _minter = _addedMinters[i]; 23 | 24 | if (!minter[_minter]) { 25 | minters.push(_minter); 26 | minter[_minter] = true; 27 | emit MinterAdded(_minter); 28 | } 29 | } 30 | } 31 | 32 | function removeMinters(address[] memory _removedMinters) public onlyAdmin { 33 | address _minter; 34 | 35 | for (uint256 i = 0; i < _removedMinters.length; i++) { 36 | _minter = _removedMinters[i]; 37 | 38 | if (minter[_minter]) { 39 | minter[_minter] = false; 40 | emit MinterRemoved(_minter); 41 | } 42 | } 43 | 44 | uint256 i = 0; 45 | 46 | while (i < minters.length) { 47 | _minter = minters[i]; 48 | 49 | if (!minter[_minter]) { 50 | minters[i] = minters[minters.length - 1]; 51 | delete minters[minters.length - 1]; 52 | minters.length--; 53 | } else { 54 | i++; 55 | } 56 | } 57 | } 58 | 59 | function isMinter(address _addr) public view returns (bool) { 60 | return minter[_addr]; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /contracts/v0.5/references/ECVerify.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.2; 3 | 4 | library ECVerify { 5 | enum SignatureMode { 6 | EIP712, 7 | GETH, 8 | TREZOR 9 | } 10 | 11 | function recover(bytes32 _hash, bytes memory _signature) internal pure returns (address _signer) { 12 | return recover(_hash, _signature, 0); 13 | } 14 | 15 | // solium-disable-next-line security/no-assign-params 16 | function recover( 17 | bytes32 _hash, 18 | bytes memory _signature, 19 | uint256 _index 20 | ) internal pure returns (address _signer) { 21 | require(_signature.length >= _index + 66); 22 | 23 | SignatureMode _mode = SignatureMode(uint8(_signature[_index])); 24 | bytes32 _r; 25 | bytes32 _s; 26 | uint8 _v; 27 | 28 | // solium-disable-next-line security/no-inline-assembly 29 | assembly { 30 | _r := mload(add(_signature, add(_index, 33))) 31 | _s := mload(add(_signature, add(_index, 65))) 32 | _v := and(255, mload(add(_signature, add(_index, 66)))) 33 | } 34 | 35 | if (_v < 27) { 36 | _v += 27; 37 | } 38 | 39 | require(_v == 27 || _v == 28); 40 | 41 | if (_mode == SignatureMode.GETH) { 42 | _hash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _hash)); 43 | } else if (_mode == SignatureMode.TREZOR) { 44 | _hash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n\x20", _hash)); 45 | } 46 | 47 | return ecrecover(_hash, _v, _r, _s); 48 | } 49 | 50 | function ecverify( 51 | bytes32 _hash, 52 | bytes memory _signature, 53 | address _signer 54 | ) internal pure returns (bool _valid) { 55 | return _signer == recover(_hash, _signature); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@axie/ronin-smart-contracts", 3 | "version": "1.0.1", 4 | "description": "Axie Infinity gateway smart contracts.", 5 | "author": "Axie Infinity Engineering ", 6 | "scripts": { 7 | "compile": "hardhat compile", 8 | "test": "hardhat test", 9 | "clean": "hardhat clean && rimraf dist cache", 10 | "lint:fix": "lint-staged", 11 | "prepare": "husky install", 12 | "build": "hardhat compile && tsc -p tsconfig.build.json" 13 | }, 14 | "lint-staged": { 15 | "contracts/**/*.sol": "prettier --write", 16 | "{test,src}/!(types)/*.{js,ts}": "prettier --write", 17 | "hardhat.config.{js,ts}": "prettier --write" 18 | }, 19 | "main": "dist/src/index.js", 20 | "types": "dist/src/index.d.ts", 21 | "dependencies": { 22 | "@openzeppelin/contracts": "^4.4.0", 23 | "dotenv": "^10.0.0" 24 | }, 25 | "devDependencies": { 26 | "@nomiclabs/hardhat-ethers": "^2.0.3", 27 | "@nomiclabs/hardhat-waffle": "^2.0.1", 28 | "@typechain/ethers-v5": "^8.0.5", 29 | "@typechain/hardhat": "^3.0.0", 30 | "@types/chai": "^4.3.0", 31 | "@types/mocha": "^9.0.0", 32 | "@types/node": "^17.0.0", 33 | "chai": "^4.3.4", 34 | "ethereum-waffle": "^3.4.0", 35 | "ethers": "^5.5.2", 36 | "hardhat": "^2.7.1", 37 | "hardhat-deploy": "^0.9.14", 38 | "husky": "^7.0.4", 39 | "lint-staged": ">=10", 40 | "prettier": "^2.5.1", 41 | "prettier-plugin-solidity": "^1.0.0-beta.19", 42 | "rimraf": "^3.0.2", 43 | "solc-0.8": "npm:solc@^0.8.0", 44 | "solhint": "^3.3.6", 45 | "solidity-docgen": "0.5.16", 46 | "ts-node": "^10.4.0", 47 | "typechain": "^6.0.5", 48 | "typescript": "^4.5.4" 49 | }, 50 | "engines": { 51 | "node": ">=12" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/chain/common/Registry.test.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; 2 | import { expect } from 'chai'; 3 | import { ethers } from 'hardhat'; 4 | 5 | import { Registry, Registry__factory } from '../../../src/types'; 6 | 7 | describe('Registry', () => { 8 | let alice: SignerWithAddress; 9 | let bob: SignerWithAddress; 10 | let charles: SignerWithAddress; 11 | let registry: Registry; 12 | 13 | before(async () => { 14 | [alice, bob, charles] = await ethers.getSigners(); 15 | registry = await new Registry__factory(alice).deploy(); 16 | }); 17 | 18 | describe('set and read contract', async () => { 19 | it('should be able to set predefined constant contract', async () => { 20 | const wethToken = await registry.WETH_TOKEN(); 21 | await registry.updateContract(wethToken, bob.address); 22 | 23 | const addr = await registry.getContract(wethToken); 24 | expect(addr.toLowerCase()).eq(bob.address.toLowerCase()); 25 | }); 26 | 27 | it('should be able to set a random contract', async () => { 28 | const randomName = 'Some random contract name'; 29 | await registry.updateContract(randomName, charles.address); 30 | 31 | const addr = await registry.getContract(randomName); 32 | expect(addr.toLowerCase()).eq(charles.address.toLowerCase()); 33 | }); 34 | }); 35 | 36 | describe('set and read mapped token', async () => { 37 | it('should be able to map ERC20 token', async () => { 38 | await registry.mapToken(alice.address, bob.address, 20); 39 | const [mainchain, sidechain, version] = await registry.mainchainMap(alice.address); 40 | expect(mainchain.toLowerCase()).eq(alice.address.toLowerCase()); 41 | expect(sidechain.toLowerCase()).eq(bob.address.toLowerCase()); 42 | expect(version as any).eq(20); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 2 | import { BigNumberish } from "ethers"; 3 | import { arrayify } from "ethers/lib/utils"; 4 | import { ethers } from "hardhat"; 5 | 6 | export const ethToWei = (eth: number) => ethers.utils.parseEther(eth.toString()); 7 | 8 | export const withdrawalERC20Hash = (withdrawalId: number, user: string, token: string, amount: BigNumberish) => 9 | ethers.utils.solidityKeccak256( 10 | ['string', 'uint256', 'address', 'address', 'uint256'], 11 | ['withdrawERC20', withdrawalId, user, token, amount] 12 | ); 13 | 14 | export const withdrawalERC721Hash = (withdrawalId: number, user: string, token: string, id: BigNumberish) => 15 | ethers.utils.solidityKeccak256( 16 | ['string', 'uint256', 'address', 'address', 'uint256'], 17 | ['withdrawERC721', withdrawalId, user, token, id] 18 | ); 19 | 20 | 21 | const sign = async (signer: SignerWithAddress, data: string): Promise => { 22 | // Ganache return the signatures directly 23 | const signatures = await signer.signMessage(arrayify(data)); 24 | return `01${signatures.slice(2)}`; 25 | }; 26 | 27 | export const getCombinedSignatures = async ( 28 | reversed: boolean, 29 | accounts: SignerWithAddress[], 30 | data: string 31 | ): Promise<{ accounts: string[]; signatures: string }> => { 32 | const sortedAccounts = accounts.sort((a, b) => a.address.toLowerCase().localeCompare(b.address.toLowerCase())); 33 | 34 | let signatures = ''; 35 | for (const account of sortedAccounts) { 36 | const signature = await sign(account, data); 37 | if (reversed) { 38 | signatures = signature + signatures; 39 | } else { 40 | signatures += signature; 41 | } 42 | } 43 | 44 | signatures = '0x' + signatures; 45 | 46 | return { 47 | accounts: sortedAccounts.map((account) => account.address.toLowerCase()), 48 | signatures, 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import '@typechain/hardhat'; 2 | import '@nomiclabs/hardhat-waffle'; 3 | import '@nomiclabs/hardhat-ethers'; 4 | import 'hardhat-deploy'; 5 | 6 | import * as dotenv from 'dotenv'; 7 | import { HardhatUserConfig, NetworkUserConfig, SolcUserConfig } from 'hardhat/types'; 8 | 9 | dotenv.config(); 10 | 11 | const DEFAULT_MNEMONIC = 'title spike pink garlic hamster sorry few damage silver mushroom clever window'; 12 | 13 | const { TESTNET_PK, TESTNET_URL, MAINNET_PK, MAINNET_URL } = process.env; 14 | 15 | if (!TESTNET_PK) { 16 | console.warn('TESTNET_PK is unset. Using DEFAULT_MNEMONIC'); 17 | } 18 | 19 | if (!MAINNET_PK) { 20 | console.warn('MAINNET_PK is unset. Using DEFAULT_MNEMONIC'); 21 | } 22 | 23 | const testnet: NetworkUserConfig = { 24 | chainId: 2021, 25 | url: TESTNET_URL || 'https://testnet.skymavis.one/rpc', 26 | accounts: TESTNET_PK ? [TESTNET_PK] : { mnemonic: DEFAULT_MNEMONIC }, 27 | blockGasLimit: 100000000, 28 | }; 29 | 30 | const mainnet: NetworkUserConfig = { 31 | chainId: 2020, 32 | url: MAINNET_URL || 'https://api.roninchain.com/rpc', 33 | accounts: MAINNET_PK ? [MAINNET_PK] : { mnemonic: DEFAULT_MNEMONIC }, 34 | blockGasLimit: 100000000, 35 | }; 36 | 37 | const compilerConfig: SolcUserConfig = { 38 | version: '0.5.17', 39 | settings: { 40 | optimizer: { 41 | enabled: true, 42 | runs: 200, 43 | }, 44 | }, 45 | }; 46 | 47 | const config: HardhatUserConfig = { 48 | solidity: { 49 | compilers: [compilerConfig, { ...compilerConfig, version: '0.8.9' }], 50 | }, 51 | typechain: { 52 | outDir: 'src/types', 53 | }, 54 | paths: { 55 | deploy: 'src/deploy', 56 | }, 57 | namedAccounts: { 58 | deployer: 0, 59 | }, 60 | networks: { 61 | hardhat: { 62 | accounts: { 63 | mnemonic: DEFAULT_MNEMONIC, 64 | }, 65 | }, 66 | 'ronin-testnet': testnet, 67 | 'ronin-mainnet': mainnet, 68 | }, 69 | }; 70 | 71 | export default config; 72 | -------------------------------------------------------------------------------- /contracts/v0.5/references/Proxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.2; 3 | 4 | import "./ProxyStorage.sol"; 5 | 6 | /** 7 | * @title Proxy 8 | * @dev Gives the possibility to delegate any call to a foreign implementation. 9 | */ 10 | contract Proxy is ProxyStorage { 11 | event ProxyUpdated(address indexed _new, address indexed _old); 12 | 13 | constructor(address _proxyTo) public { 14 | updateProxyTo(_proxyTo); 15 | } 16 | 17 | /** 18 | * @dev Tells the address of the implementation where every call will be delegated. 19 | * @return address of the implementation to which it will be delegated 20 | */ 21 | function implementation() public view returns (address) { 22 | return _proxyTo; 23 | } 24 | 25 | /** 26 | * @dev See more at: https://eips.ethereum.org/EIPS/eip-897 27 | * @return type of proxy - always upgradable 28 | */ 29 | function proxyType() external pure returns (uint256) { 30 | // Upgradeable proxy 31 | return 2; 32 | } 33 | 34 | /** 35 | * @dev Fallback function allowing to perform a delegatecall to the given implementation. 36 | * This function will return whatever the implementation call returns 37 | */ 38 | function() external payable { 39 | address _impl = implementation(); 40 | require(_impl != address(0)); 41 | 42 | assembly { 43 | let ptr := mload(0x40) 44 | calldatacopy(ptr, 0, calldatasize) 45 | let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0) 46 | let size := returndatasize 47 | returndatacopy(ptr, 0, size) 48 | 49 | switch result 50 | case 0 { 51 | revert(ptr, size) 52 | } 53 | default { 54 | return(ptr, size) 55 | } 56 | } 57 | } 58 | 59 | function updateProxyTo(address _newProxyTo) public onlyAdmin { 60 | require(_newProxyTo != address(0x0)); 61 | 62 | _proxyTo = _newProxyTo; 63 | emit ProxyUpdated(_newProxyTo, _proxyTo); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##**Ronin-Smart-Contracts** 2 | 3 | The smart contracts that power Ronin. 4 | 5 | ## **Validators** 6 | 7 | Only validators can produce blocks on Ronin. Validators can also acknowledge deposit and withdrawal events to facilitate asset transfers. 8 | 9 | Each validator contract has a minimum threshold that must be reached when the state changes such as transfer of assets and addition/removal of validators. There is one validator contract on Ethereum and a corresponding validator contract on Ronin. An admin has the right to manage validators on Ethereum (in the future this will be upgraded to a multi-sig walllet). These changes are relayed to Ronin through the Bridge component. 10 | 11 | ## Ethereum Bridge 12 | 13 | When an event happens on Ethereum, the Bridge component in each validator node will pick it up and relay it to Ronin by sending a corresponding transaction. 14 | 15 | Depending on the action, these transactions will be relayed to *SideChainValidator* (changes in the validator list/updates to the consensus threshold) or *SidechainGatewayManager* (deposits, withdrawals). 16 | 17 | If there are enough acknowledgements (# of acknowledgement/# of validator >= ratio), the event will be confirmed on Ronin. 18 | 19 | ### **Adding Validators** 20 | 21 | An admin account will need to send a transaction to add the new validator to *MainchainValidator*. Next, the Bridge in each validator node will acknowledge and approve the new validator. After having enough acknowledgements, the new validator can start proposing blocks. 22 | 23 | ### **Deposits** 24 | 25 | Users can deposit ETH, ERC20, and ERC721 (NFTs) by sending transactions to *MainchainGatewayManager* and waiting for the deposit to be verified on Ronin. The gateway should have a mapping between token contracts on Ethereum and on Ronin before the deposit can take place. 26 | 27 | ### **Withdrawals** 28 | 29 | Similar to deposits, there should be token mapping between Ronin and Ethereum before users can withdraw. However, instead of sending a relay transaction on Ethereum, the validators simply provide a signature that the withdrawal event has taken place. The withdrawal then needs to collect enough signatures before it can be claimed by the user on Ethereum. 30 | -------------------------------------------------------------------------------- /contracts/v0.5/chain/sidechain/SidechainValidator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.17; 3 | 4 | import "../common/Validator.sol"; 5 | import "./Acknowledgement.sol"; 6 | 7 | /** 8 | * @title Validator 9 | * @dev Simple validator contract 10 | */ 11 | contract SidechainValidator is Validator { 12 | Acknowledgement public acknowledgement; 13 | 14 | modifier onlyValidator() { 15 | require(isValidator(msg.sender)); 16 | _; 17 | } 18 | 19 | constructor( 20 | address _acknowledgement, 21 | address[] memory _validators, 22 | uint256 _num, 23 | uint256 _denom 24 | ) public Validator(_validators, _num, _denom) { 25 | acknowledgement = Acknowledgement(_acknowledgement); 26 | } 27 | 28 | function addValidator(uint256 _id, address _validator) external onlyValidator { 29 | bytes32 _hash = keccak256(abi.encode("addValidator", _validator)); 30 | 31 | Acknowledgement.Status _status = acknowledgement.acknowledge(_getAckChannel(), _id, _hash, msg.sender); 32 | if (_status == Acknowledgement.Status.FirstApproved) { 33 | _addValidator(_id, _validator); 34 | } 35 | } 36 | 37 | function removeValidator(uint256 _id, address _validator) external onlyValidator { 38 | require(isValidator(_validator)); 39 | 40 | bytes32 _hash = keccak256(abi.encode("removeValidator", _validator)); 41 | 42 | Acknowledgement.Status _status = acknowledgement.acknowledge(_getAckChannel(), _id, _hash, msg.sender); 43 | if (_status == Acknowledgement.Status.FirstApproved) { 44 | _removeValidator(_id, _validator); 45 | } 46 | } 47 | 48 | function updateQuorum( 49 | uint256 _id, 50 | uint256 _numerator, 51 | uint256 _denominator 52 | ) external onlyValidator { 53 | bytes32 _hash = keccak256(abi.encode("updateQuorum", _numerator, _denominator)); 54 | 55 | Acknowledgement.Status _status = acknowledgement.acknowledge(_getAckChannel(), _id, _hash, msg.sender); 56 | if (_status == Acknowledgement.Status.FirstApproved) { 57 | _updateQuorum(_id, _numerator, _denominator); 58 | } 59 | } 60 | 61 | function _getAckChannel() internal view returns (string memory) { 62 | return acknowledgement.VALIDATOR_CHANNEL(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /contracts/v0.5/chain/common/Validator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.17; 3 | 4 | import "../../references/SafeMath.sol"; 5 | import "./IValidator.sol"; 6 | 7 | contract Validator is IValidator { 8 | using SafeMath for uint256; 9 | 10 | mapping(address => bool) validatorMap; 11 | address[] public validators; 12 | uint256 public validatorCount; 13 | 14 | uint256 public num; 15 | uint256 public denom; 16 | 17 | constructor( 18 | address[] memory _validators, 19 | uint256 _num, 20 | uint256 _denom 21 | ) public { 22 | validators = _validators; 23 | validatorCount = _validators.length; 24 | 25 | for (uint256 _i = 0; _i < validatorCount; _i++) { 26 | address _validator = _validators[_i]; 27 | validatorMap[_validator] = true; 28 | } 29 | 30 | num = _num; 31 | denom = _denom; 32 | } 33 | 34 | function isValidator(address _addr) public view returns (bool) { 35 | return validatorMap[_addr]; 36 | } 37 | 38 | function getValidators() public view returns (address[] memory _validators) { 39 | _validators = validators; 40 | } 41 | 42 | function checkThreshold(uint256 _voteCount) public view returns (bool) { 43 | return _voteCount.mul(denom) >= num.mul(validatorCount); 44 | } 45 | 46 | function _addValidator(uint256 _id, address _validator) internal { 47 | require(!validatorMap[_validator]); 48 | 49 | validators.push(_validator); 50 | validatorMap[_validator] = true; 51 | validatorCount++; 52 | 53 | emit ValidatorAdded(_id, _validator); 54 | } 55 | 56 | function _removeValidator(uint256 _id, address _validator) internal { 57 | require(isValidator(_validator)); 58 | 59 | uint256 _index; 60 | for (uint256 _i = 0; _i < validatorCount; _i++) { 61 | if (validators[_i] == _validator) { 62 | _index = _i; 63 | break; 64 | } 65 | } 66 | 67 | validatorMap[_validator] = false; 68 | validators[_index] = validators[validatorCount - 1]; 69 | validators.pop(); 70 | 71 | validatorCount--; 72 | 73 | emit ValidatorRemoved(_id, _validator); 74 | } 75 | 76 | function _updateQuorum( 77 | uint256 _id, 78 | uint256 _numerator, 79 | uint256 _denominator 80 | ) internal { 81 | require(_numerator <= _denominator); 82 | uint256 _previousNumerator = num; 83 | uint256 _previousDenominator = denom; 84 | 85 | num = _numerator; 86 | denom = _denominator; 87 | 88 | emit ThresholdUpdated(_id, _numerator, _denominator, _previousNumerator, _previousDenominator); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /contracts/v0.5/chain/sidechain/SidechainGatewayStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.17; 3 | 4 | import "../../references/ProxyStorage.sol"; 5 | import "../../references/Pausable.sol"; 6 | import "../common/Validator.sol"; 7 | import "../common/Registry.sol"; 8 | import "./Acknowledgement.sol"; 9 | 10 | /** 11 | * @title SidechainGatewayStorage 12 | * @dev Storage of deposit and withdraw information. 13 | */ 14 | contract SidechainGatewayStorage is ProxyStorage, Pausable { 15 | event TokenDeposited( 16 | uint256 indexed depositId, 17 | address indexed owner, 18 | address indexed tokenAddress, 19 | uint256 tokenNumber // ERC-20 amount or ERC721 tokenId 20 | ); 21 | 22 | event TokenWithdrew( 23 | uint256 indexed _withdrawId, 24 | address indexed _owner, 25 | address indexed _tokenAddress, 26 | address _mainchainAddress, 27 | uint32 _standard, 28 | uint256 _tokenNumber 29 | ); 30 | 31 | event RequestTokenWithdrawalSigAgain( 32 | uint256 indexed _withdrawalId, 33 | address indexed _owner, 34 | address indexed _tokenAddress, 35 | address _mainchainAddress, 36 | uint32 _standard, 37 | uint256 _tokenNumber 38 | ); 39 | 40 | struct DepositEntry { 41 | address owner; 42 | address tokenAddress; 43 | uint256 tokenNumber; 44 | } 45 | 46 | struct WithdrawalEntry { 47 | address owner; 48 | address tokenAddress; 49 | address mainchainAddress; 50 | uint32 standard; 51 | uint256 tokenNumber; 52 | } 53 | 54 | struct PendingWithdrawalInfo { 55 | uint256[] withdrawalIds; 56 | uint256 count; 57 | } 58 | 59 | Registry public registry; 60 | 61 | // Final deposit state, update only once when there is enough acknowledgement 62 | mapping(uint256 => DepositEntry) public deposits; 63 | 64 | uint256 public withdrawalCount; 65 | WithdrawalEntry[] public withdrawals; 66 | mapping(uint256 => mapping(address => bytes)) public withdrawalSig; 67 | mapping(uint256 => address[]) public withdrawalSigners; 68 | 69 | // Data for single users 70 | mapping(address => uint256[]) pendingWithdrawals; 71 | uint256 public maxPendingWithdrawal; 72 | 73 | function updateRegistry(address _registry) external onlyAdmin { 74 | registry = Registry(_registry); 75 | } 76 | 77 | function updateMaxPendingWithdrawal(uint256 _maxPendingWithdrawal) public onlyAdmin { 78 | maxPendingWithdrawal = _maxPendingWithdrawal; 79 | } 80 | 81 | function _getValidator() internal view returns (Validator) { 82 | return Validator(registry.getContract(registry.VALIDATOR())); 83 | } 84 | 85 | function _getAcknowledgementContract() internal view returns (Acknowledgement) { 86 | return Acknowledgement(registry.getContract(registry.ACKNOWLEDGEMENT())); 87 | } 88 | 89 | function _getDepositAckChannel() internal view returns (string memory) { 90 | return _getAcknowledgementContract().DEPOSIT_CHANNEL(); 91 | } 92 | 93 | function _getWithdrawalAckChannel() internal view returns (string memory) { 94 | return _getAcknowledgementContract().WITHDRAWAL_CHANNEL(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /contracts/v0.5/chain/common/Registry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.17; 3 | 4 | import "../../references/HasAdmin.sol"; 5 | 6 | contract Registry is HasAdmin { 7 | event ContractAddressUpdated(string indexed _name, bytes32 indexed _code, address indexed _newAddress); 8 | 9 | event TokenMapped(address indexed _mainchainToken, address indexed _sidechainToken, uint32 _standard); 10 | 11 | string public constant GATEWAY = "GATEWAY"; 12 | string public constant WETH_TOKEN = "WETH_TOKEN"; 13 | string public constant VALIDATOR = "VALIDATOR"; 14 | string public constant ACKNOWLEDGEMENT = "ACKNOWLEDGEMENT"; 15 | 16 | struct TokenMapping { 17 | address mainchainToken; 18 | address sidechainToken; 19 | uint32 standard; // 20, 721 or any other standards 20 | } 21 | 22 | mapping(bytes32 => address) public contractAddresses; 23 | mapping(address => TokenMapping) public mainchainMap; 24 | mapping(address => TokenMapping) public sidechainMap; 25 | 26 | function getContract(string calldata _name) external view returns (address _address) { 27 | bytes32 _code = getCode(_name); 28 | _address = contractAddresses[_code]; 29 | require(_address != address(0)); 30 | } 31 | 32 | function isTokenMapped( 33 | address _token, 34 | uint32 _standard, 35 | bool _isMainchain 36 | ) external view returns (bool) { 37 | TokenMapping memory _mapping = _getTokenMapping(_token, _isMainchain); 38 | 39 | return 40 | _mapping.mainchainToken != address(0) && _mapping.sidechainToken != address(0) && _mapping.standard == _standard; 41 | } 42 | 43 | function updateContract(string calldata _name, address _newAddress) external onlyAdmin { 44 | bytes32 _code = getCode(_name); 45 | contractAddresses[_code] = _newAddress; 46 | 47 | emit ContractAddressUpdated(_name, _code, _newAddress); 48 | } 49 | 50 | function mapToken( 51 | address _mainchainToken, 52 | address _sidechainToken, 53 | uint32 _standard 54 | ) external onlyAdmin { 55 | TokenMapping memory _map = TokenMapping(_mainchainToken, _sidechainToken, _standard); 56 | 57 | mainchainMap[_mainchainToken] = _map; 58 | sidechainMap[_sidechainToken] = _map; 59 | 60 | emit TokenMapped(_mainchainToken, _sidechainToken, _standard); 61 | } 62 | 63 | function clearMapToken(address _mainchainToken, address _sidechainToken) external onlyAdmin { 64 | TokenMapping storage _mainchainMap = mainchainMap[_mainchainToken]; 65 | _clearMapEntry(_mainchainMap); 66 | 67 | TokenMapping storage _sidechainMap = sidechainMap[_sidechainToken]; 68 | _clearMapEntry(_sidechainMap); 69 | } 70 | 71 | function getMappedToken(address _token, bool _isMainchain) 72 | external 73 | view 74 | returns ( 75 | address _mainchainToken, 76 | address _sidechainToken, 77 | uint32 _standard 78 | ) 79 | { 80 | TokenMapping memory _mapping = _getTokenMapping(_token, _isMainchain); 81 | _mainchainToken = _mapping.mainchainToken; 82 | _sidechainToken = _mapping.sidechainToken; 83 | _standard = _mapping.standard; 84 | } 85 | 86 | function getCode(string memory _name) public pure returns (bytes32) { 87 | return keccak256(abi.encodePacked(_name)); 88 | } 89 | 90 | function _getTokenMapping(address _token, bool isMainchain) internal view returns (TokenMapping memory _mapping) { 91 | if (isMainchain) { 92 | _mapping = mainchainMap[_token]; 93 | } else { 94 | _mapping = sidechainMap[_token]; 95 | } 96 | } 97 | 98 | function _clearMapEntry(TokenMapping storage _entry) internal { 99 | _entry.mainchainToken = address(0); 100 | _entry.sidechainToken = address(0); 101 | _entry.standard = 0; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /contracts/v0.5/chain/sidechain/Acknowledgement.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.17; 3 | 4 | import "../../references/HasAdmin.sol"; 5 | import "../../references/HasOperators.sol"; 6 | import "../common/Validator.sol"; 7 | 8 | contract Acknowledgement is HasOperators { 9 | // Acknowledge status, once the acknowledgements reach the threshold the 1st 10 | // time, it can take effect to the system. E.g. confirm a deposit. 11 | // Acknowledgments after that should not have any effects. 12 | enum Status { 13 | NotApproved, 14 | FirstApproved, 15 | AlreadyApproved 16 | } 17 | // Mapping from channel => boolean 18 | mapping(bytes32 => bool) public enabledChannels; 19 | // Mapping from channel => id => validator => data hash 20 | mapping(bytes32 => mapping(uint256 => mapping(address => bytes32))) public validatorAck; 21 | // Mapping from channel => id => data hash => ack count 22 | mapping(bytes32 => mapping(uint256 => mapping(bytes32 => uint256))) public ackCount; 23 | // Mapping from channel => id => data hash => ack status 24 | mapping(bytes32 => mapping(uint256 => mapping(bytes32 => Status))) public ackStatus; 25 | 26 | string public constant DEPOSIT_CHANNEL = "DEPOSIT_CHANNEL"; 27 | string public constant WITHDRAWAL_CHANNEL = "WITHDRAWAL_CHANNEL"; 28 | string public constant VALIDATOR_CHANNEL = "VALIDATOR_CHANNEL"; 29 | 30 | Validator public validator; 31 | 32 | constructor(address _validator) public { 33 | addChannel(DEPOSIT_CHANNEL); 34 | addChannel(WITHDRAWAL_CHANNEL); 35 | addChannel(VALIDATOR_CHANNEL); 36 | validator = Validator(_validator); 37 | } 38 | 39 | function getChannelHash(string memory _name) public view returns (bytes32 _channel) { 40 | _channel = _getHash(_name); 41 | _requireValidChannel(_channel); 42 | } 43 | 44 | function addChannel(string memory _name) public onlyAdmin { 45 | bytes32 _channel = _getHash(_name); 46 | enabledChannels[_channel] = true; 47 | } 48 | 49 | function removeChannel(string memory _name) public onlyAdmin { 50 | bytes32 _channel = _getHash(_name); 51 | _requireValidChannel(_channel); 52 | delete enabledChannels[_channel]; 53 | } 54 | 55 | function updateValidator(address _validator) public onlyAdmin { 56 | validator = Validator(_validator); 57 | } 58 | 59 | function acknowledge( 60 | string memory _channelName, 61 | uint256 _id, 62 | bytes32 _hash, 63 | address _validator 64 | ) public onlyOperator returns (Status) { 65 | bytes32 _channel = getChannelHash(_channelName); 66 | require( 67 | validatorAck[_channel][_id][_validator] == bytes32(0), 68 | "Acknowledgement: the validator already acknowledged" 69 | ); 70 | 71 | validatorAck[_channel][_id][_validator] = _hash; 72 | Status _status = ackStatus[_channel][_id][_hash]; 73 | uint256 _count = ackCount[_channel][_id][_hash]; 74 | 75 | if (validator.checkThreshold(_count + 1)) { 76 | if (_status == Status.NotApproved) { 77 | ackStatus[_channel][_id][_hash] = Status.FirstApproved; 78 | } else { 79 | ackStatus[_channel][_id][_hash] = Status.AlreadyApproved; 80 | } 81 | } 82 | 83 | ackCount[_channel][_id][_hash]++; 84 | 85 | return ackStatus[_channel][_id][_hash]; 86 | } 87 | 88 | function hasValidatorAcknowledged( 89 | string memory _channelName, 90 | uint256 _id, 91 | address _validator 92 | ) public view returns (bool) { 93 | bytes32 _channel = _getHash(_channelName); 94 | return validatorAck[_channel][_id][_validator] != bytes32(0); 95 | } 96 | 97 | function getAcknowledgementStatus( 98 | string memory _channelName, 99 | uint256 _id, 100 | bytes32 _hash 101 | ) public view returns (Status) { 102 | bytes32 _channel = _getHash(_channelName); 103 | return ackStatus[_channel][_id][_hash]; 104 | } 105 | 106 | function _getHash(string memory _name) internal pure returns (bytes32 _hash) { 107 | _hash = keccak256(abi.encode(_name)); 108 | } 109 | 110 | function _requireValidChannel(bytes32 _channelHash) internal view { 111 | require(enabledChannels[_channelHash], "Acknowledgement: invalid channel"); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /bug-bounty.md: -------------------------------------------------------------------------------- 1 | If you have questions please join the [Axie Discord](https://discord.com/invite/axie) and post in the #Ronin channel. 2 | 3 | Ronin testnet endpoints: https://ronin-testnet.skymavis.com/rpc or via websocket at: wss://ronin-testnet.skymavis.com/ws 4 | with chainId: 2021 5 | 6 | **Ronin Key Facts** 7 | 8 | - Ronin is an Ethereum sidechain built specifically for Axie Infinity. Each Axie is an ERC721 token represented as a unique digital creature that can be used in a variety of separate games. So far, there are Axie battles and a kingdom-building game centered around ownership of land plots. Land and items (artifacts) are also ERC 721 tokens. Small Love Potions (SLP) and Axie Infinity Shards (AXS) are ERC 20 tokens native to the Axie ecosystem. 9 | - Ronin is currently a Byzantine Fault Tolerant proof of authority(POA) network operated by validators. Validators are appointed by Sky Mavis, the core developers of Axie Infinity. 10 | - Blocks require approval from 2/3 of Validators in order to be approved. Over time, Ronin will be upgraded to incorporate proof of stake elements as well as new layer 2 solutions such as Zk sync and Optimistic rollups. 11 | - Validators are responsible for authoring and validating blocks, updating price oracles, and approving deposits and transfers of assets (ETH, ERC20, and ERC721) to and from Ronin. Validators also control the addition and removal of other validators. 12 | - Ronin is developed based on Ethereum codebase so you can use web3 client to connect to Ronin. At the moment all transactions on Ronin is free of charge. (You can set the gas price in the transaction to be 0). 13 | 14 | **Bug Bounty Program** 15 | 16 | The bug bounty program will commence at 9:00 AM EST on December 23rd, 2020, and run until Mainnet launch. The scope of this program is to double-check functionality related to deposits, withdrawals, and validator addition/removal. All code related to this bounty program is publicly available within this repo. We are specifically looking for issues related to: 17 | 18 | - Theft of user assets 19 | - Block validation 20 | - Issues around validating deposits and withdrawals 21 | - Issues around deployment 22 | - Loss of private keys 23 | - Admin control issues 24 | - Other process breakdowns 25 | 26 | **Rules** 27 | 28 | - Rewards for bugs are issued first come first serve. Issues that have already been flagged are not eligible for rewards. 29 | - This program only covers code from this Github repo. 30 | - Description of vulnerabilities must be submitted as issues to this repo. 31 | - Rewards will be distributed at the end of the bug bounty program. 32 | - We will keep you updated on the status of your submitted issue. 33 | 34 | **Submission Guidelines** 35 | 36 | Use this template when submitting issues: 37 | 38 | **Description:** Use clear, concise phrases when describing the issue and it's potential impact 39 | 40 | **Impact:** What will happen if the issue is left unaddressed? 41 | 42 | **Reproduction:** How can we reproduce the vulnerability? 43 | 44 | **Fix:** How can the issue be fixed? 45 | 46 | **Additional Comments:** Use this space for additional information. 47 | 48 | **ETH Address:** Needed for reward distribution. 49 | 50 | Please make sure to include relevant screenshots and code snippets. 51 | 52 | **Rewards** 53 | 54 | Rewards will be based on severity which is derived from impact and likelihood. 55 | 56 | - **Critical bugs:** Issues that can result in a hack, theft of user funds, and chain collapse. Up to 3000 AXS 57 | - **Major bugs:** Can cause significant problems when using the chain such as issues with validating user deposits and withdrawals or loss of private keys. Up to 1500 AXS 58 | - **Minor bugs:** A small issue, perhaps with wallets, accounts, or deployment. Not chain breaking but still great to resolve early. Up to 300 AXS 59 | - **Useful feedback:** Comments that while not specifying a bug, can help improve confidence and integrity of the system. Up to 75 AXS 60 | 61 | We have capped the amount of AXS reserved for this program at 15,000 AXS. 62 | 63 | _Please note that AXS is not the native token for Ronin. All AXS will be taken from Sky Mavis owned AXS reserves._ 64 | 65 | **Important Legal Information:** 66 | 67 | The bug bounty program is an experimental rewards program for our community developers to help us improve Ronin. Rewards are at the sole discretion of the Sky Mavis team. All rewards are subject to applicable law and thus applicable taxes. Don't target our physical security measures, or attempt to Sybil attack or (DDOS) attack the program. Your testing must not violate any law or compromise any data that is not yours. 68 | 69 | Copyright (c) 2020 Sky Mavis PTE. LTD 70 | -------------------------------------------------------------------------------- /contracts/v0.5/chain/mainchain/MainchainGatewayManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.17; 3 | 4 | import "../../references/ERC20/IERC20.sol"; 5 | import "../../references/ERC20/IERC20Mintable.sol"; 6 | import "../../references/ERC721/IERC721.sol"; 7 | import "../../references/ERC721/IERC721Mintable.sol"; 8 | import "../../references/ECVerify.sol"; 9 | import "../../references/SafeMath.sol"; 10 | import "../../references/AddressUtils.sol"; 11 | import "./WETH.sol"; 12 | import "./MainchainGatewayStorage.sol"; 13 | 14 | /** 15 | * @title MainchainGatewayManager 16 | * @dev Logic to handle deposits and withdrawl on Mainchain. 17 | */ 18 | contract MainchainGatewayManager is MainchainGatewayStorage { 19 | using AddressUtils for address; 20 | using SafeMath for uint256; 21 | using ECVerify for bytes32; 22 | 23 | modifier onlyMappedToken(address _token, uint32 _standard) { 24 | require(registry.isTokenMapped(_token, _standard, true), "MainchainGatewayManager: Token is not mapped"); 25 | _; 26 | } 27 | 28 | modifier onlyNewWithdrawal(uint256 _withdrawalId) { 29 | WithdrawalEntry storage _entry = withdrawals[_withdrawalId]; 30 | require(_entry.owner == address(0) && _entry.tokenAddress == address(0)); 31 | _; 32 | } 33 | 34 | // Should be able to withdraw from WETH 35 | function() external payable {} 36 | 37 | function depositEth() external payable whenNotPaused returns (uint256) { 38 | return depositEthFor(msg.sender); 39 | } 40 | 41 | function depositERC20(address _token, uint256 _amount) external whenNotPaused returns (uint256) { 42 | return depositERC20For(msg.sender, _token, _amount); 43 | } 44 | 45 | function depositERC721(address _token, uint256 _tokenId) external whenNotPaused returns (uint256) { 46 | return depositERC721For(msg.sender, _token, _tokenId); 47 | } 48 | 49 | function depositEthFor(address _owner) public payable whenNotPaused returns (uint256) { 50 | address _weth = registry.getContract(registry.WETH_TOKEN()); 51 | WETH(_weth).deposit.value(msg.value)(); 52 | return _createDepositEntry(_owner, _weth, 20, msg.value); 53 | } 54 | 55 | function depositERC20For( 56 | address _user, 57 | address _token, 58 | uint256 _amount 59 | ) public whenNotPaused returns (uint256) { 60 | require( 61 | IERC20(_token).transferFrom(msg.sender, address(this), _amount), 62 | "MainchainGatewayManager: ERC-20 token transfer failed" 63 | ); 64 | return _createDepositEntry(_user, _token, 20, _amount); 65 | } 66 | 67 | function depositERC721For( 68 | address _user, 69 | address _token, 70 | uint256 _tokenId 71 | ) public whenNotPaused returns (uint256) { 72 | IERC721(_token).transferFrom(msg.sender, address(this), _tokenId); 73 | return _createDepositEntry(_user, _token, 721, _tokenId); 74 | } 75 | 76 | function depositBulkFor( 77 | address _user, 78 | address[] memory _tokens, 79 | uint256[] memory _tokenNumbers 80 | ) public whenNotPaused { 81 | require(_tokens.length == _tokenNumbers.length); 82 | 83 | for (uint256 _i = 0; _i < _tokens.length; _i++) { 84 | address _token = _tokens[_i]; 85 | uint256 _tokenNumber = _tokenNumbers[_i]; 86 | (, , uint32 _standard) = registry.getMappedToken(_token, true); 87 | 88 | if (_standard == 20) { 89 | depositERC20For(_user, _token, _tokenNumber); 90 | } else if (_standard == 721) { 91 | depositERC721For(_user, _token, _tokenNumber); 92 | } else { 93 | revert("Token is not mapped or token type not supported"); 94 | } 95 | } 96 | } 97 | 98 | function withdrawToken( 99 | uint256 _withdrawalId, 100 | address _token, 101 | uint256 _amount, 102 | bytes memory _signatures 103 | ) public whenNotPaused { 104 | withdrawTokenFor(_withdrawalId, msg.sender, _token, _amount, _signatures); 105 | } 106 | 107 | function withdrawTokenFor( 108 | uint256 _withdrawalId, 109 | address _user, 110 | address _token, 111 | uint256 _amount, 112 | bytes memory _signatures 113 | ) public whenNotPaused { 114 | (, , uint32 _tokenType) = registry.getMappedToken(_token, true); 115 | 116 | if (_tokenType == 20) { 117 | withdrawERC20For(_withdrawalId, _user, _token, _amount, _signatures); 118 | } else if (_tokenType == 721) { 119 | withdrawERC721For(_withdrawalId, _user, _token, _amount, _signatures); 120 | } 121 | } 122 | 123 | function withdrawERC20( 124 | uint256 _withdrawalId, 125 | address _token, 126 | uint256 _amount, 127 | bytes memory _signatures 128 | ) public whenNotPaused { 129 | withdrawERC20For(_withdrawalId, msg.sender, _token, _amount, _signatures); 130 | } 131 | 132 | function withdrawERC20For( 133 | uint256 _withdrawalId, 134 | address _user, 135 | address _token, 136 | uint256 _amount, 137 | bytes memory _signatures 138 | ) public whenNotPaused onlyMappedToken(_token, 20) { 139 | bytes32 _hash = keccak256(abi.encodePacked("withdrawERC20", _withdrawalId, _user, _token, _amount)); 140 | 141 | require(verifySignatures(_hash, _signatures)); 142 | 143 | if (_token == registry.getContract(registry.WETH_TOKEN())) { 144 | _withdrawETHFor(_user, _amount); 145 | } else { 146 | uint256 _gatewayBalance = IERC20(_token).balanceOf(address(this)); 147 | 148 | if (_gatewayBalance < _amount) { 149 | require( 150 | IERC20Mintable(_token).mint(address(this), _amount.sub(_gatewayBalance)), 151 | "MainchainGatewayManager: Minting ERC20 token to gateway failed" 152 | ); 153 | } 154 | 155 | require(IERC20(_token).transfer(_user, _amount), "Transfer failed"); 156 | } 157 | 158 | _insertWithdrawalEntry(_withdrawalId, _user, _token, _amount); 159 | } 160 | 161 | function withdrawERC721( 162 | uint256 _withdrawalId, 163 | address _token, 164 | uint256 _tokenId, 165 | bytes memory _signatures 166 | ) public whenNotPaused { 167 | withdrawERC721For(_withdrawalId, msg.sender, _token, _tokenId, _signatures); 168 | } 169 | 170 | function withdrawERC721For( 171 | uint256 _withdrawalId, 172 | address _user, 173 | address _token, 174 | uint256 _tokenId, 175 | bytes memory _signatures 176 | ) public whenNotPaused onlyMappedToken(_token, 721) { 177 | bytes32 _hash = keccak256(abi.encodePacked("withdrawERC721", _withdrawalId, _user, _token, _tokenId)); 178 | 179 | require(verifySignatures(_hash, _signatures)); 180 | 181 | if (!_tryERC721TransferFrom(_token, address(this), _user, _tokenId)) { 182 | require( 183 | IERC721Mintable(_token).mint(_user, _tokenId), 184 | "MainchainGatewayManager: Minting ERC721 token to gateway failed" 185 | ); 186 | } 187 | 188 | _insertWithdrawalEntry(_withdrawalId, _user, _token, _tokenId); 189 | } 190 | 191 | /** 192 | * @dev returns true if there is enough signatures from validators. 193 | */ 194 | function verifySignatures(bytes32 _hash, bytes memory _signatures) public view returns (bool) { 195 | uint256 _signatureCount = _signatures.length.div(66); 196 | 197 | Validator _validator = Validator(registry.getContract(registry.VALIDATOR())); 198 | uint256 _validatorCount = 0; 199 | address _lastSigner = address(0); 200 | 201 | for (uint256 i = 0; i < _signatureCount; i++) { 202 | address _signer = _hash.recover(_signatures, i.mul(66)); 203 | if (_validator.isValidator(_signer)) { 204 | _validatorCount++; 205 | } 206 | // Prevent duplication of signatures 207 | require(_signer > _lastSigner); 208 | _lastSigner = _signer; 209 | } 210 | 211 | return _validator.checkThreshold(_validatorCount); 212 | } 213 | 214 | function _createDepositEntry( 215 | address _owner, 216 | address _token, 217 | uint32 _standard, 218 | uint256 _number 219 | ) internal onlyMappedToken(_token, _standard) returns (uint256 _depositId) { 220 | (, address _sidechainToken, uint32 _tokenStandard) = registry.getMappedToken(_token, true); 221 | require(_standard == _tokenStandard); 222 | 223 | DepositEntry memory _entry = DepositEntry(_owner, _token, _sidechainToken, _standard, _number); 224 | 225 | deposits.push(_entry); 226 | _depositId = depositCount++; 227 | 228 | emit TokenDeposited(_depositId, _owner, _token, _sidechainToken, _standard, _number); 229 | } 230 | 231 | function _insertWithdrawalEntry( 232 | uint256 _withdrawalId, 233 | address _owner, 234 | address _token, 235 | uint256 _number 236 | ) internal onlyNewWithdrawal(_withdrawalId) { 237 | WithdrawalEntry memory _entry = WithdrawalEntry(_owner, _token, _number); 238 | 239 | withdrawals[_withdrawalId] = _entry; 240 | 241 | emit TokenWithdrew(_withdrawalId, _owner, _token, _number); 242 | } 243 | 244 | function _withdrawETHFor(address _user, uint256 _amount) internal { 245 | address _weth = registry.getContract(registry.WETH_TOKEN()); 246 | WETH(_weth).withdraw(_amount); 247 | _user.toPayable().transfer(_amount); 248 | } 249 | 250 | // See more here https://blog.polymath.network/try-catch-in-solidity-handling-the-revert-exception-f53718f76047 251 | function _tryERC721TransferFrom( 252 | address _token, 253 | address _from, 254 | address _to, 255 | uint256 _tokenId 256 | ) internal returns (bool) { 257 | (bool success, ) = _token.call(abi.encodeWithSelector(IERC721(_token).transferFrom.selector, _from, _to, _tokenId)); 258 | return success; 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /contracts/v0.5/chain/sidechain/SidechainGatewayManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.5.17; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "../../references/ECVerify.sol"; 6 | import "../../references/SafeMath.sol"; 7 | import "../../references/ERC20/IERC20.sol"; 8 | import "../../references/ERC20/IERC20Mintable.sol"; 9 | import "../../references/ERC721/IERC721.sol"; 10 | import "../../references/ERC721/IERC721Mintable.sol"; 11 | import "../../references/AddressUtils.sol"; 12 | import "./SidechainGatewayStorage.sol"; 13 | 14 | /** 15 | * @title SidechainGatewayManager 16 | * @dev Logic to handle deposits and withdrawals on Sidechain. 17 | */ 18 | contract SidechainGatewayManager is SidechainGatewayStorage { 19 | using AddressUtils for address; 20 | using SafeMath for uint256; 21 | using ECVerify for bytes32; 22 | 23 | modifier onlyMappedToken(address _token, uint32 _standard) { 24 | require(registry.isTokenMapped(_token, _standard, false), "SidechainGatewayManager: token is not mapped"); 25 | _; 26 | } 27 | 28 | modifier onlyValidator() { 29 | require(_getValidator().isValidator(msg.sender), "SidechainGatewayManager: sender is not validator"); 30 | _; 31 | } 32 | 33 | function() external { 34 | revert("SidechainGatewayManager: Invalid method"); 35 | } 36 | 37 | function batchDepositERCTokenFor( 38 | uint256[] calldata _depositIds, 39 | address[] calldata _owners, 40 | address[] calldata _tokens, 41 | uint32[] calldata _standards, 42 | uint256[] calldata _tokenNumbers 43 | ) external whenNotPaused onlyValidator { 44 | require( 45 | _depositIds.length == _owners.length && 46 | _depositIds.length == _tokens.length && 47 | _depositIds.length == _standards.length && 48 | _depositIds.length == _tokenNumbers.length, 49 | "SidechainGatewayManager: invalid input array length" 50 | ); 51 | 52 | for (uint256 _i; _i < _depositIds.length; _i++) { 53 | depositERCTokenFor(_depositIds[_i], _owners[_i], _tokens[_i], _standards[_i], _tokenNumbers[_i]); 54 | } 55 | } 56 | 57 | function batchAcknowledWithdrawalOnMainchain(uint256[] calldata _withdrawalIds) external whenNotPaused onlyValidator { 58 | for (uint256 _i; _i < _withdrawalIds.length; _i++) { 59 | acknowledWithdrawalOnMainchain(_withdrawalIds[_i]); 60 | } 61 | } 62 | 63 | function batchSubmitWithdrawalSignatures( 64 | uint256[] calldata _withdrawalIds, 65 | bool[] calldata _shouldReplaces, 66 | bytes[] calldata _sigs 67 | ) external whenNotPaused onlyValidator { 68 | require( 69 | _withdrawalIds.length == _shouldReplaces.length && _withdrawalIds.length == _sigs.length, 70 | "SidechainGatewayManager: invalid input array length" 71 | ); 72 | 73 | for (uint256 _i; _i < _withdrawalIds.length; _i++) { 74 | submitWithdrawalSignatures(_withdrawalIds[_i], _shouldReplaces[_i], _sigs[_i]); 75 | } 76 | } 77 | 78 | function withdrawETH(uint256 _amount) external whenNotPaused returns (uint256) { 79 | address _weth = registry.getContract(registry.WETH_TOKEN()); 80 | return withdrawERC20For(msg.sender, _weth, _amount); 81 | } 82 | 83 | function withdrawERC20(address _token, uint256 _amount) external whenNotPaused returns (uint256) { 84 | return withdrawERC20For(msg.sender, _token, _amount); 85 | } 86 | 87 | function depositERCTokenFor( 88 | uint256 _depositId, 89 | address _owner, 90 | address _token, 91 | uint32 _standard, 92 | uint256 _tokenNumber 93 | ) public whenNotPaused onlyValidator { 94 | (, , uint32 _tokenStandard) = registry.getMappedToken(_token, false); 95 | 96 | require(_tokenStandard == _standard, "SidechainGatewayManager: token standard is not matched"); 97 | 98 | bytes32 _hash = keccak256(abi.encode(_owner, _token, _standard, _tokenNumber)); 99 | 100 | Acknowledgement.Status _status = _getAcknowledgementContract().acknowledge( 101 | _getDepositAckChannel(), 102 | _depositId, 103 | _hash, 104 | msg.sender 105 | ); 106 | 107 | if (_status == Acknowledgement.Status.FirstApproved) { 108 | if (_standard == 20) { 109 | _depositERC20For(_owner, _token, _tokenNumber); 110 | } else if (_standard == 721) { 111 | _depositERC721For(_owner, _token, _tokenNumber); 112 | } 113 | 114 | deposits[_depositId] = DepositEntry(_owner, _token, _tokenNumber); 115 | emit TokenDeposited(_depositId, _owner, _token, _tokenNumber); 116 | } 117 | } 118 | 119 | function withdrawERC20For( 120 | address _owner, 121 | address _token, 122 | uint256 _amount 123 | ) public whenNotPaused returns (uint256) { 124 | require( 125 | IERC20(_token).transferFrom(msg.sender, address(this), _amount), 126 | "SidechainGatewayManager: ERC20 token transfer failed" 127 | ); 128 | return _createWithdrawalEntry(_owner, _token, 20, _amount); 129 | } 130 | 131 | function withdrawERC721(address _token, uint256 _tokenId) public whenNotPaused returns (uint256) { 132 | return withdrawalERC721For(msg.sender, _token, _tokenId); 133 | } 134 | 135 | function withdrawalERC721For( 136 | address _owner, 137 | address _token, 138 | uint256 _tokenId 139 | ) public whenNotPaused returns (uint256) { 140 | IERC721(_token).transferFrom(msg.sender, address(this), _tokenId); 141 | return _createWithdrawalEntry(_owner, _token, 721, _tokenId); 142 | } 143 | 144 | function submitWithdrawalSignatures( 145 | uint256 _withdrawalId, 146 | bool _shouldReplace, 147 | bytes memory _sig 148 | ) public whenNotPaused onlyValidator { 149 | bytes memory _currentSig = withdrawalSig[_withdrawalId][msg.sender]; 150 | 151 | bool _alreadyHasSig = _currentSig.length != 0; 152 | 153 | if (!_shouldReplace && _alreadyHasSig) { 154 | return; 155 | } 156 | 157 | withdrawalSig[_withdrawalId][msg.sender] = _sig; 158 | if (!_alreadyHasSig) { 159 | withdrawalSigners[_withdrawalId].push(msg.sender); 160 | } 161 | } 162 | 163 | /** 164 | * Request signature again, in case the withdrawer didn't submit to mainchain in time and the set of the validator 165 | * has changed. Later on this should require some penaties, e.g some money. 166 | */ 167 | function requestSignatureAgain(uint256 _withdrawalId) public whenNotPaused { 168 | WithdrawalEntry memory _entry = withdrawals[_withdrawalId]; 169 | 170 | require(_entry.owner == msg.sender, "SidechainGatewayManager: sender is not entry owner"); 171 | 172 | emit RequestTokenWithdrawalSigAgain( 173 | _withdrawalId, 174 | _entry.owner, 175 | _entry.tokenAddress, 176 | _entry.mainchainAddress, 177 | _entry.standard, 178 | _entry.tokenNumber 179 | ); 180 | } 181 | 182 | function getPendingWithdrawals(address _owner) 183 | public 184 | view 185 | returns (uint256[] memory ids, WithdrawalEntry[] memory entries) 186 | { 187 | ids = pendingWithdrawals[_owner]; 188 | entries = new WithdrawalEntry[](ids.length); 189 | 190 | for (uint256 _i = 0; _i < ids.length; _i++) { 191 | WithdrawalEntry memory _entry = withdrawals[ids[_i]]; 192 | entries[_i] = _entry; 193 | } 194 | } 195 | 196 | function acknowledWithdrawalOnMainchain(uint256 _withdrawalId) public whenNotPaused onlyValidator { 197 | bytes32 _hash = keccak256(abi.encode(_withdrawalId)); 198 | Acknowledgement.Status _status = _getAcknowledgementContract().acknowledge( 199 | _getWithdrawalAckChannel(), 200 | _withdrawalId, 201 | _hash, 202 | msg.sender 203 | ); 204 | 205 | if (_status == Acknowledgement.Status.FirstApproved) { 206 | // Remove out of the pending withdrawals 207 | WithdrawalEntry storage _entry = withdrawals[_withdrawalId]; 208 | uint256[] storage _ids = pendingWithdrawals[_entry.owner]; 209 | uint256 _len = _ids.length; 210 | for (uint256 _i = 0; _i < _len; _i++) { 211 | if (_ids[_i] == _withdrawalId) { 212 | _ids[_i] = _ids[_len - 1]; 213 | _ids.length--; 214 | break; 215 | } 216 | } 217 | } 218 | } 219 | 220 | function getWithdrawalSigners(uint256 _withdrawalId) public view returns (address[] memory) { 221 | return withdrawalSigners[_withdrawalId]; 222 | } 223 | 224 | function getWithdrawalSignatures(uint256 _withdrawalId) 225 | public 226 | view 227 | returns (address[] memory _signers, bytes[] memory _sigs) 228 | { 229 | _signers = getWithdrawalSigners(_withdrawalId); 230 | _sigs = new bytes[](_signers.length); 231 | for (uint256 _i = 0; _i < _signers.length; _i++) { 232 | _sigs[_i] = withdrawalSig[_withdrawalId][_signers[_i]]; 233 | } 234 | } 235 | 236 | function _depositERC20For( 237 | address _owner, 238 | address _token, 239 | uint256 _amount 240 | ) internal { 241 | uint256 _gatewayBalance = IERC20(_token).balanceOf(address(this)); 242 | if (_gatewayBalance < _amount) { 243 | require( 244 | IERC20Mintable(_token).mint(address(this), _amount.sub(_gatewayBalance)), 245 | "SidechainGatewayManager: Minting ERC20 to gateway failed" 246 | ); 247 | } 248 | 249 | require(IERC20(_token).transfer(_owner, _amount), "SidechainGatewayManager: Transfer ERC20 token failed"); 250 | } 251 | 252 | function _depositERC721For( 253 | address _owner, 254 | address _token, 255 | uint256 _tokenId 256 | ) internal { 257 | if (!_tryERC721TransferFrom(_token, address(this), _owner, _tokenId)) { 258 | require( 259 | IERC721Mintable(_token).mint(_owner, _tokenId), 260 | "SidechainGatewayManager: Minting ERC721 token to gateway failed" 261 | ); 262 | } 263 | } 264 | 265 | function _alreadyReleased(uint256 _depositId) internal view returns (bool) { 266 | return deposits[_depositId].owner != address(0) || deposits[_depositId].tokenAddress != address(0); 267 | } 268 | 269 | function _createWithdrawalEntry( 270 | address _owner, 271 | address _token, 272 | uint32 _standard, 273 | uint256 _number 274 | ) internal onlyMappedToken(_token, _standard) returns (uint256 _withdrawalId) { 275 | (address _mainchainToken, , ) = registry.getMappedToken(_token, false); 276 | 277 | WithdrawalEntry memory _entry = WithdrawalEntry(_owner, _token, _mainchainToken, _standard, _number); 278 | 279 | _withdrawalId = withdrawalCount; 280 | withdrawals.push(_entry); 281 | withdrawalCount++; 282 | 283 | pendingWithdrawals[_owner].push(_withdrawalId); 284 | 285 | require( 286 | pendingWithdrawals[_owner].length <= maxPendingWithdrawal, 287 | "SidechainGatewayManager: pending withdrawal quantity reached the limit" 288 | ); 289 | 290 | emit TokenWithdrew(_withdrawalId, _owner, _token, _mainchainToken, _standard, _number); 291 | } 292 | 293 | // See more here https://blog.polymath.network/try-catch-in-solidity-handling-the-revert-exception-f53718f76047 294 | function _tryERC721TransferFrom( 295 | address _token, 296 | address _from, 297 | address _to, 298 | uint256 _tokenId 299 | ) internal returns (bool) { 300 | (bool success, ) = _token.call(abi.encodeWithSelector(IERC721(_token).transferFrom.selector, _from, _to, _tokenId)); 301 | return success; 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /test/chain/mainchain/MainchainGateway.test.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; 2 | import { expect } from 'chai'; 3 | import { ethers } from 'hardhat'; 4 | 5 | import { 6 | ERC20, 7 | ERC20Mintable, 8 | ERC20Mintable__factory, 9 | ERC20__factory, 10 | ERC721PresetMinterPauserAutoId__factory, 11 | MainchainGatewayManager, 12 | MainchainGatewayManager__factory, 13 | MainchainGatewayProxy, 14 | MainchainGatewayProxy__factory, 15 | MainchainValidator, 16 | MainchainValidator__factory, 17 | MockERC721, 18 | MockERC721__factory, 19 | PausableAdmin__factory, 20 | Registry, 21 | Registry__factory, 22 | WETH, 23 | WETH__factory, 24 | } from '../../../src/types'; 25 | import { withdrawalERC20Hash, getCombinedSignatures, ethToWei, withdrawalERC721Hash } from '../../../src/utils'; 26 | 27 | let alice: SignerWithAddress; 28 | let bob: SignerWithAddress; 29 | let charles: SignerWithAddress; 30 | let mainchainGateway: MainchainGatewayManager; 31 | let registry: Registry; 32 | let validator: MainchainValidator; 33 | let mainchainGatewayProxy: MainchainGatewayProxy; 34 | let weth: WETH; 35 | let erc20: ERC20Mintable; 36 | let erc721: MockERC721; 37 | 38 | describe('Mainchain gateway', () => { 39 | before(async () => { 40 | [alice, bob, charles] = await ethers.getSigners(); 41 | mainchainGateway = await new MainchainGatewayManager__factory(alice).deploy(); 42 | weth = await new WETH__factory(alice).deploy(); 43 | registry = await new Registry__factory(alice).deploy(); 44 | 45 | const validatorStr = await registry.VALIDATOR(); 46 | validator = await new MainchainValidator__factory(alice).deploy([alice.address, bob.address], 99, 100); 47 | await registry.updateContract(validatorStr, validator.address); 48 | 49 | mainchainGatewayProxy = await new MainchainGatewayProxy__factory(alice).deploy( 50 | mainchainGateway.address, 51 | registry.address 52 | ); 53 | 54 | // Use the logic in place of proxy address 55 | mainchainGateway = MainchainGatewayManager__factory.connect(mainchainGatewayProxy.address, alice); 56 | }); 57 | 58 | describe('test deposit', async () => { 59 | it('deploy WETH and update registry', async () => { 60 | const wethToken = await registry.WETH_TOKEN(); 61 | await registry.updateContract(wethToken, weth.address); 62 | }); 63 | 64 | it('should not be able to deposit eth when weth is not mapped', async () => { 65 | await expect(mainchainGateway.depositEth({ value: ethToWei(1) })).reverted; 66 | }); 67 | 68 | it('should be able to deposit eth after weth is mapped', async () => { 69 | await registry.mapToken(weth.address, weth.address, 20); 70 | await mainchainGateway.depositEth({ value: ethToWei(1) }); 71 | 72 | const depositCount = await mainchainGateway.depositCount(); 73 | expect(depositCount.toNumber()).eq(1); 74 | const [owner, token, , , amount] = await mainchainGateway.deposits(0); 75 | expect(owner.toLowerCase()).eq(alice.address.toLowerCase()); 76 | expect(token.toLowerCase()).eq(weth.address.toLowerCase()); 77 | expect(amount.toString()).eq(ethToWei(1).toString()); 78 | }); 79 | 80 | it('should be able to deposit ERC20', async () => { 81 | erc20 = await new ERC20Mintable__factory(alice).deploy(); 82 | await erc20.addMinters([alice.address]); 83 | await erc20.mint(alice.address, 1000); 84 | await erc20.mint(bob.address, 1000); 85 | await erc20.approve(mainchainGateway.address, 1000000000); 86 | await expect(mainchainGateway.depositERC20(erc20.address, 100)).reverted; 87 | 88 | await registry.mapToken(erc20.address, weth.address, 20); 89 | await mainchainGateway.depositERC20(erc20.address, 100); 90 | 91 | const [owner, token, sidechain, standard, amount] = await mainchainGateway.deposits(1); 92 | expect(owner.toLowerCase()).eq(alice.address.toLowerCase()); 93 | expect(token.toLowerCase()).eq(erc20.address.toLowerCase()); 94 | expect(sidechain.toLowerCase()).eq(weth.address.toLowerCase()); 95 | expect(standard).eq(20); 96 | expect(amount.toNumber()).eq(100); 97 | }); 98 | 99 | it('should be able to deposit ERC721', async () => { 100 | erc721 = await new MockERC721__factory(alice).deploy('ERC721', '721', ''); 101 | await erc721['mint(address,uint256)'](alice.address, 0); 102 | await erc721['mint(address,uint256)'](bob.address, 1); 103 | 104 | await registry.mapToken(erc721.address, erc721.address, 721); 105 | await expect(mainchainGateway.depositERC721(erc721.address, 0)).reverted; 106 | 107 | await erc721.setApprovalForAll(mainchainGateway.address, true); 108 | await mainchainGateway.depositERC721(erc721.address, 0); 109 | 110 | await erc721.connect(charles).setApprovalForAll(mainchainGateway.address, true); 111 | 112 | await expect(mainchainGateway.connect(charles).depositERC721(erc721.address, 2)).reverted; 113 | await erc721['mint(address,uint256)'](charles.address, 2); 114 | await mainchainGateway.connect(charles).depositERC721(erc721.address, 2); 115 | 116 | const [owner, token, sidechain, standard, tokenId] = await mainchainGateway.deposits(3); 117 | expect(owner.toLowerCase()).eq(charles.address.toLowerCase()); 118 | expect(token.toLowerCase()).eq(erc721.address.toLowerCase()); 119 | expect(sidechain.toLowerCase()).eq(erc721.address.toLowerCase()); 120 | expect(standard).eq(721); 121 | expect(tokenId.toNumber()).eq(2); 122 | }); 123 | 124 | it('should be able to deposit bulk', async () => { 125 | await erc721['mint(address,uint256)'](alice.address, 3); 126 | await erc721['mint(address,uint256)'](bob.address, 4); 127 | await erc721.setApprovalForAll(mainchainGateway.address, true); 128 | 129 | await expect( 130 | mainchainGateway.depositBulkFor(bob.address, [erc20.address, erc721.address, erc721.address], [1000, 3, 4]) 131 | ).reverted; 132 | 133 | await erc721.connect(bob).transferFrom(bob.address, alice.address, 4); 134 | 135 | await mainchainGateway.depositBulkFor(bob.address, [erc20.address, erc721.address, erc721.address], [10, 3, 4]); 136 | 137 | const depositCount = await mainchainGateway.depositCount(); 138 | expect(depositCount.toNumber()).eq(7); 139 | const [owner, token, , , tokenId] = await mainchainGateway.deposits(6); 140 | expect(owner.toLowerCase()).eq(bob.address.toLowerCase()); 141 | expect(token.toLowerCase()).eq(erc721.address.toLowerCase()); 142 | expect(tokenId.toNumber()).eq(4); 143 | }); 144 | }); 145 | 146 | describe('test withdrawal', async () => { 147 | it('should not be able to withdraw without enough signature', async () => { 148 | const { signatures } = await getCombinedSignatures( 149 | false, 150 | [alice], 151 | withdrawalERC20Hash(1, alice.address, weth.address, ethToWei(1)) 152 | ); 153 | 154 | await expect(mainchainGateway.withdrawToken(1, weth.address, ethToWei(1), signatures)).reverted; 155 | }); 156 | 157 | it('should not be able to withdraw eth with wrong order of signatures', async () => { 158 | const { signatures } = await getCombinedSignatures( 159 | true, 160 | [alice, bob], 161 | withdrawalERC20Hash(1, alice.address, weth.address, ethToWei(1)) 162 | ); 163 | await expect(mainchainGateway.withdrawToken(1, weth.address, ethToWei(1), signatures)).reverted; 164 | }); 165 | 166 | it('should be able to withdraw eth', async () => { 167 | const hash = withdrawalERC20Hash(1, alice.address, weth.address, ethToWei(0.5)); 168 | const { signatures } = await getCombinedSignatures(false, [alice, bob], hash); 169 | const beforeBalance = await alice.getBalance(); 170 | await mainchainGateway.connect(bob).withdrawERC20For(1, alice.address, weth.address, ethToWei(0.5), signatures); 171 | const afterBalance = await alice.getBalance(); 172 | expect(afterBalance.sub(beforeBalance).toString()).eq(ethToWei(0.5).toString()); 173 | }); 174 | 175 | it('should not able to withdraw with same withdrawalId', async () => { 176 | const { signatures } = await getCombinedSignatures( 177 | false, 178 | [alice, bob], 179 | withdrawalERC20Hash(1, alice.address, weth.address, ethToWei(0.5)) 180 | ); 181 | 182 | await expect( 183 | mainchainGateway.connect(bob).withdrawERC20For(1, alice.address, weth.address, ethToWei(0.5), signatures) 184 | ).reverted; 185 | }); 186 | 187 | it('should be able to withdraw for self', async () => { 188 | const { signatures } = await getCombinedSignatures( 189 | false, 190 | [alice, bob], 191 | withdrawalERC20Hash(2, alice.address, weth.address, ethToWei(0.5)) 192 | ); 193 | 194 | await mainchainGateway.withdrawToken(2, weth.address, ethToWei(0.5), signatures); 195 | }); 196 | 197 | it('should be able to withdraw locked erc20', async () => { 198 | const erc20Balance = await erc20.balanceOf(mainchainGateway.address); 199 | const { signatures } = await getCombinedSignatures( 200 | false, 201 | [alice, bob], 202 | withdrawalERC20Hash(3, alice.address, erc20.address, erc20Balance) 203 | ); 204 | 205 | const beforeBalance = await erc20.balanceOf(alice.address); 206 | await mainchainGateway.withdrawERC20(3, erc20.address, erc20Balance, signatures); 207 | const afterBalance = await erc20.balanceOf(alice.address); 208 | expect(beforeBalance.add(erc20Balance).toString()).eq(afterBalance.toString()); 209 | }); 210 | 211 | it('should be able to mint new erc20 token when withdrawing', async () => { 212 | await erc20.addMinters([mainchainGateway.address]); 213 | 214 | const { signatures } = await getCombinedSignatures( 215 | false, 216 | [alice, bob], 217 | withdrawalERC20Hash(4, bob.address, erc20.address, 1000) 218 | ); 219 | 220 | const beforeBalance = await erc20.balanceOf(bob.address); 221 | await mainchainGateway.withdrawERC20For(4, bob.address, erc20.address, 1000, signatures); 222 | const afterBalance = await erc20.balanceOf(bob.address); 223 | expect(beforeBalance.add(1000).toString()).eq(afterBalance.toString()); 224 | }); 225 | 226 | it('should be able to withdraw locked erc721', async () => { 227 | const { signatures } = await getCombinedSignatures( 228 | false, 229 | [alice, bob], 230 | withdrawalERC721Hash(5, charles.address, erc721.address, 4) 231 | ); 232 | 233 | await mainchainGateway.connect(charles).withdrawERC721(5, erc721.address, 4, signatures); 234 | const owner = await erc721.ownerOf(4); 235 | 236 | expect(owner.toLowerCase()).eq(charles.address.toLowerCase()); 237 | }); 238 | 239 | it('should be able to mint new erc721 when withdrawing', async () => { 240 | const MINTER_ROLE = await erc721.MINTER_ROLE(); 241 | await erc721.grantRole(MINTER_ROLE, mainchainGateway.address); 242 | await erc721.grantRole(MINTER_ROLE, alice.address); 243 | 244 | await erc721['mint(address,uint256)'](alice.address, 500); 245 | 246 | const { signatures } = await getCombinedSignatures( 247 | false, 248 | [alice, bob], 249 | withdrawalERC721Hash(6, bob.address, erc721.address, 1000) 250 | ); 251 | 252 | await mainchainGateway.withdrawERC721For(6, bob.address, erc721.address, 1000, signatures); 253 | const owner = await erc721.ownerOf(1000); 254 | expect(owner.toLowerCase()).eq(bob.address.toLowerCase()); 255 | }); 256 | 257 | it('should be able to set pausable admin ', async () => { 258 | const pausableAdmin = await new PausableAdmin__factory(alice).deploy(mainchainGateway.address); 259 | await mainchainGateway.changeAdmin(pausableAdmin.address); 260 | 261 | await pausableAdmin.pauseGateway(); 262 | const paused = await mainchainGateway.paused(); 263 | expect(paused).eq(true); 264 | }); 265 | }); 266 | }); 267 | -------------------------------------------------------------------------------- /test/chain/sidechain/SidechainGateway.test.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; 2 | import { expect } from 'chai'; 3 | import { BigNumber } from 'ethers'; 4 | import { ethers } from 'hardhat'; 5 | 6 | import { 7 | Acknowledgement, 8 | Acknowledgement__factory, 9 | MockERC721, 10 | MockERC721__factory, 11 | Registry, 12 | Registry__factory, 13 | RoninWETH, 14 | RoninWETH__factory, 15 | SidechainGatewayManager, 16 | SidechainGatewayManager__factory, 17 | SidechainGatewayProxy, 18 | SidechainGatewayProxy__factory, 19 | SidechainValidator, 20 | SidechainValidator__factory, 21 | } from '../../../src/types'; 22 | import { withdrawalERC20Hash, getCombinedSignatures, ethToWei } from '../../../src/utils'; 23 | 24 | describe('Sidechain gateway', () => { 25 | let alice: SignerWithAddress; 26 | let bob: SignerWithAddress; 27 | let charles: SignerWithAddress; 28 | let dan: SignerWithAddress; 29 | let sidechainGateway: SidechainGatewayManager; 30 | let registry: Registry; 31 | let validator: SidechainValidator; 32 | let acknowledgement: Acknowledgement; 33 | let sidechainGatewayProxy: SidechainGatewayProxy; 34 | let weth: RoninWETH; 35 | let erc721: MockERC721; 36 | 37 | before(async () => { 38 | [alice, bob, charles, dan] = await ethers.getSigners(); 39 | sidechainGateway = await new SidechainGatewayManager__factory(alice).deploy(); 40 | weth = await new RoninWETH__factory(alice).deploy(); 41 | erc721 = await new MockERC721__factory(alice).deploy('ERC721', '721', ''); 42 | registry = await new Registry__factory(alice).deploy(); 43 | acknowledgement = await new Acknowledgement__factory(alice).deploy(alice.address); // dummy validator contract 44 | validator = await new SidechainValidator__factory(alice).deploy( 45 | acknowledgement.address, 46 | [alice.address, bob.address, charles.address], 47 | 19, 48 | 30 49 | ); 50 | await acknowledgement.updateValidator(validator.address); 51 | 52 | const validatorContract = await registry.VALIDATOR(); 53 | await registry.updateContract(validatorContract, validator.address); 54 | 55 | const acknowledgementContract = await registry.ACKNOWLEDGEMENT(); 56 | await registry.updateContract(acknowledgementContract, acknowledgement.address); 57 | 58 | sidechainGatewayProxy = await new SidechainGatewayProxy__factory(alice).deploy( 59 | sidechainGateway.address, 60 | registry.address, 61 | 10 62 | ); 63 | 64 | // Use the contract logic in place of proxy address 65 | sidechainGateway = SidechainGatewayManager__factory.connect(sidechainGatewayProxy.address, alice); 66 | 67 | await weth.addMinters([sidechainGateway.address, alice.address]); 68 | const MINTER_ROLE = await erc721.MINTER_ROLE(); 69 | await erc721.grantRole(MINTER_ROLE, sidechainGateway.address); 70 | await erc721.grantRole(MINTER_ROLE, alice.address); 71 | await acknowledgement.addOperators([sidechainGateway.address, validator.address]); 72 | }); 73 | 74 | describe('test deposit', async () => { 75 | it('deploy WETH and update registry', async () => { 76 | const wethToken = await registry.WETH_TOKEN(); 77 | await registry.updateContract(wethToken, weth.address); 78 | }); 79 | 80 | it('should not be able to deposit weth when weth is not mapped', async () => { 81 | await expect(sidechainGateway.depositERCTokenFor(0, alice.address, weth.address, 20, ethToWei(1))).reverted; 82 | }); 83 | 84 | it('should be able to call deposit weth but not release yet', async () => { 85 | await registry.mapToken(bob.address, weth.address, 20); 86 | 87 | await sidechainGateway.depositERCTokenFor(0, alice.address, weth.address, 20, ethToWei(1)); 88 | 89 | const balance = await weth.balanceOf(alice.address); 90 | expect(balance.toNumber()).eq(0); 91 | }); 92 | 93 | it('should be able to call deposit and release token', async () => { 94 | const amount = ethToWei(1); 95 | await sidechainGateway.connect(bob).batchDepositERCTokenFor([0], [alice.address], [weth.address], [20], [amount]); 96 | 97 | const balance = await weth.balanceOf(alice.address); 98 | expect(balance.toString()).eq(amount.toString()); 99 | }); 100 | 101 | it('should not be able to deposit again', async () => { 102 | const amount = ethToWei(1); 103 | await expect(sidechainGateway.connect(bob).depositERCTokenFor(0, alice.address, weth.address, 20, amount)) 104 | .reverted; 105 | }); 106 | 107 | it('should be acknowledge again', async () => { 108 | const amount = ethToWei(1); 109 | await sidechainGateway.connect(charles).depositERCTokenFor(0, alice.address, weth.address, 20, amount); 110 | 111 | const balance = await weth.balanceOf(alice.address); 112 | expect(balance.toString()).eq(amount.toString()); 113 | }); 114 | 115 | it('should be able to trust the majority', async () => { 116 | const amount = ethToWei(1); 117 | await sidechainGateway.connect(bob).depositERCTokenFor(2, bob.address, weth.address, 20, amount.mul(2)); 118 | 119 | await sidechainGateway.connect(alice).depositERCTokenFor(2, bob.address, weth.address, 20, amount); 120 | 121 | let balance = await weth.balanceOf(bob.address); 122 | expect(balance.toString()).eq('0'); 123 | 124 | await sidechainGateway.connect(charles).depositERCTokenFor(2, bob.address, weth.address, 20, amount); 125 | 126 | const [owner, token, tokenNumber] = await sidechainGateway.deposits(2); 127 | expect(owner.toLowerCase()).eq(bob.address.toLowerCase()); 128 | expect(token.toLowerCase()).eq(weth.address.toLowerCase()); 129 | expect(tokenNumber.toString()).eq(amount.toString()); 130 | 131 | balance = await weth.balanceOf(bob.address); 132 | expect(balance.toString()).eq(amount.toString()); 133 | }); 134 | 135 | it('should be able to deposit ERC721 token', async () => { 136 | await registry.mapToken(charles.address, erc721.address, 721); 137 | await sidechainGateway.connect(bob).depositERCTokenFor(1, bob.address, erc721.address, 721, 2); 138 | await sidechainGateway.connect(charles).depositERCTokenFor(1, bob.address, erc721.address, 721, 2); 139 | 140 | const owner = await erc721.ownerOf(2); 141 | expect(owner.toLowerCase()).eq(bob.address.toLowerCase()); 142 | }); 143 | }); 144 | 145 | describe('test withdrawal', async () => { 146 | it('should not be able to withdraw ETH when not enough balance', async () => { 147 | await weth.connect(charles).approve(sidechainGateway.address, BigNumber.from(2).pow(255)); 148 | await expect(sidechainGateway.connect(charles).withdrawETH(ethToWei(1))).reverted; 149 | }); 150 | 151 | it('should be able to withdraw ETH', async () => { 152 | await weth.mint(charles.address, ethToWei(1)); 153 | await sidechainGateway.connect(charles).withdrawETH(ethToWei(1)); 154 | 155 | const [owner, token, mainchainToken, standard, amount] = await sidechainGateway.withdrawals(0); 156 | expect(owner.toLowerCase()).eq(charles.address.toLowerCase()); 157 | expect(token.toLowerCase()).eq(weth.address.toLowerCase()); 158 | expect(mainchainToken.toLowerCase()).eq(bob.address.toLowerCase()); 159 | expect(standard).eq(20); 160 | expect(amount.toString()).eq(ethToWei(1).toString()); 161 | }); 162 | 163 | it('should not be able to withdraw not owned ERC721', async () => { 164 | await erc721.setApprovalForAll(sidechainGateway.address, true); 165 | await expect(sidechainGateway.withdrawERC721(erc721.address, 1)).reverted; 166 | }); 167 | 168 | it('should be able to withdraw ERC721 for', async () => { 169 | await erc721['mint(address,uint256)'](alice.address, 1); 170 | await sidechainGateway.withdrawalERC721For(bob.address, erc721.address, 1); 171 | const [owner, token, mainchainToken, standard, id] = await sidechainGateway.withdrawals(1); 172 | 173 | expect(owner.toLowerCase()).eq(bob.address.toLowerCase()); 174 | expect(token.toLowerCase()).eq(erc721.address.toLowerCase()); 175 | expect(mainchainToken.toLowerCase()).eq(charles.address.toLowerCase()); 176 | expect(standard).eq(721); 177 | expect(id.toString()).eq('1'); 178 | }); 179 | 180 | it('should not be able have more than 10 withdrawals', async () => { 181 | await weth.mint(alice.address, ethToWei(100000)); 182 | await weth.approve(sidechainGateway.address, BigNumber.from(2).pow(255)); 183 | 184 | for (let i = 0; i < 10; i++) { 185 | await sidechainGateway.withdrawERC20(weth.address, ethToWei(1).mul(i)); 186 | } 187 | 188 | await expect(sidechainGateway.withdrawERC20(weth.address, ethToWei(1))).reverted; 189 | }); 190 | 191 | it('should be able to return correct pending withdrawal', async () => { 192 | const [ids, entries] = await sidechainGateway.getPendingWithdrawals(alice.address); 193 | expect(ids.length).eq(10); 194 | for (let i = 0; i < 10; i++) { 195 | expect(entries[i].tokenAddress.toLowerCase()).eq(weth.address.toLowerCase()); 196 | expect(entries[i].tokenNumber.toString()).eq(ethToWei(1).mul(i).toString()); 197 | } 198 | }); 199 | 200 | it('should be able to acknowleged token withdrawal', async () => { 201 | await sidechainGateway.acknowledWithdrawalOnMainchain(3); 202 | const [withdrawalIds] = await sidechainGateway.getPendingWithdrawals(alice.address); 203 | expect(withdrawalIds.length).eq(10); 204 | await sidechainGateway.connect(bob).batchAcknowledWithdrawalOnMainchain([3]); 205 | const [ids, entries] = await sidechainGateway.getPendingWithdrawals(alice.address); 206 | expect(ids.length).eq(9); 207 | expect(entries[1].tokenNumber.toString()).eq(ethToWei(1).mul(9).toString()); 208 | }); 209 | 210 | it('should be able to submit withdrawal signatures', async () => { 211 | const hash = withdrawalERC20Hash(1, '0x', '0x', 4); 212 | const { signatures: sig1 } = await getCombinedSignatures(false, [alice], hash); 213 | await sidechainGateway.submitWithdrawalSignatures(0, false, sig1); 214 | const { signatures: sig2 } = await getCombinedSignatures(false, [bob], hash); 215 | await sidechainGateway.connect(bob).batchSubmitWithdrawalSignatures([0], [false], [sig2]); 216 | const firstSigner = await sidechainGateway.withdrawalSigners(0, 0); 217 | expect(firstSigner.toLowerCase()).eq(alice.address.toLowerCase()); 218 | 219 | const aliceSig = await sidechainGateway.withdrawalSig(0, alice.address); 220 | expect(aliceSig).eq(sig1); 221 | const bobSig = await sidechainGateway.withdrawalSig(0, bob.address); 222 | expect(bobSig).eq(sig2); 223 | 224 | const signers = await sidechainGateway.getWithdrawalSigners(0); 225 | expect(signers[0].toLowerCase()).eq(alice.address.toLowerCase()); 226 | expect(signers[1].toLowerCase()).eq(bob.address.toLowerCase()); 227 | 228 | const [, allSig] = await sidechainGateway.getWithdrawalSignatures(0); 229 | expect(allSig[0]).eq(sig1); 230 | expect(allSig[1]).eq(sig2); 231 | }); 232 | 233 | it('should be able to request signature again', async () => { 234 | await expect(sidechainGateway.requestSignatureAgain(0)).reverted; 235 | await sidechainGateway.connect(charles).requestSignatureAgain(0); 236 | }); 237 | }); 238 | 239 | describe('test validator', async () => { 240 | it('Alice, Bob & Charles should be validators', async () => { 241 | const aliceResult = await validator.isValidator(alice.address); 242 | const bobResult = await validator.isValidator(bob.address); 243 | const charlesResult = await validator.isValidator(charles.address); 244 | 245 | expect(aliceResult).to.eq(true); 246 | expect(bobResult).to.eq(true); 247 | expect(charlesResult).to.eq(true); 248 | }); 249 | 250 | it('should not be able to add Dan as a validator because Bob send wrong message', async () => { 251 | await validator.connect(alice).addValidator(0, dan.address); 252 | await validator.connect(bob).addValidator(0, '0x0000000000000000000000000000000000000001'); 253 | const danResult = await validator.isValidator(dan.address); 254 | expect(danResult).to.eq(false); 255 | 256 | await expect(validator.connect(bob).addValidator(0, dan.address)).reverted; 257 | }); 258 | 259 | it('should be able to add Dan as a validator', async () => { 260 | await validator.connect(alice).addValidator(1, dan.address); 261 | await validator.connect(bob).addValidator(1, dan.address); 262 | const danResult = await validator.isValidator(dan.address); 263 | 264 | expect(danResult).to.eq(true); 265 | }); 266 | 267 | it('should not be able to update quorum because Bob would like other quorum', async () => { 268 | await validator.connect(alice).updateQuorum(2, 29, 40); 269 | await validator.connect(bob).updateQuorum(2, 19, 40); 270 | await validator.connect(charles).updateQuorum(2, 29, 40); 271 | 272 | const num = await validator.num(); 273 | const denom = await validator.denom(); 274 | 275 | expect(num.toString()).to.eq('19'); 276 | expect(denom.toString()).to.eq('30'); 277 | }); 278 | 279 | it('should be able to update the quorum successfully', async () => { 280 | await validator.connect(dan).updateQuorum(2, 29, 40); 281 | 282 | const num = await validator.num(); 283 | const denom = await validator.denom(); 284 | expect(num.toString()).to.eq('29'); 285 | expect(denom.toString()).to.eq('40'); 286 | }); 287 | }); 288 | }); 289 | --------------------------------------------------------------------------------