├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── backlog-item.md │ ├── bug_report.md │ └── feature-request-or-epic.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── publish-contracts.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── codechecks.yml ├── contracts ├── Bridge.sol ├── CentrifugeAsset.sol ├── ERC1155Safe.sol ├── ERC20Safe.sol ├── ERC721MinterBurnerPauser.sol ├── ERC721Safe.sol ├── Forwarder.sol ├── Migrations.sol ├── TestContracts.sol ├── handlers │ ├── ERC1155Handler.sol │ ├── ERC20Handler.sol │ ├── ERC721Handler.sol │ ├── GenericHandler.sol │ └── HandlerHelpers.sol ├── interfaces │ ├── IBridge.sol │ ├── IDepositExecute.sol │ ├── IERCHandler.sol │ └── IGenericHandler.sol └── utils │ ├── AccessControl.sol │ ├── Pausable.sol │ ├── SafeCast.sol │ └── SafeMath.sol ├── migrations └── 1_initial_migration.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── scripts ├── .solcover.js ├── compileAbiBin.js ├── create_bindings.sh ├── geth │ ├── genesis.json │ ├── keystore │ │ ├── UTC--2020-04-07T13-50-35.447Z--ff93b45308fd417df303d6515ab04d9e89a750ca │ │ ├── UTC--2020-04-07T13-52-12.564Z--8e0a907331554af72563bd8d43051c2e64be5d35 │ │ ├── UTC--2020-04-07T13-53-49.003Z--24962717f8fa5ba3b931bacaf9ac03924eb475a0 │ │ ├── UTC--2020-04-07T13-55-20.258Z--148ffb2074a9e59ed58142822b3eb3fcbffb0cd7 │ │ └── UTC--2020-04-07T13-56-44.768Z--4ceef6139f00f9f4535ad19640ff7a0137708485 │ ├── password.txt │ ├── run_geth.sh │ ├── start_geth.sh │ └── start_geth2.sh ├── install_deps.sh └── start_ganache.sh ├── src └── index.ts ├── test ├── contractBridge │ ├── admin.js │ ├── cancelDepositProposal.js │ ├── constructor.js │ ├── createDepositProposal.js │ ├── depositERC1155.js │ ├── depositERC20.js │ ├── depositERC721.js │ ├── depositGeneric.js │ ├── executeWithFailedHandler.js │ ├── fee.js │ ├── voteDepositProposal.js │ ├── voteDepositProposalWithRealForwarder.js │ └── voteDepositProposalWithTestForwarder.js ├── e2e │ ├── erc1155 │ │ ├── differentChainsMock.js │ │ └── sameChain.js │ ├── erc20 │ │ ├── differentChainsMock.js │ │ └── sameChain.js │ └── erc721 │ │ ├── differentChainsMock.js │ │ └── sameChain.js ├── forwarder │ └── forwarder.js ├── gasBenchmarks │ ├── deployments.js │ ├── deposits.js │ ├── executeProposal.js │ └── voteProposal.js ├── handlers │ ├── erc1155 │ │ ├── burnList.js │ │ ├── deposit.js │ │ └── depositBurn.js │ ├── erc20 │ │ ├── burnList.js │ │ ├── constructor.js │ │ ├── deposit.js │ │ ├── depositBurn.js │ │ ├── isWhitelisted.js │ │ └── setResourceIDAndContractAddress.js │ ├── erc721 │ │ ├── burnList.js │ │ ├── deposit.js │ │ └── depositBurn.js │ └── generic │ │ ├── constructor.js │ │ ├── deposit.js │ │ └── executeProposal.js ├── helpers.js └── safeCast.js ├── truffle-config.js ├── tsconfig.json └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/backlog-item.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Template For Internal Use 3 | about: Speccing out the details of development for specific features/epics 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | ## Implementation details 13 | 14 | 15 | ## Testing details 16 | 17 | 18 | ## Acceptance Criteria 19 | 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | ## Expected Behavior 13 | 14 | 15 | 16 | ## Current Behavior 17 | 18 | 19 | 20 | ## Possible Solution 21 | 22 | 23 | 24 | ## Steps to Reproduce (for bugs) 25 | 26 | 27 | 1. 28 | 2. 29 | 3. 30 | 4. 31 | 32 | ## Versions 33 | ChainBridge commit (or docker tag): 34 | chainbridge-solidity version: 35 | chainbridge-substrate version: 36 | Go version: 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request-or-epic.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request or epic 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Story 11 | As a 12 | I want 13 | So that I can 14 | 15 | ## Background 16 | 17 | 18 | ## Details 19 | 20 | 21 | ## Scenarios 22 | Scenario: 23 | Given I am 24 | When 25 | And 26 | Then 27 | 28 | ## Implementation details 29 | 30 | 31 | ## Testing details 32 | 33 | 34 | ## Acceptance criteria 35 | 36 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2020 ChainSafe Systems 2 | # SPDX-License-Identifier: LGPL-3.0-only 3 | 4 | version: 2 5 | updates: 6 | # Maintain dependencies for GitHub Actions 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | # Maintain npm dependencies 12 | - package-ecosystem: "npm" 13 | directory: "/" 14 | schedule: 15 | interval: "daily" 16 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Related Issue Or Context 7 | 8 | 9 | 10 | 11 | Closes: # 12 | 13 | ## How Has This Been Tested? Testing details. 14 | 15 | 16 | 17 | 18 | ## Types of changes 19 | 20 | - [ ] Bug fix (non-breaking change which fixes an issue) 21 | - [ ] New feature (non-breaking change which adds functionality) 22 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 23 | - [ ] Documentation 24 | 25 | ## Checklist: 26 | 27 | 28 | - [ ] I have commented my code, particularly in hard-to-understand areas. 29 | - [ ] I have ensured that all acceptance criteria (or expected behavior) from issue are met 30 | - [ ] I have updated the documentation locally and in chainbridge-docs. 31 | - [ ] I have added tests to cover my changes. 32 | - [ ] I have ensured that all the checks are passing and green, I've signed the CLA bot 33 | -------------------------------------------------------------------------------- /.github/workflows/publish-contracts.yml: -------------------------------------------------------------------------------- 1 | name: Release Contracts 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | 8 | jobs: 9 | tag: 10 | name: Check and Tag 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | - name: Create tag 16 | id: tag 17 | uses: butlerlogic/action-autotag@1.1.1 18 | with: 19 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 20 | strategy: package # Optional, since "package" is the default strategy 21 | tag_prefix: "v" 22 | outputs: 23 | tag: ${{ steps.tag.outputs.tagname }} 24 | 25 | release-contracts: 26 | name: Release Contracts 27 | runs-on: ubuntu-latest 28 | needs: tag 29 | if: needs.tag.outputs.tag != '' 30 | steps: 31 | - name: Checkout code 32 | uses: actions/checkout@v2 33 | - name: Setup Nodejs 34 | uses: actions/setup-node@v1 35 | with: 36 | node-version: '12.x' 37 | always-auth: true 38 | registry-url: 'https://registry.npmjs.org' 39 | scope: '@chainsafe' 40 | env: 41 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 42 | - name: Install dependencies 43 | run: yarn install --frozen-lockfile --non-interactive --ignore-optional 44 | env: 45 | NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | 47 | - name: Compile contracts 48 | run: yarn run compile 49 | 50 | - name: Generate types 51 | run: yarn run generate-types 52 | 53 | - name: Build packages 54 | run: yarn run build 55 | 56 | - name: Publish to npm registry 57 | run: yarn publish --ignore-scripts --no-git-tag-version --no-commit-hooks --non-interactive 58 | env: 59 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 60 | 61 | - name: Create Release 62 | id: create_release 63 | uses: actions/create-release@v1 64 | env: 65 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 66 | with: 67 | tag_name: ${{ needs.tag.outputs.tag }} 68 | release_name: ${{ needs.tag.outputs.tag }} 69 | #in case of failure 70 | - name: Rollback on failure 71 | if: failure() 72 | uses: author/action-rollback@9ec72a6af74774e00343c6de3e946b0901c23013 73 | with: 74 | id: ${{ steps.create_release.outputs.id }} 75 | tag: ${{ needs.tag.outputs.tag }} 76 | delete_orphan_tag: true 77 | env: 78 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 79 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened] 6 | 7 | jobs: 8 | test: 9 | name: Test 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | node-version: [12.x] 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v2.4.0 17 | - uses: actions/cache@v2.1.7 18 | with: 19 | path: ~/.npm 20 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 21 | restore-keys: | 22 | ${{ runner.os }}-node- 23 | - name: Install Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v2.5.1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - name: Install dependencies 28 | run: make install-deps 29 | - name: Ganache Tests 30 | run: | 31 | SILENT=true make start-ganache 32 | make test 33 | 34 | coverage: 35 | name: Coverage 36 | runs-on: ubuntu-latest 37 | strategy: 38 | matrix: 39 | node-version: [12.x] 40 | steps: 41 | - name: Checkout code 42 | uses: actions/checkout@v2.4.0 43 | - name: Install Node.js ${{ matrix.node-version }} 44 | uses: actions/setup-node@v2.5.1 45 | with: 46 | node-version: ${{ matrix.node-version }} 47 | - name: NPM install 48 | run: npm install 49 | - name: Run coverage 50 | run: ./node_modules/.bin/truffle run coverage -solcoverjs ./scripts/.solcover.js --network test 51 | - name: Coverall 52 | uses: coverallsapp/github-action@master 53 | with: 54 | github-token: ${{ secrets.GITHUB_TOKEN }} 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | /build 3 | .history/ 4 | node_modules/ 5 | gethdata/ 6 | gethdata2/ 7 | coverage/ 8 | coverage.json 9 | src/ethers 10 | src/web3 11 | ganache-cli/ 12 | dist/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | URL?=http://localhost:8545 2 | 3 | install-deps: 4 | @echo " > \033[32mInstalling dependencies... \033[0m " 5 | ./scripts/install_deps.sh 6 | 7 | .PHONY: test 8 | test: 9 | @echo " > \033[32mTesting contracts... \033[0m " 10 | npx truffle test 11 | 12 | compile: 13 | @echo " > \033[32mCompiling contracts... \033[0m " 14 | npx truffle compile 15 | 16 | start-ganache: 17 | @echo " > \033[32mStarting ganache... \033[0m " 18 | ./scripts/start_ganache.sh 19 | 20 | start-geth: 21 | @echo " > \033[32mStarting geth... \033[0m " 22 | ./scripts/geth/start_geth.sh 23 | 24 | bindings: compile 25 | @echo " > \033[32mCreating go bindings for ethereum contracts... \033[0m " 26 | ./scripts/create_bindings.sh 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chainbridge-solidity 2 | 3 | [![Coverage Status](https://coveralls.io/repos/github/ChainSafe/chainbridge-solidity/badge.svg?branch=master)](https://coveralls.io/github/ChainSafe/chainbridge-solidity?branch=master) 4 | 5 | ChainBridge uses Solidity smart contracts to enable transfers to and from EVM compatible chains. These contracts consist of a core bridge contract (Bridge.sol) and a set of handler contracts (ERC20Handler.sol, ERC721Handler.sol, and GenericHandler.sol). The bridge contract is responsible for initiating, voting on, and executing proposed transfers. The handlers are used by the bridge contract to interact with other existing contracts. 6 | 7 | Read more [here](https://chainbridge.chainsafe.io/). 8 | 9 | A CLI to deploy and interact with these contracts can be found [here](https://github.com/ChainSafe/chainbridge-deploy/tree/master/cb-sol-cli). 10 | 11 | ## Dependencies 12 | 13 | Requires `nodejs` and `npm`. 14 | 15 | ## Commands 16 | 17 | `make install-deps`: Installs truffle and ganache globally, fetches local dependencies. Also installs `abigen` from `go-ethereum`. 18 | 19 | `make bindings`: Creates go bindings in `./build/bindings/go` 20 | 21 | `PORT= SILENT= make start-ganache`: Starts a ganache instance, default `PORT=8545 SILENT=false` 22 | 23 | `QUIET= make start-geth`: Starts a geth instance with test keys 24 | 25 | `PORT= make deploy`: Deploys all contract instances, default `PORT=8545` 26 | 27 | `make test`: Runs truffle tests. 28 | 29 | `make compile`: Compile contracts. 30 | 31 | # ChainSafe Security Policy 32 | 33 | ## Reporting a Security Bug 34 | 35 | We take all security issues seriously, if you believe you have found a security issue within a ChainSafe 36 | project please notify us immediately. If an issue is confirmed, we will take all necessary precautions 37 | to ensure a statement and patch release is made in a timely manner. 38 | 39 | Please email us a description of the flaw and any related information (e.g. reproduction steps, version) to 40 | [security at chainsafe dot io](mailto:security@chainsafe.io). 41 | 42 | 43 | -------------------------------------------------------------------------------- /codechecks.yml: -------------------------------------------------------------------------------- 1 | checks: 2 | - name: eth-gas-reporter/codechecks 3 | -------------------------------------------------------------------------------- /contracts/CentrifugeAsset.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-only 2 | pragma solidity 0.8.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | /** 6 | @title Represents a bridged Centrifuge asset. 7 | @author ChainSafe Systems. 8 | */ 9 | contract CentrifugeAsset { 10 | mapping (bytes32 => bool) public _assetsStored; 11 | 12 | event AssetStored(bytes32 indexed asset); 13 | 14 | /** 15 | @notice Marks {asset} as stored. 16 | @param asset Hash of asset deposited on Centrifuge chain. 17 | @notice {asset} must not have already been stored. 18 | @notice Emits {AssetStored} event. 19 | */ 20 | function store(bytes32 asset) external { 21 | require(!_assetsStored[asset], "asset is already stored"); 22 | 23 | _assetsStored[asset] = true; 24 | emit AssetStored(asset); 25 | } 26 | } -------------------------------------------------------------------------------- /contracts/ERC1155Safe.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-only 2 | pragma solidity 0.8.11; 3 | 4 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 5 | import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; 6 | import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol"; 7 | import "@openzeppelin/contracts/token/ERC1155/presets/ERC1155PresetMinterPauser.sol"; 8 | 9 | /** 10 | @title Manages deposited ERC1155s. 11 | @author ChainSafe Systems. 12 | @notice This contract is intended to be used with ERC1155Handler contract. 13 | */ 14 | contract ERC1155Safe { 15 | using SafeMath for uint256; 16 | 17 | /** 18 | @notice Used to gain custoday of deposited token with batching. 19 | @param tokenAddress Address of ERC1155 to transfer. 20 | @param owner Address of current token owner. 21 | @param recipient Address to transfer token to. 22 | @param tokenIDs IDs of tokens to transfer. 23 | @param amounts Amounts of tokens to transfer. 24 | @param data Additional data. 25 | */ 26 | function lockBatchERC1155(address tokenAddress, address owner, address recipient, uint[] memory tokenIDs, uint[] memory amounts, bytes memory data) internal { 27 | IERC1155 erc1155 = IERC1155(tokenAddress); 28 | erc1155.safeBatchTransferFrom(owner, recipient, tokenIDs, amounts, data); 29 | } 30 | 31 | /** 32 | @notice Transfers custody of token to recipient with batching. 33 | @param tokenAddress Address of ERC1155 to transfer. 34 | @param owner Address of current token owner. 35 | @param recipient Address to transfer token to. 36 | @param tokenIDs IDs of tokens to transfer. 37 | @param amounts Amounts of tokens to transfer. 38 | @param data Additional data. 39 | */ 40 | function releaseBatchERC1155(address tokenAddress, address owner, address recipient, uint256[] memory tokenIDs, uint[] memory amounts, bytes memory data) internal { 41 | IERC1155 erc1155 = IERC1155(tokenAddress); 42 | erc1155.safeBatchTransferFrom(owner, recipient, tokenIDs, amounts, data); 43 | } 44 | 45 | /** 46 | @notice Used to create new ERC1155s with batching. 47 | @param tokenAddress Address of ERC1155 to mint. 48 | @param recipient Address to mint token to. 49 | @param tokenIDs IDs of tokens to mint. 50 | @param amounts Amounts of token to mint. 51 | @param data Additional data. 52 | */ 53 | function mintBatchERC1155(address tokenAddress, address recipient, uint[] memory tokenIDs, uint[] memory amounts, bytes memory data) internal { 54 | ERC1155PresetMinterPauser erc1155 = ERC1155PresetMinterPauser(tokenAddress); 55 | erc1155.mintBatch(recipient, tokenIDs, amounts, data); 56 | } 57 | 58 | /** 59 | @notice Used to burn ERC1155s with batching. 60 | @param tokenAddress Address of ERC1155 to burn. 61 | @param tokenIDs IDs of tokens to burn. 62 | @param amounts Amounts of tokens to burn. 63 | */ 64 | function burnBatchERC1155(address tokenAddress, address owner, uint[] memory tokenIDs, uint[] memory amounts) internal { 65 | ERC1155Burnable erc1155 = ERC1155Burnable(tokenAddress); 66 | erc1155.burnBatch(owner, tokenIDs, amounts); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /contracts/ERC20Safe.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-only 2 | pragma solidity 0.8.11; 3 | 4 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; 7 | import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 8 | 9 | /** 10 | @title Manages deposited ERC20s. 11 | @author ChainSafe Systems. 12 | @notice This contract is intended to be used with ERC20Handler contract. 13 | */ 14 | contract ERC20Safe { 15 | using SafeMath for uint256; 16 | 17 | /** 18 | @notice Used to gain custody of deposited token. 19 | @param tokenAddress Address of ERC20 to transfer. 20 | @param owner Address of current token owner. 21 | @param recipient Address to transfer tokens to. 22 | @param amount Amount of tokens to transfer. 23 | */ 24 | function lockERC20(address tokenAddress, address owner, address recipient, uint256 amount) internal { 25 | IERC20 erc20 = IERC20(tokenAddress); 26 | _safeTransferFrom(erc20, owner, recipient, amount); 27 | } 28 | 29 | /** 30 | @notice Transfers custody of token to recipient. 31 | @param tokenAddress Address of ERC20 to transfer. 32 | @param recipient Address to transfer tokens to. 33 | @param amount Amount of tokens to transfer. 34 | */ 35 | function releaseERC20(address tokenAddress, address recipient, uint256 amount) internal { 36 | IERC20 erc20 = IERC20(tokenAddress); 37 | _safeTransfer(erc20, recipient, amount); 38 | } 39 | 40 | /** 41 | @notice Used to create new ERC20s. 42 | @param tokenAddress Address of ERC20 to transfer. 43 | @param recipient Address to mint token to. 44 | @param amount Amount of token to mint. 45 | */ 46 | function mintERC20(address tokenAddress, address recipient, uint256 amount) internal { 47 | ERC20PresetMinterPauser erc20 = ERC20PresetMinterPauser(tokenAddress); 48 | erc20.mint(recipient, amount); 49 | 50 | } 51 | 52 | /** 53 | @notice Used to burn ERC20s. 54 | @param tokenAddress Address of ERC20 to burn. 55 | @param owner Current owner of tokens. 56 | @param amount Amount of tokens to burn. 57 | */ 58 | function burnERC20(address tokenAddress, address owner, uint256 amount) internal { 59 | ERC20Burnable erc20 = ERC20Burnable(tokenAddress); 60 | erc20.burnFrom(owner, amount); 61 | } 62 | 63 | /** 64 | @notice used to transfer ERC20s safely 65 | @param token Token instance to transfer 66 | @param to Address to transfer token to 67 | @param value Amount of token to transfer 68 | */ 69 | function _safeTransfer(IERC20 token, address to, uint256 value) private { 70 | _safeCall(token, abi.encodeWithSelector(token.transfer.selector, to, value)); 71 | } 72 | 73 | 74 | /** 75 | @notice used to transfer ERC20s safely 76 | @param token Token instance to transfer 77 | @param from Address to transfer token from 78 | @param to Address to transfer token to 79 | @param value Amount of token to transfer 80 | */ 81 | function _safeTransferFrom(IERC20 token, address from, address to, uint256 value) private { 82 | _safeCall(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); 83 | } 84 | 85 | /** 86 | @notice used to make calls to ERC20s safely 87 | @param token Token instance call targets 88 | @param data encoded call data 89 | */ 90 | function _safeCall(IERC20 token, bytes memory data) private { 91 | uint256 tokenSize; 92 | assembly { 93 | tokenSize := extcodesize(token) 94 | } 95 | require(tokenSize > 0, "ERC20: not a contract"); 96 | 97 | (bool success, bytes memory returndata) = address(token).call(data); 98 | require(success, "ERC20: call failed"); 99 | 100 | if (returndata.length > 0) { 101 | 102 | require(abi.decode(returndata, (bool)), "ERC20: operation did not succeed"); 103 | } 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /contracts/ERC721MinterBurnerPauser.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-only 2 | pragma solidity 0.8.11; 3 | 4 | // This is adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.0/contracts/presets/ERC721PresetMinterPauserAutoId.sol 5 | 6 | import "./utils/AccessControl.sol"; 7 | import "@openzeppelin/contracts/utils/Context.sol"; 8 | import "@openzeppelin/contracts/utils/Counters.sol"; 9 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; 10 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Pausable.sol"; 11 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; 12 | 13 | contract ERC721MinterBurnerPauser is Context, AccessControl, ERC721Burnable, ERC721Pausable, ERC721URIStorage { 14 | bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); 15 | bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); 16 | 17 | string public baseURI; 18 | 19 | /** 20 | * @dev Grants `DEFAULT_ADMIN_ROLE` and `MINTER_ROLE`to the account that 21 | * deploys the contract. 22 | * 23 | * Token URIs will be autogenerated based on `baseURI` and their token IDs. 24 | * See {ERC721-tokenURI}. 25 | */ 26 | constructor(string memory name, string memory symbol, string memory baseURI) public ERC721(name, symbol) { 27 | _setupRole(DEFAULT_ADMIN_ROLE, _msgSender()); 28 | 29 | _setupRole(MINTER_ROLE, _msgSender()); 30 | _setupRole(PAUSER_ROLE, _msgSender()); 31 | 32 | _setBaseURI(baseURI); 33 | } 34 | 35 | /** 36 | * @dev Creates a new token for `to`. Its token ID will be automatically 37 | * assigned (and available on the emitted {Transfer} event), and the token 38 | * URI autogenerated based on the base URI passed at construction. 39 | * 40 | * See {ERC721-_mint}. 41 | * 42 | * Requirements: 43 | * 44 | * - the caller must have the `MINTER_ROLE`. 45 | */ 46 | function mint(address to, uint256 tokenId, string memory _data) public { 47 | require(hasRole(MINTER_ROLE, _msgSender()), "ERC721MinterBurnerPauser: must have minter role to mint"); 48 | 49 | _mint(to, tokenId); 50 | _setTokenURI(tokenId, _data); 51 | } 52 | 53 | /** 54 | * @dev Pauses all token transfers. 55 | * 56 | * See {ERC721Pausable} and {Pausable-_pause}. 57 | * 58 | * Requirements: 59 | * 60 | * - the caller must have the `PAUSER_ROLE`. 61 | */ 62 | function pause() public { 63 | require(hasRole(PAUSER_ROLE, _msgSender()), "ERC721MinterBurnerPauser: must have pauser role to pause"); 64 | _pause(); 65 | } 66 | 67 | /** 68 | * @dev Unpauses all token transfers. 69 | * 70 | * See {ERC721Pausable} and {Pausable-_unpause}. 71 | * 72 | * Requirements: 73 | * 74 | * - the caller must have the `PAUSER_ROLE`. 75 | */ 76 | function unpause() public { 77 | require(hasRole(PAUSER_ROLE, _msgSender()), "ERC721MinterBurnerPauser: must have pauser role to unpause"); 78 | _unpause(); 79 | } 80 | 81 | function tokenURI(uint256 tokenId) public view virtual override(ERC721, ERC721URIStorage) returns (string memory) { 82 | return super.tokenURI(tokenId); 83 | } 84 | 85 | function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override(ERC721, ERC721Pausable) { 86 | super._beforeTokenTransfer(from, to, tokenId); 87 | } 88 | 89 | function _setBaseURI(string memory baseURI_) internal { 90 | baseURI = baseURI_; 91 | } 92 | 93 | function _baseURI() internal view virtual override returns (string memory) { 94 | return baseURI; 95 | } 96 | 97 | function _burn(uint256 tokenId) internal virtual override(ERC721, ERC721URIStorage) { 98 | super._burn(tokenId); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /contracts/ERC721Safe.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-only 2 | pragma solidity 0.8.11; 3 | 4 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 5 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 6 | import "./ERC721MinterBurnerPauser.sol"; 7 | 8 | /** 9 | @title Manages deposited ERC721s. 10 | @author ChainSafe Systems. 11 | @notice This contract is intended to be used with ERC721Handler contract. 12 | */ 13 | contract ERC721Safe { 14 | using SafeMath for uint256; 15 | 16 | /** 17 | @notice Used to gain custoday of deposited token. 18 | @param tokenAddress Address of ERC721 to transfer. 19 | @param owner Address of current token owner. 20 | @param recipient Address to transfer token to. 21 | @param tokenID ID of token to transfer. 22 | */ 23 | function lockERC721(address tokenAddress, address owner, address recipient, uint tokenID) internal { 24 | IERC721 erc721 = IERC721(tokenAddress); 25 | erc721.transferFrom(owner, recipient, tokenID); 26 | 27 | } 28 | 29 | /** 30 | @notice Transfers custody of token to recipient. 31 | @param tokenAddress Address of ERC721 to transfer. 32 | @param owner Address of current token owner. 33 | @param recipient Address to transfer token to. 34 | @param tokenID ID of token to transfer. 35 | */ 36 | function releaseERC721(address tokenAddress, address owner, address recipient, uint256 tokenID) internal { 37 | IERC721 erc721 = IERC721(tokenAddress); 38 | erc721.transferFrom(owner, recipient, tokenID); 39 | } 40 | 41 | /** 42 | @notice Used to create new ERC721s. 43 | @param tokenAddress Address of ERC721 to mint. 44 | @param recipient Address to mint token to. 45 | @param tokenID ID of token to mint. 46 | @param data Optional data to send along with mint call. 47 | */ 48 | function mintERC721(address tokenAddress, address recipient, uint256 tokenID, bytes memory data) internal { 49 | ERC721MinterBurnerPauser erc721 = ERC721MinterBurnerPauser(tokenAddress); 50 | erc721.mint(recipient, tokenID, string(data)); 51 | } 52 | 53 | /** 54 | @notice Used to burn ERC721s. 55 | @param tokenAddress Address of ERC721 to burn. 56 | @param tokenID ID of token to burn. 57 | */ 58 | function burnERC721(address tokenAddress, address owner, uint256 tokenID) internal { 59 | ERC721MinterBurnerPauser erc721 = ERC721MinterBurnerPauser(tokenAddress); 60 | require(erc721.ownerOf(tokenID) == owner, 'Burn not from owner'); 61 | erc721.burn(tokenID); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /contracts/Forwarder.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-only 2 | pragma solidity 0.8.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 6 | import "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol"; 7 | 8 | /** 9 | @notice This contract refers to Openzeppelin's MinimalForwarder contract. 10 | */ 11 | contract Forwarder is EIP712 { 12 | using ECDSA for bytes32; 13 | 14 | struct ForwardRequest { 15 | address from; 16 | address to; 17 | uint256 value; 18 | uint256 gas; 19 | uint256 nonce; 20 | bytes data; 21 | } 22 | 23 | bytes32 private constant _TYPEHASH = 24 | keccak256("ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,bytes data)"); 25 | 26 | mapping(address => uint256) private _nonces; 27 | 28 | constructor() EIP712("Forwarder", "0.0.1") public {} 29 | 30 | function getNonce(address from) public view returns (uint256) { 31 | return _nonces[from]; 32 | } 33 | 34 | function verify(ForwardRequest calldata req, bytes calldata signature) public view returns (bool) { 35 | address signer = _hashTypedDataV4( 36 | keccak256(abi.encode(_TYPEHASH, req.from, req.to, req.value, req.gas, req.nonce, keccak256(req.data))) 37 | ).recover(signature); 38 | return _nonces[req.from] == req.nonce && signer == req.from; 39 | } 40 | 41 | function execute(ForwardRequest calldata req, bytes calldata signature) 42 | public 43 | payable 44 | returns (bool, bytes memory) 45 | { 46 | require(verify(req, signature), "MinimalForwarder: signature does not match request"); 47 | _nonces[req.from] = req.nonce + 1; 48 | 49 | (bool success, bytes memory returndata) = req.to.call{gas: req.gas, value: req.value}( 50 | abi.encodePacked(req.data, req.from) 51 | ); 52 | 53 | assert(gasleft() > req.gas / 63); 54 | 55 | return (success, returndata); 56 | } 57 | } -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-only 2 | pragma solidity 0.8.11; 3 | 4 | contract Migrations { 5 | address public owner; 6 | uint public last_completed_migration; 7 | 8 | constructor() public { 9 | owner = msg.sender; 10 | } 11 | 12 | modifier restricted() { 13 | if (msg.sender == owner) _; 14 | } 15 | 16 | function setCompleted(uint completed) public restricted { 17 | last_completed_migration = completed; 18 | } 19 | 20 | function upgrade(address new_address) public restricted { 21 | Migrations upgraded = Migrations(new_address); 22 | upgraded.setCompleted(last_completed_migration); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/TestContracts.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-only 2 | pragma solidity 0.8.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "./utils/SafeCast.sol"; 6 | import "./handlers/HandlerHelpers.sol"; 7 | 8 | contract NoArgument { 9 | event NoArgumentCalled(); 10 | 11 | function noArgument() external { 12 | emit NoArgumentCalled(); 13 | } 14 | } 15 | 16 | contract OneArgument { 17 | event OneArgumentCalled(uint256 indexed argumentOne); 18 | 19 | function oneArgument(uint256 argumentOne) external { 20 | emit OneArgumentCalled(argumentOne); 21 | } 22 | } 23 | 24 | contract TwoArguments { 25 | event TwoArgumentsCalled(address[] argumentOne, bytes4 argumentTwo); 26 | 27 | function twoArguments(address[] calldata argumentOne, bytes4 argumentTwo) external { 28 | emit TwoArgumentsCalled(argumentOne, argumentTwo); 29 | } 30 | } 31 | 32 | contract ThreeArguments { 33 | event ThreeArgumentsCalled(string argumentOne, int8 argumentTwo, bool argumentThree); 34 | 35 | function threeArguments(string calldata argumentOne, int8 argumentTwo, bool argumentThree) external { 36 | emit ThreeArgumentsCalled(argumentOne, argumentTwo, argumentThree); 37 | } 38 | } 39 | 40 | contract WithDepositer { 41 | event WithDepositerCalled(address argumentOne, uint256 argumentTwo); 42 | 43 | function withDepositer(address argumentOne, uint256 argumentTwo) external { 44 | emit WithDepositerCalled(argumentOne, argumentTwo); 45 | } 46 | } 47 | 48 | contract SafeCaster { 49 | using SafeCast for *; 50 | 51 | function toUint200(uint input) external pure returns(uint200) { 52 | return input.toUint200(); 53 | } 54 | } 55 | 56 | contract ReturnData { 57 | function returnData(string memory argument) external pure returns(bytes32 response) { 58 | assembly { 59 | response := mload(add(argument, 32)) 60 | } 61 | } 62 | } 63 | 64 | contract HandlerRevert is HandlerHelpers { 65 | uint private _totalAmount; 66 | 67 | constructor( 68 | address bridgeAddress 69 | ) public HandlerHelpers(bridgeAddress) { 70 | } 71 | 72 | function executeProposal(bytes32, bytes calldata) external view { 73 | if (_totalAmount == 0) { 74 | revert('Something bad happened'); 75 | } 76 | return; 77 | } 78 | 79 | function virtualIncreaseBalance(uint amount) external { 80 | _totalAmount = amount; 81 | } 82 | } 83 | 84 | contract TestForwarder { 85 | function execute(bytes memory data, address to, address sender) external { 86 | bytes memory callData = abi.encodePacked(data, sender); 87 | (bool success, ) = to.call(callData); 88 | require(success, "Relay call failed"); 89 | } 90 | } 91 | 92 | contract TestTarget { 93 | uint public calls = 0; 94 | uint public gasLeft; 95 | bytes public data; 96 | bool public burnAllGas; 97 | fallback() external payable { 98 | gasLeft = gasleft(); 99 | calls++; 100 | data = msg.data; 101 | if (burnAllGas) { 102 | assert(false); 103 | } 104 | } 105 | 106 | function setBurnAllGas() public { 107 | burnAllGas = true; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /contracts/handlers/ERC1155Handler.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-only 2 | pragma solidity 0.8.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "../interfaces/IDepositExecute.sol"; 6 | import "./HandlerHelpers.sol"; 7 | import "../ERC1155Safe.sol"; 8 | import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; 9 | import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; 10 | import "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol"; 11 | 12 | contract ERC1155Handler is IDepositExecute, HandlerHelpers, ERC1155Safe, ERC1155Holder { 13 | using ERC165Checker for address; 14 | 15 | bytes4 private constant _INTERFACE_ERC1155_METADATA = 0x0e89341c; 16 | bytes private constant EMPTY_BYTES = ""; 17 | 18 | /** 19 | @param bridgeAddress Contract address of previously deployed Bridge. 20 | */ 21 | constructor( 22 | address bridgeAddress 23 | ) public HandlerHelpers(bridgeAddress) { 24 | } 25 | 26 | /** 27 | @notice A deposit is initiatied by making a deposit in the Bridge contract. 28 | @param resourceID ResourceID used to find address of token to be used for deposit. 29 | @param depositer Address of account making the deposit in the Bridge contract. 30 | @param data Consists of ABI-encoded arrays of tokenIDs and amounts. 31 | */ 32 | function deposit(bytes32 resourceID, address depositer, bytes calldata data) external override onlyBridge returns (bytes memory metaData) { 33 | uint[] memory tokenIDs; 34 | uint[] memory amounts; 35 | 36 | (tokenIDs, amounts) = abi.decode(data, (uint[], uint[])); 37 | 38 | address tokenAddress = _resourceIDToTokenContractAddress[resourceID]; 39 | require(tokenAddress != address(0), "provided resourceID does not exist"); 40 | 41 | if (_burnList[tokenAddress]) { 42 | burnBatchERC1155(tokenAddress, depositer, tokenIDs, amounts); 43 | } else { 44 | lockBatchERC1155(tokenAddress, depositer, address(this), tokenIDs, amounts, EMPTY_BYTES); 45 | } 46 | } 47 | 48 | /** 49 | @notice Proposal execution should be initiated when a proposal is finalized in the Bridge contract. 50 | by a relayer on the deposit's destination chain. 51 | @param data Consists of ABI-encoded {tokenIDs}, {amounts}, {recipient}, 52 | and {transferData} of types uint[], uint[], bytes, bytes. 53 | */ 54 | function executeProposal(bytes32 resourceID, bytes calldata data) external override onlyBridge { 55 | uint[] memory tokenIDs; 56 | uint[] memory amounts; 57 | bytes memory recipient; 58 | bytes memory transferData; 59 | 60 | (tokenIDs, amounts, recipient, transferData) = abi.decode(data, (uint[], uint[], bytes, bytes)); 61 | 62 | bytes20 recipientAddress; 63 | 64 | assembly { 65 | recipientAddress := mload(add(recipient, 0x20)) 66 | } 67 | 68 | address tokenAddress = _resourceIDToTokenContractAddress[resourceID]; 69 | require(_contractWhitelist[address(tokenAddress)], "provided tokenAddress is not whitelisted"); 70 | 71 | if (_burnList[tokenAddress]) { 72 | mintBatchERC1155(tokenAddress, address(recipientAddress), tokenIDs, amounts, transferData); 73 | } else { 74 | releaseBatchERC1155(tokenAddress, address(this), address(recipientAddress), tokenIDs, amounts, transferData); 75 | } 76 | } 77 | 78 | /** 79 | @notice Used to manually release ERC1155 tokens from ERC1155Safe. 80 | @param data Consists of ABI-encoded {tokenAddress}, {recipient}, {tokenIDs}, 81 | {amounts}, and {transferData} of types address, address, uint[], uint[], bytes. 82 | */ 83 | function withdraw(bytes memory data) external override onlyBridge { 84 | address tokenAddress; 85 | address recipient; 86 | uint[] memory tokenIDs; 87 | uint[] memory amounts; 88 | bytes memory transferData; 89 | 90 | (tokenAddress, recipient, tokenIDs, amounts, transferData) = abi.decode(data, (address, address, uint[], uint[], bytes)); 91 | 92 | releaseBatchERC1155(tokenAddress, address(this), recipient, tokenIDs, amounts, transferData); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /contracts/handlers/ERC20Handler.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-only 2 | pragma solidity 0.8.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "../interfaces/IDepositExecute.sol"; 6 | import "./HandlerHelpers.sol"; 7 | import "../ERC20Safe.sol"; 8 | 9 | /** 10 | @title Handles ERC20 deposits and deposit executions. 11 | @author ChainSafe Systems. 12 | @notice This contract is intended to be used with the Bridge contract. 13 | */ 14 | contract ERC20Handler is IDepositExecute, HandlerHelpers, ERC20Safe { 15 | /** 16 | @param bridgeAddress Contract address of previously deployed Bridge. 17 | */ 18 | constructor( 19 | address bridgeAddress 20 | ) public HandlerHelpers(bridgeAddress) { 21 | } 22 | 23 | /** 24 | @notice A deposit is initiatied by making a deposit in the Bridge contract. 25 | @param resourceID ResourceID used to find address of token to be used for deposit. 26 | @param depositer Address of account making the deposit in the Bridge contract. 27 | @param data Consists of {amount} padded to 32 bytes. 28 | @notice Data passed into the function should be constructed as follows: 29 | amount uint256 bytes 0 - 32 30 | @dev Depending if the corresponding {tokenAddress} for the parsed {resourceID} is 31 | marked true in {_burnList}, deposited tokens will be burned, if not, they will be locked. 32 | @return an empty data. 33 | */ 34 | function deposit( 35 | bytes32 resourceID, 36 | address depositer, 37 | bytes calldata data 38 | ) external override onlyBridge returns (bytes memory) { 39 | uint256 amount; 40 | (amount) = abi.decode(data, (uint)); 41 | 42 | address tokenAddress = _resourceIDToTokenContractAddress[resourceID]; 43 | require(_contractWhitelist[tokenAddress], "provided tokenAddress is not whitelisted"); 44 | 45 | if (_burnList[tokenAddress]) { 46 | burnERC20(tokenAddress, depositer, amount); 47 | } else { 48 | lockERC20(tokenAddress, depositer, address(this), amount); 49 | } 50 | } 51 | 52 | /** 53 | @notice Proposal execution should be initiated when a proposal is finalized in the Bridge contract. 54 | by a relayer on the deposit's destination chain. 55 | @param data Consists of {resourceID}, {amount}, {lenDestinationRecipientAddress}, 56 | and {destinationRecipientAddress} all padded to 32 bytes. 57 | @notice Data passed into the function should be constructed as follows: 58 | amount uint256 bytes 0 - 32 59 | destinationRecipientAddress length uint256 bytes 32 - 64 60 | destinationRecipientAddress bytes bytes 64 - END 61 | */ 62 | function executeProposal(bytes32 resourceID, bytes calldata data) external override onlyBridge { 63 | uint256 amount; 64 | uint256 lenDestinationRecipientAddress; 65 | bytes memory destinationRecipientAddress; 66 | 67 | (amount, lenDestinationRecipientAddress) = abi.decode(data, (uint, uint)); 68 | destinationRecipientAddress = bytes(data[64:64 + lenDestinationRecipientAddress]); 69 | 70 | bytes20 recipientAddress; 71 | address tokenAddress = _resourceIDToTokenContractAddress[resourceID]; 72 | 73 | assembly { 74 | recipientAddress := mload(add(destinationRecipientAddress, 0x20)) 75 | } 76 | 77 | require(_contractWhitelist[tokenAddress], "provided tokenAddress is not whitelisted"); 78 | 79 | if (_burnList[tokenAddress]) { 80 | mintERC20(tokenAddress, address(recipientAddress), amount); 81 | } else { 82 | releaseERC20(tokenAddress, address(recipientAddress), amount); 83 | } 84 | } 85 | 86 | /** 87 | @notice Used to manually release ERC20 tokens from ERC20Safe. 88 | @param data Consists of {tokenAddress}, {recipient}, and {amount} all padded to 32 bytes. 89 | @notice Data passed into the function should be constructed as follows: 90 | tokenAddress address bytes 0 - 32 91 | recipient address bytes 32 - 64 92 | amount uint bytes 64 - 96 93 | */ 94 | function withdraw(bytes memory data) external override onlyBridge { 95 | address tokenAddress; 96 | address recipient; 97 | uint amount; 98 | 99 | (tokenAddress, recipient, amount) = abi.decode(data, (address, address, uint)); 100 | 101 | releaseERC20(tokenAddress, recipient, amount); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /contracts/handlers/ERC721Handler.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-only 2 | pragma solidity 0.8.11; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "../interfaces/IDepositExecute.sol"; 6 | import "./HandlerHelpers.sol"; 7 | import "../ERC721Safe.sol"; 8 | import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; 9 | import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; 10 | 11 | 12 | /** 13 | @title Handles ERC721 deposits and deposit executions. 14 | @author ChainSafe Systems. 15 | @notice This contract is intended to be used with the Bridge contract. 16 | */ 17 | contract ERC721Handler is IDepositExecute, HandlerHelpers, ERC721Safe { 18 | using ERC165Checker for address; 19 | 20 | bytes4 private constant _INTERFACE_ERC721_METADATA = 0x5b5e139f; 21 | 22 | /** 23 | @param bridgeAddress Contract address of previously deployed Bridge. 24 | */ 25 | constructor( 26 | address bridgeAddress 27 | ) public HandlerHelpers(bridgeAddress) { 28 | } 29 | 30 | /** 31 | @notice A deposit is initiatied by making a deposit in the Bridge contract. 32 | @param resourceID ResourceID used to find address of token to be used for deposit. 33 | @param depositer Address of account making the deposit in the Bridge contract. 34 | @param data Consists of {tokenID} padded to 32 bytes. 35 | @notice Data passed into the function should be constructed as follows: 36 | tokenID uint256 bytes 0 - 32 37 | @notice If the corresponding {tokenAddress} for the parsed {resourceID} supports {_INTERFACE_ERC721_METADATA}, 38 | then {metaData} will be set according to the {tokenURI} method in the token contract. 39 | @dev Depending if the corresponding {tokenAddress} for the parsed {resourceID} is 40 | marked true in {_burnList}, deposited tokens will be burned, if not, they will be locked. 41 | @return metaData : the deposited token metadata acquired by calling a {tokenURI} method in the token contract. 42 | */ 43 | function deposit(bytes32 resourceID, 44 | address depositer, 45 | bytes calldata data 46 | ) external override onlyBridge returns (bytes memory metaData) { 47 | uint tokenID; 48 | 49 | (tokenID) = abi.decode(data, (uint)); 50 | 51 | address tokenAddress = _resourceIDToTokenContractAddress[resourceID]; 52 | require(_contractWhitelist[tokenAddress], "provided tokenAddress is not whitelisted"); 53 | 54 | // Check if the contract supports metadata, fetch it if it does 55 | if (tokenAddress.supportsInterface(_INTERFACE_ERC721_METADATA)) { 56 | IERC721Metadata erc721 = IERC721Metadata(tokenAddress); 57 | metaData = bytes(erc721.tokenURI(tokenID)); 58 | } 59 | 60 | if (_burnList[tokenAddress]) { 61 | burnERC721(tokenAddress, depositer, tokenID); 62 | } else { 63 | lockERC721(tokenAddress, depositer, address(this), tokenID); 64 | } 65 | } 66 | 67 | /** 68 | @notice Proposal execution should be initiated when a proposal is finalized in the Bridge contract. 69 | by a relayer on the deposit's destination chain. 70 | @param data Consists of {tokenID}, {resourceID}, {lenDestinationRecipientAddress}, 71 | {destinationRecipientAddress}, {lenMeta}, and {metaData} all padded to 32 bytes. 72 | @notice Data passed into the function should be constructed as follows: 73 | tokenID uint256 bytes 0 - 32 74 | destinationRecipientAddress length uint256 bytes 32 - 64 75 | destinationRecipientAddress bytes bytes 64 - (64 + len(destinationRecipientAddress)) 76 | metadata length uint256 bytes (64 + len(destinationRecipientAddress)) - (64 + len(destinationRecipientAddress) + 32) 77 | metadata bytes bytes (64 + len(destinationRecipientAddress) + 32) - END 78 | */ 79 | function executeProposal(bytes32 resourceID, bytes calldata data) external override onlyBridge { 80 | uint tokenID; 81 | uint lenDestinationRecipientAddress; 82 | bytes memory destinationRecipientAddress; 83 | uint offsetMetaData; 84 | uint lenMetaData; 85 | bytes memory metaData; 86 | 87 | (tokenID, lenDestinationRecipientAddress) = abi.decode(data, (uint, uint)); 88 | offsetMetaData = 64 + lenDestinationRecipientAddress; 89 | destinationRecipientAddress = bytes(data[64:offsetMetaData]); 90 | lenMetaData = abi.decode(data[offsetMetaData:], (uint)); 91 | metaData = bytes(data[offsetMetaData + 32:offsetMetaData + 32 + lenMetaData]); 92 | 93 | bytes20 recipientAddress; 94 | 95 | assembly { 96 | recipientAddress := mload(add(destinationRecipientAddress, 0x20)) 97 | } 98 | 99 | address tokenAddress = _resourceIDToTokenContractAddress[resourceID]; 100 | require(_contractWhitelist[address(tokenAddress)], "provided tokenAddress is not whitelisted"); 101 | 102 | if (_burnList[tokenAddress]) { 103 | mintERC721(tokenAddress, address(recipientAddress), tokenID, metaData); 104 | } else { 105 | releaseERC721(tokenAddress, address(this), address(recipientAddress), tokenID); 106 | } 107 | } 108 | 109 | /** 110 | @notice Used to manually release ERC721 tokens from ERC721Safe. 111 | @param data Consists of {tokenAddress}, {recipient}, and {tokenID} all padded to 32 bytes. 112 | @notice Data passed into the function should be constructed as follows: 113 | tokenAddress address bytes 0 - 32 114 | recipient address bytes 32 - 64 115 | tokenID uint bytes 64 - 96 116 | */ 117 | function withdraw(bytes memory data) external override onlyBridge { 118 | address tokenAddress; 119 | address recipient; 120 | uint tokenID; 121 | 122 | (tokenAddress, recipient, tokenID) = abi.decode(data, (address, address, uint)); 123 | 124 | releaseERC721(tokenAddress, address(this), recipient, tokenID); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /contracts/handlers/HandlerHelpers.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-only 2 | pragma solidity 0.8.11; 3 | 4 | import "../interfaces/IERCHandler.sol"; 5 | 6 | /** 7 | @title Function used across handler contracts. 8 | @author ChainSafe Systems. 9 | @notice This contract is intended to be used with the Bridge contract. 10 | */ 11 | contract HandlerHelpers is IERCHandler { 12 | address public immutable _bridgeAddress; 13 | 14 | // resourceID => token contract address 15 | mapping (bytes32 => address) public _resourceIDToTokenContractAddress; 16 | 17 | // token contract address => resourceID 18 | mapping (address => bytes32) public _tokenContractAddressToResourceID; 19 | 20 | // token contract address => is whitelisted 21 | mapping (address => bool) public _contractWhitelist; 22 | 23 | // token contract address => is burnable 24 | mapping (address => bool) public _burnList; 25 | 26 | modifier onlyBridge() { 27 | _onlyBridge(); 28 | _; 29 | } 30 | 31 | /** 32 | @param bridgeAddress Contract address of previously deployed Bridge. 33 | */ 34 | constructor( 35 | address bridgeAddress 36 | ) public { 37 | _bridgeAddress = bridgeAddress; 38 | } 39 | 40 | function _onlyBridge() private view { 41 | require(msg.sender == _bridgeAddress, "sender must be bridge contract"); 42 | } 43 | 44 | /** 45 | @notice First verifies {_resourceIDToContractAddress}[{resourceID}] and 46 | {_contractAddressToResourceID}[{contractAddress}] are not already set, 47 | then sets {_resourceIDToContractAddress} with {contractAddress}, 48 | {_contractAddressToResourceID} with {resourceID}, 49 | and {_contractWhitelist} to true for {contractAddress}. 50 | @param resourceID ResourceID to be used when making deposits. 51 | @param contractAddress Address of contract to be called when a deposit is made and a deposited is executed. 52 | */ 53 | function setResource(bytes32 resourceID, address contractAddress) external override onlyBridge { 54 | 55 | _setResource(resourceID, contractAddress); 56 | } 57 | 58 | /** 59 | @notice First verifies {contractAddress} is whitelisted, then sets {_burnList}[{contractAddress}] 60 | to true. 61 | @param contractAddress Address of contract to be used when making or executing deposits. 62 | */ 63 | function setBurnable(address contractAddress) external override onlyBridge{ 64 | _setBurnable(contractAddress); 65 | } 66 | 67 | function withdraw(bytes memory data) external virtual override {} 68 | 69 | function _setResource(bytes32 resourceID, address contractAddress) internal { 70 | _resourceIDToTokenContractAddress[resourceID] = contractAddress; 71 | _tokenContractAddressToResourceID[contractAddress] = resourceID; 72 | 73 | _contractWhitelist[contractAddress] = true; 74 | } 75 | 76 | function _setBurnable(address contractAddress) internal { 77 | require(_contractWhitelist[contractAddress], "provided contract is not whitelisted"); 78 | _burnList[contractAddress] = true; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /contracts/interfaces/IBridge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-only 2 | pragma solidity 0.8.11; 3 | 4 | /** 5 | @title Interface for Bridge contract. 6 | @author ChainSafe Systems. 7 | */ 8 | interface IBridge { 9 | /** 10 | @notice Exposing getter for {_domainID} instead of forcing the use of call. 11 | @return uint8 The {_domainID} that is currently set for the Bridge contract. 12 | */ 13 | function _domainID() external returns (uint8); 14 | } -------------------------------------------------------------------------------- /contracts/interfaces/IDepositExecute.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-only 2 | pragma solidity 0.8.11; 3 | 4 | /** 5 | @title Interface for handler contracts that support deposits and deposit executions. 6 | @author ChainSafe Systems. 7 | */ 8 | interface IDepositExecute { 9 | /** 10 | @notice It is intended that deposit are made using the Bridge contract. 11 | @param depositer Address of account making the deposit in the Bridge contract. 12 | @param data Consists of additional data needed for a specific deposit. 13 | */ 14 | function deposit(bytes32 resourceID, address depositer, bytes calldata data) external returns (bytes memory); 15 | 16 | /** 17 | @notice It is intended that proposals are executed by the Bridge contract. 18 | @param data Consists of additional data needed for a specific deposit execution. 19 | */ 20 | function executeProposal(bytes32 resourceID, bytes calldata data) external; 21 | } 22 | -------------------------------------------------------------------------------- /contracts/interfaces/IERCHandler.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-only 2 | pragma solidity 0.8.11; 3 | 4 | /** 5 | @title Interface to be used with handlers that support ERC20s and ERC721s. 6 | @author ChainSafe Systems. 7 | */ 8 | interface IERCHandler { 9 | /** 10 | @notice Correlates {resourceID} with {contractAddress}. 11 | @param resourceID ResourceID to be used when making deposits. 12 | @param contractAddress Address of contract to be called when a deposit is made and a deposited is executed. 13 | */ 14 | function setResource(bytes32 resourceID, address contractAddress) external; 15 | /** 16 | @notice Marks {contractAddress} as mintable/burnable. 17 | @param contractAddress Address of contract to be used when making or executing deposits. 18 | */ 19 | function setBurnable(address contractAddress) external; 20 | 21 | /** 22 | @notice Withdraw funds from ERC safes. 23 | @param data ABI-encoded withdrawal params relevant to the handler. 24 | */ 25 | function withdraw(bytes memory data) external; 26 | } 27 | -------------------------------------------------------------------------------- /contracts/interfaces/IGenericHandler.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-3.0-only 2 | pragma solidity 0.8.11; 3 | 4 | /** 5 | @title Interface for handler that handles generic deposits and deposit executions. 6 | @author ChainSafe Systems. 7 | */ 8 | interface IGenericHandler { 9 | /** 10 | @notice Correlates {resourceID} with {contractAddress}, {depositFunctionSig}, and {executeFunctionSig}. 11 | @param resourceID ResourceID to be used when making deposits. 12 | @param contractAddress Address of contract to be called when a deposit is made and a deposited is executed. 13 | @param depositFunctionSig Function signature of method to be called in {contractAddress} when a deposit is made. 14 | @param depositFunctionDepositerOffset Depositer address position offset in the metadata, in bytes. 15 | @param executeFunctionSig Function signature of method to be called in {contractAddress} when a deposit is executed. 16 | */ 17 | function setResource( 18 | bytes32 resourceID, 19 | address contractAddress, 20 | bytes4 depositFunctionSig, 21 | uint depositFunctionDepositerOffset, 22 | bytes4 executeFunctionSig) external; 23 | } -------------------------------------------------------------------------------- /contracts/utils/Pausable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity 0.8.11; 4 | 5 | 6 | /** 7 | * @dev Contract module which allows children to implement an emergency stop 8 | * mechanism that can be triggered by an authorized account. 9 | * 10 | * This is a stripped down version of Open zeppelin's Pausable contract. 11 | * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/EnumerableSet.sol 12 | * 13 | */ 14 | contract Pausable { 15 | /** 16 | * @dev Emitted when the pause is triggered by `account`. 17 | */ 18 | event Paused(address account); 19 | 20 | /** 21 | * @dev Emitted when the pause is lifted by `account`. 22 | */ 23 | event Unpaused(address account); 24 | 25 | bool private _paused; 26 | 27 | /** 28 | * @dev Initializes the contract in unpaused state. 29 | */ 30 | constructor () { 31 | _paused = false; 32 | } 33 | 34 | /** 35 | * @dev Returns true if the contract is paused, and false otherwise. 36 | */ 37 | function paused() public view returns (bool) { 38 | return _paused; 39 | } 40 | 41 | /** 42 | * @dev Modifier to make a function callable only when the contract is not paused. 43 | * 44 | * Requirements: 45 | * 46 | * - The contract must not be paused. 47 | */ 48 | modifier whenNotPaused() { 49 | _whenNotPaused(); 50 | _; 51 | } 52 | 53 | function _whenNotPaused() private view { 54 | require(!_paused, "Pausable: paused"); 55 | } 56 | 57 | /** 58 | * @dev Modifier to make a function callable only when the contract is not paused. 59 | * 60 | * Requirements: 61 | * 62 | * - The contract must not be paused. 63 | */ 64 | modifier whenPaused() { 65 | _whenPaused(); 66 | _; 67 | } 68 | 69 | function _whenPaused() private view { 70 | require(_paused, "Pausable: not paused"); 71 | } 72 | 73 | /** 74 | * @dev Triggers stopped state. 75 | * @param sender Address which executes pause. 76 | * 77 | * Requirements: 78 | * 79 | * - The contract must not be paused. 80 | */ 81 | function _pause(address sender) internal virtual whenNotPaused { 82 | _paused = true; 83 | emit Paused(sender); 84 | } 85 | 86 | /** 87 | * @dev Returns to normal state. 88 | * @param sender Address which executes unpause. 89 | * 90 | * Requirements: 91 | * 92 | * - The contract must be paused. 93 | */ 94 | function _unpause(address sender) internal virtual whenPaused { 95 | _paused = false; 96 | emit Unpaused(sender); 97 | } 98 | } -------------------------------------------------------------------------------- /contracts/utils/SafeCast.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity 0.8.11; 4 | 5 | 6 | library SafeCast { 7 | function toUint200(uint256 value) internal pure returns (uint200) { 8 | require(value < 2**200, "value does not fit in 200 bits"); 9 | return uint200(value); 10 | } 11 | 12 | function toUint128(uint256 value) internal pure returns (uint128) { 13 | require(value < 2**128, "value does not fit in 128 bits"); 14 | return uint128(value); 15 | } 16 | 17 | function toUint40(uint256 value) internal pure returns (uint40) { 18 | require(value < 2**40, "value does not fit in 40 bits"); 19 | return uint40(value); 20 | } 21 | 22 | function toUint8(uint256 value) internal pure returns (uint8) { 23 | require(value < 2**8, "value does not fit in 8 bits"); 24 | return uint8(value); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /contracts/utils/SafeMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity 0.8.11; 4 | 5 | /** 6 | * @dev Wrappers over Solidity's arithmetic operations with added overflow 7 | * checks. 8 | * 9 | * note that this is a stripped down version of open zeppelin's safemath 10 | * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/math/SafeMath.sol 11 | */ 12 | 13 | contract SafeMath { 14 | 15 | /** 16 | * @dev Returns the subtraction of two unsigned integers, reverting on 17 | * overflow (when the result is negative). 18 | * 19 | * Counterpart to Solidity's `-` operator. 20 | * 21 | * Requirements: 22 | * - Subtraction cannot overflow. 23 | */ 24 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 25 | return _sub(a, b, "SafeMath: subtraction overflow"); 26 | } 27 | 28 | /** 29 | * @dev Returns the subtraction of two unsigned integers, reverting with custom message on 30 | * overflow (when the result is negative). 31 | * 32 | * Counterpart to Solidity's `-` operator. 33 | * 34 | * Requirements: 35 | * - Subtraction cannot overflow. 36 | */ 37 | function _sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 38 | require(b <= a, errorMessage); 39 | uint256 c = a - b; 40 | 41 | return c; 42 | } 43 | } -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | 6 | const Migrations = artifacts.require("Migrations"); 7 | 8 | module.exports = function(deployer) { 9 | deployer.deploy(Migrations); 10 | }; 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chainsafe/chainbridge-contracts", 3 | "version": "2.1.4", 4 | "description": "", 5 | "main": "dist/index.js", 6 | "repository": "https://github.com/ChainSafe/chainbridge-solidity.git", 7 | "files": [ 8 | "dist", 9 | "build/contracts/Bridge.json", 10 | "build/contracts/ERC20Handler.json", 11 | "build/contracts/ERC721Handler.json", 12 | "build/contracts/GenericHandler.json", 13 | "build/contracts/HandlerHelpers.json" 14 | ], 15 | "directories": { 16 | "test": "test" 17 | }, 18 | "scripts": { 19 | "compile": "truffle compile", 20 | "build": "rollup -c", 21 | "publish-package": "npm run compile && npm run generate-types && npm run build && npm publish", 22 | "generate-types": "npm run generate-types:ethers", 23 | "generate-types:ethers": "npx typechain \"build/contracts/*\" --target=ethers-v5 --out-dir src/ethers", 24 | "test": "echo \\\\\\\"Error: no test specified\\\\\\\" && exit 1" 25 | }, 26 | "author": "Chainsafe Systems", 27 | "license": "GPL-3.0-only", 28 | "devDependencies": { 29 | "@babel/core": "^7.17.4", 30 | "@codechecks/client": "^0.1.12", 31 | "@openzeppelin/contracts": "^4.5.0", 32 | "@rollup/plugin-babel": "^5.3.0", 33 | "@rollup/plugin-commonjs": "^21.0.1", 34 | "@rollup/plugin-node-resolve": "^13.1.3", 35 | "@typechain/ethers-v5": "^9.0.0", 36 | "commander": "^9.0.0", 37 | "coveralls": "^3.1.1", 38 | "eth-sig-util": "^3.0.1", 39 | "ethereumjs-wallet": "^1.0.2", 40 | "ethers": "^5.5.4", 41 | "ganache-cli": "^6.9.1", 42 | "lodash.template": "^4.5.0", 43 | "rimraf": "^3.0.2", 44 | "rollup": "^2.67.2", 45 | "rollup-plugin-node-polyfills": "^0.2.1", 46 | "rollup-plugin-peer-deps-external": "^2.2.4", 47 | "rollup-plugin-typescript2": "^0.31.2", 48 | "solidity-coverage": "^0.7.20", 49 | "truffle": "^5.4.32", 50 | "truffle-assertions": "^0.9.2", 51 | "typechain": "^7.0.0", 52 | "typescript": "^4.5.5" 53 | }, 54 | "peerDependencies": { 55 | "ethers": ">= 5.0.0" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from "@rollup/plugin-commonjs"; 2 | import resolve from "@rollup/plugin-node-resolve"; 3 | import peerDepsExternal from "rollup-plugin-peer-deps-external"; 4 | import typescript from "rollup-plugin-typescript2"; 5 | import babel from "@rollup/plugin-babel"; 6 | 7 | export default { 8 | input: "./src/index.ts", 9 | output: { 10 | format: "cjs", 11 | dir: "dist/", 12 | exports: "named", 13 | sourcemap: true, 14 | strict: true, 15 | }, 16 | plugins: [ 17 | peerDepsExternal(), 18 | resolve({ 19 | preferBuiltins: true, 20 | }), 21 | commonjs(), 22 | typescript(), 23 | babel({ 24 | exclude: "node_modules/**", 25 | }), 26 | ], 27 | external: ["ethers", "web3"], 28 | }; 29 | -------------------------------------------------------------------------------- /scripts/.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | providerOptions: { 3 | "port" : 8545 4 | } 5 | }; -------------------------------------------------------------------------------- /scripts/compileAbiBin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * Copyright 2020 ChainSafe Systems 4 | * SPDX-License-Identifier: LGPL-3.0-only 5 | */ 6 | 7 | const fs = require("fs"); 8 | const rimraf = require("rimraf"); 9 | 10 | const BUILD_PATH = "./build/bindings/"; 11 | const ABI_PATH = BUILD_PATH + "abi/" 12 | const BIN_PATH = BUILD_PATH + "bin/" 13 | const RUNTIME_PATH = BUILD_PATH + "runtime/" 14 | 15 | // Loop through all the files in the temp directory 16 | fs.readdir("./build/contracts", function (err, files) { 17 | if (err) { 18 | console.error("Could not list the directory.", err); 19 | process.exit(1); 20 | } 21 | 22 | // Remove old build 23 | rimraf.sync(BUILD_PATH); 24 | 25 | // Create empty dirs 26 | fs.mkdirSync(BUILD_PATH) 27 | if (!fs.existsSync(ABI_PATH)) { 28 | fs.mkdirSync(ABI_PATH); 29 | } 30 | if (!fs.existsSync(BIN_PATH)) { 31 | fs.mkdirSync(BIN_PATH); 32 | } 33 | if (!fs.existsSync(RUNTIME_PATH)) { 34 | fs.mkdirSync(RUNTIME_PATH); 35 | } 36 | 37 | files.forEach(function (file, index) { 38 | const basename = file.split(".")[0]; 39 | const path = './build/contracts/' + file 40 | let rawdata = fs.readFileSync(path); 41 | let contract = JSON.parse(rawdata); 42 | let { abi, bytecode} = contract; 43 | bytecode = bytecode.substring(2); 44 | 45 | if (abi.length === 0) return; 46 | fs.writeFileSync(ABI_PATH + basename + ".abi" , JSON.stringify(abi)); 47 | fs.writeFileSync(BIN_PATH + basename + ".bin", bytecode); 48 | }); 49 | }); -------------------------------------------------------------------------------- /scripts/create_bindings.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2020 ChainSafe Systems 3 | # SPDX-License-Identifier: LGPL-3.0-only 4 | 5 | 6 | set -e 7 | 8 | base_path="./build/bindings" 9 | BIN_DIR="$base_path/bin" 10 | ABI_DIR="$base_path/abi" 11 | RUNTIME_DIR="$base_path/runtime" 12 | GO_DIR="$base_path/go" 13 | 14 | echo "Generating json ABI and associated files..." 15 | ./scripts/compileAbiBin.js 16 | 17 | # Remove old bin and abi 18 | echo "Removing old builds..." 19 | mkdir $GO_DIR 20 | 21 | for file in "$BIN_DIR"/*.bin 22 | do 23 | base=`basename $file` 24 | value="${base%.*}" 25 | echo Compiling file $value from path $file 26 | 27 | # Create the go package directory 28 | mkdir $GO_DIR/$value 29 | 30 | # Build the go package 31 | abigen --abi $ABI_DIR/${value}.abi --pkg $value --type $value --bin $BIN_DIR/${value}.bin --out $GO_DIR/$value/$value.go 32 | done -------------------------------------------------------------------------------- /scripts/geth/genesis.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "chainId": 5, 4 | "homesteadBlock": 0, 5 | "eip150Block": 0, 6 | "eip155Block": 0, 7 | "eip158Block": 0, 8 | "byzantiumBlock": 0, 9 | "constantinopleBlock": 0, 10 | "petersburgBlock": 0, 11 | "clique": { 12 | "period": 1, 13 | "epoch": 30000 14 | } 15 | }, 16 | "difficulty": "1", 17 | "gasLimit": "8000000", 18 | "extradata": "0x0000000000000000000000000000000000000000000000000000000000000000ff93B45308FD417dF303D6515aB04D9e89a750Ca0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 19 | "alloc": { 20 | "0xff93B45308FD417dF303D6515aB04D9e89a750Ca": { "balance": "20000000000000000000"}, 21 | "0x8e0a907331554AF72563Bd8D43051C2E64Be5d35": { "balance": "20000000000000000000"}, 22 | "0x24962717f8fA5BA3b931bACaF9ac03924EB475a0": { "balance": "20000000000000000000"}, 23 | "0x148FfB2074A9e59eD58142822b3eB3fcBffb0cd7": { "balance": "20000000000000000000"}, 24 | "0x4CEEf6139f00F9F4535Ad19640Ff7A0137708485": { "balance": "20000000000000000000"} 25 | } 26 | } -------------------------------------------------------------------------------- /scripts/geth/keystore/UTC--2020-04-07T13-50-35.447Z--ff93b45308fd417df303d6515ab04d9e89a750ca: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"369ea1d8-73ac-440b-932c-0722491eb9a2","address":"ff93b45308fd417df303d6515ab04d9e89a750ca","crypto":{"ciphertext":"095e1ad5c02249880243cfba01c79297b3a58315d5819d79c5349db7e65fcb13","cipherparams":{"iv":"589cfe4b3f5c935ea4847dd233980f0a"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"f01da06f89421cdc88d7b896844ca3b0295f01208cff36f89fa2a6706d248107","n":8192,"r":8,"p":1},"mac":"60267143aa71c8124156ac2dafb5e771cb1d0a9862634c78606549418573b907"}} -------------------------------------------------------------------------------- /scripts/geth/keystore/UTC--2020-04-07T13-52-12.564Z--8e0a907331554af72563bd8d43051c2e64be5d35: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"1bdffff9-241c-4cda-b9b1-a2afffa73a3a","address":"8e0a907331554af72563bd8d43051c2e64be5d35","crypto":{"ciphertext":"d1af2dc6976cabe7b390799c908940150315285515622c3e5332412564c57d6d","cipherparams":{"iv":"87c6ffc252c435c3b612fcca5e9a10a0"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"e85eebb3570913942908be7201003435f9541beb26668ed275a1f2a6d4bcd932","n":8192,"r":8,"p":1},"mac":"dc8e177a84ac62c8395ecda5b7680f7ad7b0278499beddaa2e8c71965477b07b"}} -------------------------------------------------------------------------------- /scripts/geth/keystore/UTC--2020-04-07T13-53-49.003Z--24962717f8fa5ba3b931bacaf9ac03924eb475a0: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"fca758b1-8782-4e54-9ede-96ca22931b72","address":"24962717f8fa5ba3b931bacaf9ac03924eb475a0","crypto":{"ciphertext":"5120712ba214043947befd835e3d86a38fce60c8d830f878ca1a053120d37056","cipherparams":{"iv":"71168d1cbf61b9e65ea02b5db38de7e7"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"2010ca09bebf3988fb95997279ad7d761ab0d4b8323c647470dd680bdfc2f202","n":8192,"r":8,"p":1},"mac":"920f27b77fcdaaa3c322a33e91e088a7c30f82b0e3a9bfd1072058198b367f07"}} -------------------------------------------------------------------------------- /scripts/geth/keystore/UTC--2020-04-07T13-55-20.258Z--148ffb2074a9e59ed58142822b3eb3fcbffb0cd7: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"2995e9ab-3ac6-4814-bc40-03165cf23f1b","address":"148ffb2074a9e59ed58142822b3eb3fcbffb0cd7","crypto":{"ciphertext":"c986b6930bd7814092d613c607e7f4df0758d895f6216294fc0452bb18348d91","cipherparams":{"iv":"527bb46169ca968a6821d75c845bfd3f"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"71f9c2041a9a6b5476393331ef6924335f5485cf4c175cbc33c83c685c3f4394","n":8192,"r":8,"p":1},"mac":"c10a5aaf7a34a9d08377a02a517339b3afaa4ed7e6ec9a6a70dd1bcf3b4e1fba"}} -------------------------------------------------------------------------------- /scripts/geth/keystore/UTC--2020-04-07T13-56-44.768Z--4ceef6139f00f9f4535ad19640ff7a0137708485: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"a163ef8f-b883-4474-a79f-9ecddf079683","address":"4ceef6139f00f9f4535ad19640ff7a0137708485","crypto":{"ciphertext":"fa612d9881ab9863b830daea365acaac6643469d0f02189b82fb84948f59d643","cipherparams":{"iv":"b91f79de31d0a9bdc8a0670a129efea8"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"ddb540564be1bf1546245fafafd1b952431213b4d742a9af3d4fbedc54846ae3","n":8192,"r":8,"p":1},"mac":"ffbc5fab063f361c8dfa52f91be661d08a1e9119c9a81aac83fa81af4a2f5909"}} -------------------------------------------------------------------------------- /scripts/geth/password.txt: -------------------------------------------------------------------------------- 1 | passwordpassword 2 | passwordpassword 3 | passwordpassword 4 | passwordpassword 5 | passwordpassword -------------------------------------------------------------------------------- /scripts/geth/run_geth.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2020 ChainSafe Systems 3 | # SPDX-License-Identifier: LGPL-3.0-only 4 | 5 | # Exit on failure 6 | set -e 7 | 8 | # Delete old chain data 9 | rm -rf $DATADIR 10 | # Init genesis 11 | geth init ./scripts/geth/genesis.json --datadir $DATADIR 12 | # Copy keystore 13 | rm -rf $DATADIR/keystore 14 | cp -r ./scripts/geth/keystore $DATADIR 15 | # Start geth with rpc, mining and unlocked accounts 16 | 17 | if [[ $QUIET ]]; then 18 | geth --verbosity 2 \ 19 | --datadir $DATADIR \ 20 | --port P2P_PORT \ 21 | --nodiscover \ 22 | --unlock "0xff93B45308FD417dF303D6515aB04D9e89a750Ca","0x8e0a907331554AF72563Bd8D43051C2E64Be5d35","0x24962717f8fA5BA3b931bACaF9ac03924EB475a0","0x148FfB2074A9e59eD58142822b3eB3fcBffb0cd7","0x4CEEf6139f00F9F4535Ad19640Ff7A0137708485" \ 23 | --password ./scripts/geth/password.txt \ 24 | --ws \ 25 | --wsport RPC_PORT \ 26 | --wsorigins="*" \ 27 | --rpc \ 28 | --rpcport RPC_PORT \ 29 | --rpccorsdomain="*" \ 30 | --networkid 5 \ 31 | --targetgaslimit 8000000 \ 32 | --allow-insecure-unlock \ 33 | --mine & 34 | else 35 | geth --datadir $DATADIR \ 36 | --port P2P_PORT \ 37 | --nodiscover \ 38 | --unlock "0xff93B45308FD417dF303D6515aB04D9e89a750Ca","0x8e0a907331554AF72563Bd8D43051C2E64Be5d35","0x24962717f8fA5BA3b931bACaF9ac03924EB475a0","0x148FfB2074A9e59eD58142822b3eB3fcBffb0cd7","0x4CEEf6139f00F9F4535Ad19640Ff7A0137708485" \ 39 | --password ./scripts/geth/password.txt \ 40 | --ws \ 41 | --wsport RPC_PORT \ 42 | --wsorigins="*" \ 43 | --rpc \ 44 | --rpcport RPC_PORT \ 45 | --rpccorsdomain="*" \ 46 | --networkid 5 \ 47 | --targetgaslimit 8000000 \ 48 | --allow-insecure-unlock \ 49 | --mine 50 | fi 51 | -------------------------------------------------------------------------------- /scripts/geth/start_geth.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2020 ChainSafe Systems 3 | # SPDX-License-Identifier: LGPL-3.0-only 4 | DATADIR=./gethdata 5 | 6 | # Exit on failure 7 | set -e 8 | 9 | # Delete old chain data 10 | rm -rf $DATADIR 11 | # Init genesis 12 | geth init ./scripts/geth/genesis.json --datadir $DATADIR 13 | # Copy keystore 14 | rm -rf $DATADIR/keystore 15 | cp -r ./scripts/geth/keystore $DATADIR 16 | # Start geth with rpc, mining and unlocked accounts 17 | 18 | if [[ $QUIET ]]; then 19 | geth --verbosity 2 \ 20 | --datadir $DATADIR \ 21 | --nodiscover \ 22 | --unlock "0xff93B45308FD417dF303D6515aB04D9e89a750Ca","0x8e0a907331554AF72563Bd8D43051C2E64Be5d35","0x24962717f8fA5BA3b931bACaF9ac03924EB475a0","0x148FfB2074A9e59eD58142822b3eB3fcBffb0cd7","0x4CEEf6139f00F9F4535Ad19640Ff7A0137708485" \ 23 | --password ./scripts/geth/password.txt \ 24 | --ws \ 25 | --wsport 8545 \ 26 | --networkid 5 \ 27 | --wsorigins="*" \ 28 | --rpc \ 29 | --rpcport 8545 \ 30 | --rpccorsdomain="*" \ 31 | --targetgaslimit 8000000 \ 32 | --allow-insecure-unlock \ 33 | --mine 34 | else 35 | geth --datadir $DATADIR \ 36 | --nodiscover \ 37 | --unlock "0xff93B45308FD417dF303D6515aB04D9e89a750Ca","0x8e0a907331554AF72563Bd8D43051C2E64Be5d35","0x24962717f8fA5BA3b931bACaF9ac03924EB475a0","0x148FfB2074A9e59eD58142822b3eB3fcBffb0cd7","0x4CEEf6139f00F9F4535Ad19640Ff7A0137708485" \ 38 | --password ./scripts/geth/password.txt \ 39 | --ws \ 40 | --wsport 8545 \ 41 | --networkid 5 \ 42 | --wsorigins="*" \ 43 | --rpc \ 44 | --rpcport 8545 \ 45 | --rpccorsdomain="*" \ 46 | --targetgaslimit 8000000 \ 47 | --allow-insecure-unlock \ 48 | --mine 49 | fi 50 | -------------------------------------------------------------------------------- /scripts/geth/start_geth2.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2020 ChainSafe Systems 3 | # SPDX-License-Identifier: LGPL-3.0-only 4 | DATADIR=./gethdata2 5 | 6 | # Exit on failure 7 | set -e 8 | 9 | # Delete old chain data 10 | rm -rf $DATADIR 11 | # Init genesis 12 | geth init ./scripts/geth/genesis.json --datadir $DATADIR 13 | # Copy keystore 14 | rm -rf $DATADIR/keystore 15 | cp -r ./scripts/geth/keystore $DATADIR 16 | # Start geth with rpc, mining and unlocked accounts 17 | 18 | if [[ $QUIET ]]; then 19 | geth --verbosity 2 \ 20 | --datadir $DATADIR \ 21 | --port 30304 \ 22 | --nodiscover \ 23 | --unlock "0xff93B45308FD417dF303D6515aB04D9e89a750Ca","0x8e0a907331554AF72563Bd8D43051C2E64Be5d35","0x24962717f8fA5BA3b931bACaF9ac03924EB475a0","0x148FfB2074A9e59eD58142822b3eB3fcBffb0cd7","0x4CEEf6139f00F9F4535Ad19640Ff7A0137708485" \ 24 | --password ./scripts/geth/password.txt \ 25 | --ws \ 26 | --wsport 8546 \ 27 | --networkid 5 \ 28 | --wsorigins="*" \ 29 | --targetgaslimit 8000000 \ 30 | --allow-insecure-unlock \ 31 | --mine 32 | else 33 | geth --datadir $DATADIR \ 34 | --nodiscover \ 35 | --port 30304 \ 36 | --unlock "0xff93B45308FD417dF303D6515aB04D9e89a750Ca","0x8e0a907331554AF72563Bd8D43051C2E64Be5d35","0x24962717f8fA5BA3b931bACaF9ac03924EB475a0","0x148FfB2074A9e59eD58142822b3eB3fcBffb0cd7","0x4CEEf6139f00F9F4535Ad19640Ff7A0137708485" \ 37 | --password ./scripts/geth/password.txt \ 38 | --ws \ 39 | --wsport 8546 \ 40 | --networkid 5 \ 41 | --wsorigins="*" \ 42 | --targetgaslimit 8000000 \ 43 | --allow-insecure-unlock \ 44 | --mine 45 | fi -------------------------------------------------------------------------------- /scripts/install_deps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2020 ChainSafe Systems 3 | # SPDX-License-Identifier: LGPL-3.0-only 4 | 5 | 6 | set -e 7 | 8 | (set -x; npm install) 9 | 10 | if [ -x "$(command -v truffle)" ] 11 | then 12 | echo "truffle found, skipping install" 13 | else 14 | (set -x; npm install --global truffle) 15 | fi 16 | 17 | if [ -x "$(command -v ganache-cli)" ] 18 | then 19 | echo "ganache-cli found, skipping install" 20 | else 21 | (set -x; npm install --global ganache-cli) 22 | fi 23 | 24 | if [ -x "$(command -v abigen)" ] 25 | then 26 | echo "abigen found, skipping install" 27 | else 28 | unameOut="$(uname -s)" 29 | case "${unameOut}" in 30 | Linux*) 31 | echo "Found linux machine, will try using apt to install" 32 | ( set -x; sudo add-apt-repository -y ppa:ethereum/ethereum && 33 | sudo apt-get update && 34 | sudo apt-get install ethereum ) 35 | ;; 36 | Darwin*) 37 | echo "Found macOS machine, will try using brew to install" 38 | ( set -x; brew tap ethereum/ethereum && 39 | brew install ethereum ) 40 | ;; 41 | *) 42 | echo "Operating system not supported, please manually install: https://geth.ethereum.org/docs/install-and-build/installing-geth" 43 | esac 44 | fi 45 | 46 | -------------------------------------------------------------------------------- /scripts/start_ganache.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2020 ChainSafe Systems 3 | # SPDX-License-Identifier: LGPL-3.0-only 4 | 5 | 6 | # Exit on failure 7 | set -e 8 | 9 | PORT=${PORT:-8545} 10 | 11 | echo "Running ganache..." 12 | if [[ $SILENT ]]; then 13 | ganache-cli -q -p $PORT --account "0x000000000000000000000000000000000000000000000000000000616c696365,100000000000000000000" --account "0x0000000000000000000000000000000000000000000000000000000000626f62,100000000000000000000" --account "0x00000000000000000000000000000000000000000000000000636861726c6965,100000000000000000000" --account "0x0000000000000000000000000000000000000000000000000000000064617665,100000000000000000000" --account "0x0000000000000000000000000000000000000000000000000000000000657665,100000000000000000000" & 14 | # Otherwise CI will run tests before ganache has started 15 | sleep 3 16 | else 17 | ganache-cli -p $PORT --account "0x000000000000000000000000000000000000000000000000000000616c696365,100000000000000000000" --account "0x0000000000000000000000000000000000000000000000000000000000626f62,100000000000000000000" --account "0x00000000000000000000000000000000000000000000000000636861726c6965,100000000000000000000" --account "0x0000000000000000000000000000000000000000000000000000000064617665,100000000000000000000" --account "0x0000000000000000000000000000000000000000000000000000000000657665,100000000000000000000" 18 | fi -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ethers"; 2 | -------------------------------------------------------------------------------- /test/contractBridge/constructor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | const TruffleAssert = require('truffle-assertions'); 6 | 7 | const BridgeContract = artifacts.require("Bridge"); 8 | 9 | contract('Bridge - [constructor]', async accounts => { 10 | const domainID = 1; 11 | const initialRelayers = accounts.slice(0, 3); 12 | const initialRelayerThreshold = 2; 13 | 14 | const expectedBridgeAdmin = accounts[0]; 15 | const someAddress = "0xcafecafecafecafecafecafecafecafecafecafe"; 16 | const bytes32 = "0x0"; 17 | 18 | const BN = (num) => { 19 | return web3.utils.toBN(num); 20 | }; 21 | 22 | it('Bridge should not allow to set initialRelayerThreshold above 255', async () => { 23 | return TruffleAssert.fails(BridgeContract.new(domainID, initialRelayers, 256, 0, 100), "value does not fit in 8 bits"); 24 | }); 25 | 26 | it('Bridge should not allow to set fee above 2**128 - 1', async () => { 27 | return TruffleAssert.fails(BridgeContract.new( 28 | domainID, initialRelayers, initialRelayerThreshold, BN(2).pow(BN(128)), 100), "value does not fit in 128 bits"); 29 | }); 30 | 31 | it('Bridge should not allow to set expiry above 2**40 - 1', async () => { 32 | return TruffleAssert.fails(BridgeContract.new(domainID, initialRelayers, initialRelayerThreshold, 0, BN(2).pow(BN(40))), "value does not fit in 40 bits"); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/contractBridge/depositERC1155.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | 6 | const TruffleAssert = require('truffle-assertions'); 7 | 8 | const Helpers = require('../helpers'); 9 | 10 | const BridgeContract = artifacts.require("Bridge"); 11 | const ERC1155MintableContract = artifacts.require("ERC1155PresetMinterPauser"); 12 | const ERC1155HandlerContract = artifacts.require("ERC1155Handler"); 13 | 14 | contract('Bridge - [deposit - ERC1155]', async (accounts) => { 15 | const originDomainID = 1; 16 | const destinationDomainID = 2; 17 | const relayerThreshold = 0; 18 | const depositerAddress = accounts[1]; 19 | const originChainTokenID = 42; 20 | const originChainInitialTokenAmount = 100; 21 | const depositAmount = 10; 22 | const expectedDepositNonce = 1; 23 | 24 | let BridgeInstance; 25 | let OriginERC1155MintableInstance; 26 | let OriginERC1155HandlerInstance; 27 | let depositData; 28 | 29 | beforeEach(async () => { 30 | await Promise.all([ 31 | ERC1155MintableContract.new("TOK").then(instance => OriginERC1155MintableInstance = instance), 32 | BridgeInstance = await BridgeContract.new(originDomainID, [], relayerThreshold, 0, 100) 33 | ]); 34 | 35 | 36 | resourceID = Helpers.createResourceID(OriginERC1155MintableInstance.address, originDomainID); 37 | 38 | OriginERC1155HandlerInstance = await ERC1155HandlerContract.new(BridgeInstance.address); 39 | 40 | await Promise.all([ 41 | BridgeInstance.adminSetResource(OriginERC1155HandlerInstance.address, resourceID, OriginERC1155MintableInstance.address), 42 | OriginERC1155MintableInstance.mintBatch(depositerAddress, [originChainTokenID], [originChainInitialTokenAmount], "0x0") 43 | ]); 44 | await OriginERC1155MintableInstance.setApprovalForAll(OriginERC1155HandlerInstance.address, true, { from: depositerAddress }); 45 | 46 | depositData = Helpers.createERC1155DepositData([originChainTokenID], [depositAmount]); 47 | }); 48 | 49 | it("[sanity] test depositerAddress' balance", async () => { 50 | const originChainDepositerBalance = await OriginERC1155MintableInstance.balanceOf(depositerAddress, originChainTokenID); 51 | assert.strictEqual(originChainDepositerBalance.toNumber(), originChainInitialTokenAmount); 52 | }); 53 | 54 | it("[sanity] test OriginERC1155HandlerInstance.address' allowance", async () => { 55 | const originChainHandlerApprovedStatus = await OriginERC1155MintableInstance.isApprovedForAll(depositerAddress, OriginERC1155HandlerInstance.address); 56 | assert.strictEqual(originChainHandlerApprovedStatus, true); 57 | }); 58 | 59 | it('ERC1155 deposit can be made', async () => { 60 | await TruffleAssert.passes(BridgeInstance.deposit( 61 | destinationDomainID, 62 | resourceID, 63 | depositData, 64 | { from: depositerAddress } 65 | )); 66 | }); 67 | 68 | it('_depositCounts should be increments from 0 to 1', async () => { 69 | await BridgeInstance.deposit( 70 | destinationDomainID, 71 | resourceID, 72 | depositData, 73 | { from: depositerAddress } 74 | ); 75 | 76 | const depositCount = await BridgeInstance._depositCounts.call(destinationDomainID); 77 | assert.strictEqual(depositCount.toNumber(), expectedDepositNonce); 78 | }); 79 | 80 | it('ERC1155 can be deposited with correct balances', async () => { 81 | await BridgeInstance.deposit( 82 | destinationDomainID, 83 | resourceID, 84 | depositData, 85 | { from: depositerAddress } 86 | ); 87 | 88 | const originChainDepositerBalance = await OriginERC1155MintableInstance.balanceOf(depositerAddress, originChainTokenID); 89 | assert.strictEqual(originChainDepositerBalance.toNumber(), originChainInitialTokenAmount - depositAmount); 90 | 91 | const originChainHandlerBalance = await OriginERC1155MintableInstance.balanceOf(OriginERC1155HandlerInstance.address, originChainTokenID); 92 | assert.strictEqual(originChainHandlerBalance.toNumber(), depositAmount); 93 | }); 94 | 95 | it('Deposit event is fired with expected value', async () => { 96 | let depositTx = await BridgeInstance.deposit( 97 | destinationDomainID, 98 | resourceID, 99 | depositData, 100 | { from: depositerAddress } 101 | ); 102 | 103 | TruffleAssert.eventEmitted(depositTx, 'Deposit', (event) => { 104 | return event.destinationDomainID.toNumber() === destinationDomainID && 105 | event.resourceID === resourceID.toLowerCase() && 106 | event.depositNonce.toNumber() === expectedDepositNonce 107 | }); 108 | 109 | depositTx = await BridgeInstance.deposit( 110 | destinationDomainID, 111 | resourceID, 112 | depositData, 113 | { from: depositerAddress } 114 | ); 115 | 116 | TruffleAssert.eventEmitted(depositTx, 'Deposit', (event) => { 117 | return event.destinationDomainID.toNumber() === destinationDomainID && 118 | event.resourceID === resourceID.toLowerCase() && 119 | event.depositNonce.toNumber() === expectedDepositNonce + 1 120 | }); 121 | }); 122 | 123 | it('deposit requires resourceID that is mapped to a handler', async () => { 124 | await TruffleAssert.reverts(BridgeInstance.deposit(destinationDomainID, '0x0', depositData, { from: depositerAddress }), "resourceID not mapped to handler"); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /test/contractBridge/depositERC20.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | 6 | const TruffleAssert = require('truffle-assertions'); 7 | 8 | const Helpers = require('../helpers'); 9 | 10 | const BridgeContract = artifacts.require("Bridge"); 11 | const ERC20MintableContract = artifacts.require("ERC20PresetMinterPauser"); 12 | const ERC20HandlerContract = artifacts.require("ERC20Handler"); 13 | 14 | contract('Bridge - [deposit - ERC20]', async (accounts) => { 15 | const originDomainID = 1; 16 | const destinationDomainID = 2; 17 | const relayerThreshold = 0; 18 | const depositerAddress = accounts[1]; 19 | const recipientAddress = accounts[2]; 20 | const originChainInitialTokenAmount = 100; 21 | const depositAmount = 10; 22 | const expectedDepositNonce = 1; 23 | 24 | let BridgeInstance; 25 | let OriginERC20MintableInstance; 26 | let OriginERC20HandlerInstance; 27 | let depositData; 28 | 29 | beforeEach(async () => { 30 | await Promise.all([ 31 | ERC20MintableContract.new("token", "TOK").then(instance => OriginERC20MintableInstance = instance), 32 | BridgeInstance = await BridgeContract.new(originDomainID, [], relayerThreshold, 0, 100) 33 | ]); 34 | 35 | 36 | resourceID = Helpers.createResourceID(OriginERC20MintableInstance.address, originDomainID); 37 | 38 | OriginERC20HandlerInstance = await ERC20HandlerContract.new(BridgeInstance.address); 39 | 40 | await Promise.all([ 41 | BridgeInstance.adminSetResource(OriginERC20HandlerInstance.address, resourceID, OriginERC20MintableInstance.address), 42 | OriginERC20MintableInstance.mint(depositerAddress, originChainInitialTokenAmount) 43 | ]); 44 | await OriginERC20MintableInstance.approve(OriginERC20HandlerInstance.address, depositAmount * 2, { from: depositerAddress }); 45 | 46 | depositData = Helpers.createERCDepositData( 47 | depositAmount, 48 | 20, 49 | recipientAddress); 50 | }); 51 | 52 | it("[sanity] test depositerAddress' balance", async () => { 53 | const originChainDepositerBalance = await OriginERC20MintableInstance.balanceOf(depositerAddress); 54 | assert.strictEqual(originChainDepositerBalance.toNumber(), originChainInitialTokenAmount); 55 | }); 56 | 57 | it("[sanity] test OriginERC20HandlerInstance.address' allowance", async () => { 58 | const originChainHandlerAllowance = await OriginERC20MintableInstance.allowance(depositerAddress, OriginERC20HandlerInstance.address); 59 | assert.strictEqual(originChainHandlerAllowance.toNumber(), depositAmount * 2); 60 | }); 61 | 62 | it('ERC20 deposit can be made', async () => { 63 | await TruffleAssert.passes(BridgeInstance.deposit( 64 | destinationDomainID, 65 | resourceID, 66 | depositData, 67 | { from: depositerAddress } 68 | )); 69 | }); 70 | 71 | it('_depositCounts should be increments from 0 to 1', async () => { 72 | await BridgeInstance.deposit( 73 | destinationDomainID, 74 | resourceID, 75 | depositData, 76 | { from: depositerAddress } 77 | ); 78 | 79 | const depositCount = await BridgeInstance._depositCounts.call(destinationDomainID); 80 | assert.strictEqual(depositCount.toNumber(), expectedDepositNonce); 81 | }); 82 | 83 | it('ERC20 can be deposited with correct balances', async () => { 84 | await BridgeInstance.deposit( 85 | destinationDomainID, 86 | resourceID, 87 | depositData, 88 | { from: depositerAddress } 89 | ); 90 | 91 | const originChainDepositerBalance = await OriginERC20MintableInstance.balanceOf(depositerAddress); 92 | assert.strictEqual(originChainDepositerBalance.toNumber(), originChainInitialTokenAmount - depositAmount); 93 | 94 | const originChainHandlerBalance = await OriginERC20MintableInstance.balanceOf(OriginERC20HandlerInstance.address); 95 | assert.strictEqual(originChainHandlerBalance.toNumber(), depositAmount); 96 | }); 97 | 98 | it('Deposit event is fired with expected value', async () => { 99 | let depositTx = await BridgeInstance.deposit( 100 | destinationDomainID, 101 | resourceID, 102 | depositData, 103 | { from: depositerAddress } 104 | ); 105 | 106 | TruffleAssert.eventEmitted(depositTx, 'Deposit', (event) => { 107 | return event.destinationDomainID.toNumber() === destinationDomainID && 108 | event.resourceID === resourceID.toLowerCase() && 109 | event.depositNonce.toNumber() === expectedDepositNonce 110 | }); 111 | 112 | depositTx = await BridgeInstance.deposit( 113 | destinationDomainID, 114 | resourceID, 115 | depositData, 116 | { from: depositerAddress } 117 | ); 118 | 119 | TruffleAssert.eventEmitted(depositTx, 'Deposit', (event) => { 120 | return event.destinationDomainID.toNumber() === destinationDomainID && 121 | event.resourceID === resourceID.toLowerCase() && 122 | event.depositNonce.toNumber() === expectedDepositNonce + 1 123 | }); 124 | }); 125 | 126 | it('deposit requires resourceID that is mapped to a handler', async () => { 127 | await TruffleAssert.reverts(BridgeInstance.deposit(destinationDomainID, '0x0', depositData, { from: depositerAddress }), "resourceID not mapped to handler"); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /test/contractBridge/depositERC721.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | 6 | const TruffleAssert = require('truffle-assertions'); 7 | const Ethers = require('ethers'); 8 | const Helpers = require('../helpers'); 9 | 10 | const BridgeContract = artifacts.require("Bridge"); 11 | const ERC721MintableContract = artifacts.require("ERC721MinterBurnerPauser"); 12 | const ERC721HandlerContract = artifacts.require("ERC721Handler"); 13 | 14 | contract('Bridge - [deposit - ERC721]', async (accounts) => { 15 | const originDomainID = 1; 16 | const destinationDomainID = 2; 17 | const depositerAddress = accounts[1]; 18 | const recipientAddress = accounts[2]; 19 | const originChainTokenID = 42; 20 | const expectedDepositNonce = 1; 21 | const genericBytes = '0x736f796c656e745f677265656e5f69735f70656f706c65'; 22 | 23 | let BridgeInstance; 24 | let OriginERC721MintableInstance; 25 | let OriginERC721HandlerInstance; 26 | let depositData; 27 | 28 | let originResourceID; 29 | 30 | beforeEach(async () => { 31 | await Promise.all([ 32 | ERC721MintableContract.new("token", "TOK", "").then(instance => OriginERC721MintableInstance = instance), 33 | BridgeContract.new(originDomainID, [], 0, 0, 100).then(instance => BridgeInstance = instance) 34 | ]); 35 | 36 | originResourceID = Helpers.createResourceID(OriginERC721MintableInstance.address, originDomainID); 37 | 38 | await Promise.all([ 39 | ERC721HandlerContract.new(BridgeInstance.address).then(instance => OriginERC721HandlerInstance = instance), 40 | ERC721HandlerContract.new(BridgeInstance.address).then(instance => DestinationERC721HandlerInstance = instance) 41 | ]); 42 | 43 | await Promise.all([ 44 | BridgeInstance.adminSetResource(OriginERC721HandlerInstance.address, originResourceID, OriginERC721MintableInstance.address), 45 | OriginERC721MintableInstance.mint(depositerAddress, originChainTokenID, genericBytes) 46 | ]); 47 | 48 | await OriginERC721MintableInstance.approve(OriginERC721HandlerInstance.address, originChainTokenID, { from: depositerAddress }); 49 | 50 | depositData = Helpers.createERCDepositData( 51 | originChainTokenID, 52 | 20, 53 | recipientAddress); 54 | }); 55 | 56 | it("[sanity] test depositerAddress' balance", async () => { 57 | const originChainDepositerBalance = await OriginERC721MintableInstance.balanceOf(depositerAddress); 58 | assert.strictEqual(originChainDepositerBalance.toNumber(), 1); 59 | }); 60 | 61 | it(`[sanity] test depositerAddress owns token with ID: ${originChainTokenID}`, async () => { 62 | const tokenOwner = await OriginERC721MintableInstance.ownerOf(originChainTokenID); 63 | assert.strictEqual(tokenOwner, depositerAddress); 64 | }); 65 | 66 | it("[sanity] test OriginERC721HandlerInstance.address' allowance", async () => { 67 | const allowanceHolder = await OriginERC721MintableInstance.getApproved(originChainTokenID); 68 | assert.strictEqual(allowanceHolder, OriginERC721HandlerInstance.address); 69 | }); 70 | 71 | it('ERC721 deposit can be made', async () => { 72 | await BridgeInstance.deposit( 73 | destinationDomainID, 74 | originResourceID, 75 | depositData, 76 | { from: depositerAddress } 77 | ) 78 | }); 79 | 80 | it('_depositCounts should be increments from 0 to 1', async () => { 81 | await BridgeInstance.deposit( 82 | destinationDomainID, 83 | originResourceID, 84 | depositData, 85 | { from: depositerAddress } 86 | ); 87 | 88 | const depositCount = await BridgeInstance._depositCounts.call(destinationDomainID); 89 | assert.strictEqual(depositCount.toNumber(), expectedDepositNonce); 90 | }); 91 | 92 | it('ERC721 can be deposited with correct owner and balances', async () => { 93 | await BridgeInstance.deposit( 94 | destinationDomainID, 95 | originResourceID, 96 | depositData, 97 | { from: depositerAddress } 98 | ); 99 | 100 | const tokenOwner = await OriginERC721MintableInstance.ownerOf(originChainTokenID); 101 | assert.strictEqual(tokenOwner, OriginERC721HandlerInstance.address); 102 | 103 | const originChainDepositerBalance = await OriginERC721MintableInstance.balanceOf(depositerAddress); 104 | assert.strictEqual(originChainDepositerBalance.toNumber(), 0); 105 | 106 | const originChainHandlerBalance = await OriginERC721MintableInstance.balanceOf(OriginERC721HandlerInstance.address); 107 | assert.strictEqual(originChainHandlerBalance.toNumber(), 1); 108 | }); 109 | 110 | it('Deposit event is fired with expected value', async () => { 111 | const depositTx = await BridgeInstance.deposit( 112 | destinationDomainID, 113 | originResourceID, 114 | depositData, 115 | { from: depositerAddress } 116 | ); 117 | 118 | let expectedMetaData = Ethers.utils.hexlify(Ethers.utils.toUtf8Bytes(genericBytes)); 119 | 120 | TruffleAssert.eventEmitted(depositTx, 'Deposit', (event) => { 121 | 122 | return event.destinationDomainID.toNumber() === destinationDomainID && 123 | event.resourceID === originResourceID.toLowerCase() && 124 | event.depositNonce.toNumber() === expectedDepositNonce && 125 | event.user === depositerAddress && 126 | event.data === depositData.toLowerCase() && 127 | event.handlerResponse === expectedMetaData 128 | }); 129 | }); 130 | }); -------------------------------------------------------------------------------- /test/contractBridge/depositGeneric.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | 6 | const TruffleAssert = require('truffle-assertions'); 7 | 8 | const Helpers = require('../helpers'); 9 | 10 | const BridgeContract = artifacts.require("Bridge"); 11 | const CentrifugeAssetContract = artifacts.require("CentrifugeAsset"); 12 | const GenericHandlerContract = artifacts.require("GenericHandler"); 13 | 14 | contract('Bridge - [deposit - Generic]', async () => { 15 | const originDomainID = 1; 16 | const destinationDomainID = 2; 17 | const expectedDepositNonce = 1; 18 | 19 | let BridgeInstance; 20 | let GenericHandlerInstance; 21 | let depositData; 22 | let initialResourceIDs; 23 | let initialContractAddresses; 24 | let initialDepositFunctionSignatures; 25 | let initialDepositFunctionDepositerOffsets; 26 | let initialExecuteFunctionSignatures; 27 | 28 | beforeEach(async () => { 29 | await Promise.all([ 30 | CentrifugeAssetContract.new().then(instance => CentrifugeAssetInstance = instance), 31 | BridgeInstance = BridgeContract.new(originDomainID, [], 0, 0, 100).then(instance => BridgeInstance = instance) 32 | ]); 33 | 34 | resourceID = Helpers.createResourceID(CentrifugeAssetInstance.address, originDomainID) 35 | initialResourceIDs = [resourceID]; 36 | initialContractAddresses = [CentrifugeAssetInstance.address]; 37 | initialDepositFunctionSignatures = [Helpers.blankFunctionSig]; 38 | initialDepositFunctionDepositerOffsets = [Helpers.blankFunctionDepositerOffset]; 39 | initialExecuteFunctionSignatures = [Helpers.getFunctionSignature(CentrifugeAssetInstance, 'store')]; 40 | 41 | GenericHandlerInstance = await GenericHandlerContract.new( 42 | BridgeInstance.address); 43 | 44 | await BridgeInstance.adminSetGenericResource(GenericHandlerInstance.address, resourceID, initialContractAddresses[0], initialDepositFunctionSignatures[0], initialDepositFunctionDepositerOffsets[0], initialExecuteFunctionSignatures[0]); 45 | 46 | depositData = Helpers.createGenericDepositData('0xdeadbeef'); 47 | }); 48 | 49 | it('Generic deposit can be made', async () => { 50 | await TruffleAssert.passes(BridgeInstance.deposit( 51 | destinationDomainID, 52 | resourceID, 53 | depositData 54 | )); 55 | }); 56 | 57 | it('_depositCounts is incremented correctly after deposit', async () => { 58 | await BridgeInstance.deposit( 59 | destinationDomainID, 60 | resourceID, 61 | depositData 62 | ); 63 | 64 | const depositCount = await BridgeInstance._depositCounts.call(destinationDomainID); 65 | assert.strictEqual(depositCount.toNumber(), expectedDepositNonce); 66 | }); 67 | 68 | it('Deposit event is fired with expected value after Generic deposit', async () => { 69 | const depositTx = await BridgeInstance.deposit( 70 | destinationDomainID, 71 | resourceID, 72 | depositData 73 | ); 74 | 75 | TruffleAssert.eventEmitted(depositTx, 'Deposit', (event) => { 76 | return event.destinationDomainID.toNumber() === destinationDomainID && 77 | event.resourceID === resourceID.toLowerCase() && 78 | event.depositNonce.toNumber() === expectedDepositNonce 79 | }); 80 | }); 81 | }); -------------------------------------------------------------------------------- /test/contractBridge/executeWithFailedHandler.js: -------------------------------------------------------------------------------- 1 | const TruffleAssert = require('truffle-assertions'); 2 | const Ethers = require('ethers'); 3 | 4 | const Helpers = require('../helpers'); 5 | 6 | const BridgeContract = artifacts.require("Bridge"); 7 | const ERC20MintableContract = artifacts.require("ERC20PresetMinterPauser"); 8 | const ERC20HandlerContract = artifacts.require("HandlerRevert"); 9 | 10 | contract('Bridge - [execute - FailedHandlerExecution]', async accounts => { 11 | const relayerThreshold = 2; 12 | const domainID = 1; 13 | const depositerAddress = accounts[1]; 14 | const recipientAddress = accounts[2]; 15 | const relayer1Address = accounts[3]; 16 | const relayer2Address = accounts[4]; 17 | 18 | const initialTokenAmount = 100; 19 | const depositAmount = 10; 20 | const expectedDepositNonce = 1; 21 | 22 | let BridgeInstance; 23 | let ERC20MintableInstance; 24 | let ERC20HandlerInstance; 25 | 26 | let resourceID; 27 | let depositData; 28 | let depositProposalData; 29 | let depositProposalDataHash; 30 | 31 | const STATUS = { 32 | Inactive : '0', 33 | Active : '1', 34 | Passed : '2', 35 | Executed : '3', 36 | Cancelled : '4' 37 | } 38 | 39 | beforeEach(async () => { 40 | await Promise.all([ 41 | BridgeContract.new(domainID, [relayer1Address, relayer2Address], relayerThreshold, 0, 100).then(instance => BridgeInstance = instance), 42 | ERC20MintableContract.new("token", "TOK").then(instance => ERC20MintableInstance = instance) 43 | ]); 44 | 45 | resourceID = Helpers.createResourceID(ERC20MintableInstance.address, domainID); 46 | 47 | ERC20HandlerInstance = await ERC20HandlerContract.new(BridgeInstance.address); 48 | 49 | await Promise.all([ 50 | ERC20MintableInstance.mint(depositerAddress, initialTokenAmount), 51 | BridgeInstance.adminSetResource(ERC20HandlerInstance.address, resourceID, ERC20MintableInstance.address) 52 | ]); 53 | 54 | await ERC20MintableInstance.approve(ERC20HandlerInstance.address, depositAmount, { from: depositerAddress }); 55 | 56 | depositData = Helpers.createERCDepositData(depositAmount, 20, recipientAddress) 57 | depositProposalData = Helpers.createERCDepositData(depositAmount, 20, recipientAddress) 58 | depositProposalDataHash = Ethers.utils.keccak256(ERC20HandlerInstance.address + depositProposalData.substr(2)); 59 | }); 60 | 61 | it("Should revert if handler execute is reverted", async () => { 62 | const revertOnFail = true; 63 | 64 | await TruffleAssert.passes(BridgeInstance.voteProposal( 65 | domainID, 66 | expectedDepositNonce, 67 | resourceID, 68 | depositProposalData, 69 | { from: relayer1Address } 70 | )); 71 | 72 | await TruffleAssert.passes(BridgeInstance.voteProposal( 73 | domainID, 74 | expectedDepositNonce, 75 | resourceID, 76 | depositProposalData, 77 | { from: relayer2Address } 78 | )); 79 | 80 | const depositProposalBeforeFailedExecute = await BridgeInstance.getProposal( 81 | domainID, expectedDepositNonce, depositProposalDataHash); 82 | 83 | await TruffleAssert.reverts(BridgeInstance.executeProposal( 84 | domainID, 85 | expectedDepositNonce, 86 | depositProposalData, 87 | resourceID, 88 | revertOnFail, 89 | { from: relayer2Address } 90 | )); 91 | 92 | const depositProposalAfterFailedExecute = await BridgeInstance.getProposal( 93 | domainID, expectedDepositNonce, depositProposalDataHash); 94 | 95 | assert.deepInclude(Object.assign({}, depositProposalBeforeFailedExecute), depositProposalAfterFailedExecute); 96 | }); 97 | 98 | it("Should not revert even though handler execute is reverted if the proposal's status is changed to Passed during voting. FailedHandlerExecution event should be emitted with expected values. Proposal status still stays on Passed", async () => { 99 | 100 | await TruffleAssert.passes(BridgeInstance.voteProposal( 101 | domainID, 102 | expectedDepositNonce, 103 | resourceID, 104 | depositProposalData, 105 | { from: relayer1Address } 106 | )); 107 | 108 | const voteWithExecuteTx = await BridgeInstance.voteProposal( 109 | domainID, 110 | expectedDepositNonce, 111 | resourceID, 112 | depositProposalData, 113 | { from: relayer2Address } 114 | ); 115 | 116 | TruffleAssert.eventEmitted(voteWithExecuteTx, 'FailedHandlerExecution', (event) => { 117 | return Ethers.utils.parseBytes32String('0x' + event.lowLevelData.slice(-64)) === 'Something bad happened' 118 | }); 119 | 120 | const depositProposalAfterFailedExecute = await BridgeInstance.getProposal( 121 | domainID, expectedDepositNonce, depositProposalDataHash); 122 | 123 | assert.strictEqual(depositProposalAfterFailedExecute._status, STATUS.Passed); 124 | }); 125 | 126 | it("Vote proposal should be reverted if handler execution is reverted and proposal status was on Passed for vote", async () => { 127 | 128 | await TruffleAssert.passes(BridgeInstance.voteProposal( 129 | domainID, 130 | expectedDepositNonce, 131 | resourceID, 132 | depositProposalData, 133 | { from: relayer1Address } 134 | )); 135 | 136 | // After this vote, automatically executes the proposqal but handler execute is reverted. So proposal still stays on Passed after this vote. 137 | await TruffleAssert.passes(BridgeInstance.voteProposal( 138 | domainID, 139 | expectedDepositNonce, 140 | resourceID, 141 | depositProposalData, 142 | { from: relayer2Address } 143 | )); 144 | 145 | await TruffleAssert.reverts(BridgeInstance.voteProposal( 146 | domainID, 147 | expectedDepositNonce, 148 | resourceID, 149 | depositProposalData, 150 | { from: relayer2Address } 151 | ), 'Something bad happened'); 152 | }); 153 | 154 | it("Should execute the proposal successfully if the handler has enough amount after the last execution is reverted", async () => { 155 | await TruffleAssert.passes(BridgeInstance.voteProposal( 156 | domainID, 157 | expectedDepositNonce, 158 | resourceID, 159 | depositProposalData, 160 | { from: relayer1Address } 161 | )); 162 | 163 | // After this vote, automatically executes the proposal but the execution is reverted. 164 | // But the whole transaction is not reverted and proposal still be on Passed status. 165 | await TruffleAssert.passes(BridgeInstance.voteProposal( 166 | domainID, 167 | expectedDepositNonce, 168 | resourceID, 169 | depositProposalData, 170 | { from: relayer2Address } 171 | )); 172 | 173 | // Some virtual operation so that the handler can have enough conditions to be executed. 174 | await ERC20HandlerInstance.virtualIncreaseBalance(1); 175 | 176 | // Should execute directly in this vote. 177 | const voteWithExecuteTx = await BridgeInstance.voteProposal( 178 | domainID, 179 | expectedDepositNonce, 180 | resourceID, 181 | depositProposalData, 182 | { from: relayer2Address } 183 | ); 184 | 185 | TruffleAssert.eventEmitted(voteWithExecuteTx, 'ProposalEvent', (event) => { 186 | return event.originDomainID.toNumber() === domainID && 187 | event.depositNonce.toNumber() === expectedDepositNonce && 188 | event.status.toString() === STATUS.Executed && 189 | event.dataHash === depositProposalDataHash 190 | }); 191 | }) 192 | }); 193 | -------------------------------------------------------------------------------- /test/contractBridge/fee.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | 6 | const TruffleAssert = require('truffle-assertions'); 7 | const Ethers = require('ethers'); 8 | 9 | const Helpers = require('../helpers'); 10 | 11 | const BridgeContract = artifacts.require("Bridge"); 12 | const CentrifugeAssetContract = artifacts.require("CentrifugeAsset"); 13 | const GenericHandlerContract = artifacts.require("GenericHandler"); 14 | 15 | contract('Bridge - [fee]', async (accounts) => { 16 | const originDomainID = 1; 17 | const destinationDomainID = 2; 18 | const blankFunctionSig = '0x00000000'; 19 | const blankFunctionDepositerOffset = 0; 20 | const relayer = accounts[0]; 21 | 22 | let BridgeInstance; 23 | let GenericHandlerInstance; 24 | let resourceID; 25 | let depositData; 26 | let initialResourceIDs; 27 | let initialContractAddresses; 28 | let initialDepositFunctionSignatures; 29 | let initialDepositFunctionDepositerOffsets; 30 | let initialExecuteFunctionSignatures; 31 | 32 | beforeEach(async () => { 33 | await Promise.all([ 34 | CentrifugeAssetContract.new().then(instance => CentrifugeAssetInstance = instance), 35 | BridgeInstance = BridgeContract.new(originDomainID, [relayer], 0, 0, 100).then(instance => BridgeInstance = instance) 36 | ]); 37 | 38 | resourceID = Helpers.createResourceID(CentrifugeAssetInstance.address, originDomainID) 39 | initialResourceIDs = [resourceID]; 40 | initialContractAddresses = [CentrifugeAssetInstance.address]; 41 | initialDepositFunctionSignatures = [blankFunctionSig]; 42 | initialDepositFunctionDepositerOffsets = [blankFunctionDepositerOffset]; 43 | initialExecuteFunctionSignatures = [blankFunctionSig]; 44 | 45 | GenericHandlerInstance = await GenericHandlerContract.new( 46 | BridgeInstance.address); 47 | 48 | await BridgeInstance.adminSetGenericResource(GenericHandlerInstance.address, resourceID, initialContractAddresses[0], initialDepositFunctionSignatures[0], initialDepositFunctionDepositerOffsets[0], initialExecuteFunctionSignatures[0]); 49 | 50 | depositData = Helpers.createGenericDepositData('0xdeadbeef'); 51 | }); 52 | 53 | it('[sanity] Generic deposit can be made', async () => { 54 | await TruffleAssert.passes(BridgeInstance.deposit( 55 | destinationDomainID, 56 | resourceID, 57 | depositData 58 | )); 59 | }); 60 | 61 | it('deposit reverts if invalid amount supplied', async () => { 62 | // current fee is set to 0 63 | assert.equal(await BridgeInstance._fee.call(), 0) 64 | 65 | await TruffleAssert.reverts( 66 | BridgeInstance.deposit( 67 | destinationDomainID, 68 | resourceID, 69 | depositData, 70 | { 71 | value: Ethers.utils.parseEther("1.0") 72 | } 73 | ) 74 | ) 75 | }); 76 | 77 | it('deposit passes if valid amount supplied', async () => { 78 | // current fee is set to 0 79 | assert.equal(await BridgeInstance._fee.call(), 0) 80 | // Change fee to 0.5 ether 81 | await BridgeInstance.adminChangeFee(Ethers.utils.parseEther("0.5"), { from: relayer }) 82 | assert.equal(web3.utils.fromWei((await BridgeInstance._fee.call()), "ether"), "0.5"); 83 | 84 | await TruffleAssert.passes( 85 | BridgeInstance.deposit( 86 | destinationDomainID, 87 | resourceID, 88 | depositData, 89 | { 90 | value: Ethers.utils.parseEther("0.5") 91 | } 92 | ) 93 | ) 94 | }); 95 | 96 | it('distribute fees', async () => { 97 | await BridgeInstance.adminChangeFee(Ethers.utils.parseEther("1"), { from: relayer }); 98 | assert.equal(web3.utils.fromWei((await BridgeInstance._fee.call()), "ether"), "1"); 99 | 100 | // check the balance is 0 101 | assert.equal(web3.utils.fromWei((await web3.eth.getBalance(BridgeInstance.address)), "ether"), "0"); 102 | await BridgeInstance.deposit(destinationDomainID, resourceID, depositData, {value: Ethers.utils.parseEther("1")}) 103 | assert.equal(web3.utils.fromWei((await web3.eth.getBalance(BridgeInstance.address)), "ether"), "1"); 104 | 105 | let b1Before = await web3.eth.getBalance(accounts[1]); 106 | let b2Before = await web3.eth.getBalance(accounts[2]); 107 | 108 | let payout = Ethers.utils.parseEther("0.5") 109 | // Transfer the funds 110 | TruffleAssert.passes( 111 | await BridgeInstance.transferFunds( 112 | [accounts[1], accounts[2]], 113 | [payout, payout] 114 | ) 115 | ) 116 | b1 = await web3.eth.getBalance(accounts[1]); 117 | b2 = await web3.eth.getBalance(accounts[2]); 118 | assert.equal(b1, Ethers.BigNumber.from(b1Before).add(payout)); 119 | assert.equal(b2, Ethers.BigNumber.from(b2Before).add(payout)); 120 | }) 121 | }); -------------------------------------------------------------------------------- /test/e2e/erc1155/sameChain.js: -------------------------------------------------------------------------------- 1 | const TruffleAssert = require('truffle-assertions'); 2 | const Ethers = require('ethers'); 3 | 4 | const Helpers = require('../../helpers'); 5 | 6 | const BridgeContract = artifacts.require("Bridge"); 7 | const ERC1155MintableContract = artifacts.require("ERC1155PresetMinterPauser"); 8 | const ERC1155HandlerContract = artifacts.require("ERC1155Handler"); 9 | 10 | contract('E2E ERC1155 - Same Chain', async accounts => { 11 | const relayerThreshold = 2; 12 | const domainID = 1; 13 | 14 | const depositerAddress = accounts[1]; 15 | const recipientAddress = accounts[2]; 16 | const relayer1Address = accounts[3]; 17 | const relayer2Address = accounts[4]; 18 | 19 | const tokenID = 1; 20 | const initialTokenAmount = 100; 21 | const depositAmount = 10; 22 | const expectedDepositNonce = 1; 23 | 24 | let BridgeInstance; 25 | let ERC1155MintableInstance; 26 | let ERC1155HandlerInstance; 27 | let initialResourceIDs; 28 | let initialContractAddresses; 29 | let burnableContractAddresses; 30 | 31 | let resourceID; 32 | let depositData; 33 | let proposalData; 34 | 35 | beforeEach(async () => { 36 | await Promise.all([ 37 | BridgeContract.new(domainID, [relayer1Address, relayer2Address], relayerThreshold, 0, 100).then(instance => BridgeInstance = instance), 38 | ERC1155MintableContract.new("TOK").then(instance => ERC1155MintableInstance = instance) 39 | ]); 40 | 41 | resourceID = Helpers.createResourceID(ERC1155MintableInstance.address, domainID); 42 | initialResourceIDs = [resourceID]; 43 | initialContractAddresses = [ERC1155MintableInstance.address]; 44 | burnableContractAddresses = []; 45 | 46 | ERC1155HandlerInstance = await ERC1155HandlerContract.new(BridgeInstance.address); 47 | 48 | await Promise.all([ 49 | ERC1155MintableInstance.mintBatch(depositerAddress, [tokenID], [initialTokenAmount], "0x0"), 50 | BridgeInstance.adminSetResource(ERC1155HandlerInstance.address, resourceID, ERC1155MintableInstance.address) 51 | ]); 52 | 53 | await ERC1155MintableInstance.setApprovalForAll(ERC1155HandlerInstance.address, true, { from: depositerAddress }); 54 | 55 | depositData = Helpers.createERC1155DepositData([tokenID], [depositAmount]); 56 | proposalData = Helpers.createERC1155DepositProposalData([tokenID], [depositAmount], recipientAddress, "0x"); 57 | }); 58 | 59 | it("[sanity] depositerAddress' balance should be equal to initialTokenAmount", async () => { 60 | const depositerBalance = await ERC1155MintableInstance.balanceOf(depositerAddress, tokenID); 61 | assert.strictEqual(depositerBalance.toNumber(), initialTokenAmount); 62 | }); 63 | 64 | it("depositAmount of Destination ERC1155 should be transferred to recipientAddress", async () => { 65 | // depositerAddress makes initial deposit of depositAmount 66 | await TruffleAssert.passes(BridgeInstance.deposit( 67 | domainID, 68 | resourceID, 69 | depositData, 70 | { from: depositerAddress } 71 | )); 72 | 73 | // Handler should have a balance of depositAmount 74 | const handlerBalance = await ERC1155MintableInstance.balanceOf(ERC1155HandlerInstance.address, tokenID); 75 | assert.strictEqual(handlerBalance.toNumber(), depositAmount); 76 | 77 | // relayer1 creates the deposit proposal 78 | await TruffleAssert.passes(BridgeInstance.voteProposal( 79 | domainID, 80 | expectedDepositNonce, 81 | resourceID, 82 | proposalData, 83 | { from: relayer1Address } 84 | )); 85 | 86 | // relayer2 votes in favor of the deposit proposal 87 | // because the relayerThreshold is 2, the deposit proposal will go 88 | // into a finalized state 89 | // and then automatically executes the proposal 90 | await TruffleAssert.passes(BridgeInstance.voteProposal( 91 | domainID, 92 | expectedDepositNonce, 93 | resourceID, 94 | proposalData, 95 | { from: relayer2Address } 96 | )); 97 | 98 | // Assert ERC1155 balance was transferred from depositerAddress 99 | const depositerBalance = await ERC1155MintableInstance.balanceOf(depositerAddress, tokenID); 100 | assert.strictEqual(depositerBalance.toNumber(), initialTokenAmount - depositAmount); 101 | 102 | // Assert ERC1155 balance was transferred to recipientAddress 103 | const recipientBalance = await ERC1155MintableInstance.balanceOf(recipientAddress, tokenID); 104 | assert.strictEqual(recipientBalance.toNumber(), depositAmount); 105 | }); 106 | 107 | it("Handler's deposit function can be called by only bridge", async () => { 108 | await TruffleAssert.reverts(ERC1155HandlerInstance.deposit(resourceID, depositerAddress, depositData, { from: depositerAddress }), "sender must be bridge contract"); 109 | }); 110 | 111 | it("Handler's executeProposal function can be called by only bridge", async () => { 112 | await TruffleAssert.reverts(ERC1155HandlerInstance.executeProposal(resourceID, proposalData, { from: depositerAddress }), "sender must be bridge contract"); 113 | }); 114 | 115 | it("Handler's withdraw function can be called by only bridge", async () => { 116 | let withdrawData; 117 | withdrawData = Helpers.createERC1155WithdrawData(ERC1155MintableInstance.address, depositerAddress, [tokenID], [depositAmount], "0x"); 118 | 119 | await TruffleAssert.reverts(ERC1155HandlerInstance.withdraw(withdrawData, { from: depositerAddress }), "sender must be bridge contract"); 120 | }); 121 | 122 | it("Should withdraw funds", async () => { 123 | let depositerBalance; 124 | let handlerBalance; 125 | let withdrawData; 126 | 127 | depositerBalance = await ERC1155MintableInstance.balanceOf(depositerAddress, tokenID); 128 | assert.equal(depositerBalance, initialTokenAmount); 129 | 130 | await ERC1155MintableInstance.safeTransferFrom(depositerAddress, ERC1155HandlerInstance.address, tokenID, depositAmount, "0x0", { from: depositerAddress }); 131 | 132 | depositerBalance = await ERC1155MintableInstance.balanceOf(depositerAddress, tokenID); 133 | assert.equal(depositerBalance.toNumber(), initialTokenAmount - depositAmount); 134 | 135 | handlerBalance = await ERC1155MintableInstance.balanceOf(ERC1155HandlerInstance.address, tokenID); 136 | assert.equal(handlerBalance, depositAmount); 137 | 138 | withdrawData = Helpers.createERC1155WithdrawData(ERC1155MintableInstance.address, depositerAddress, [tokenID], [depositAmount], "0x"); 139 | 140 | await BridgeInstance.adminWithdraw(ERC1155HandlerInstance.address, withdrawData); 141 | 142 | depositerBalance = await ERC1155MintableInstance.balanceOf(depositerAddress, tokenID); 143 | assert.equal(depositerBalance, initialTokenAmount); 144 | }); 145 | }); 146 | -------------------------------------------------------------------------------- /test/e2e/erc20/sameChain.js: -------------------------------------------------------------------------------- 1 | const TruffleAssert = require('truffle-assertions'); 2 | const Ethers = require('ethers'); 3 | 4 | const Helpers = require('../../helpers'); 5 | 6 | const BridgeContract = artifacts.require("Bridge"); 7 | const ERC20MintableContract = artifacts.require("ERC20PresetMinterPauser"); 8 | const ERC20HandlerContract = artifacts.require("ERC20Handler"); 9 | 10 | contract('E2E ERC20 - Same Chain', async accounts => { 11 | const relayerThreshold = 2; 12 | const domainID = 1; 13 | 14 | const depositerAddress = accounts[1]; 15 | const recipientAddress = accounts[2]; 16 | const relayer1Address = accounts[3]; 17 | const relayer2Address = accounts[4]; 18 | 19 | const initialTokenAmount = 100; 20 | const depositAmount = 10; 21 | const expectedDepositNonce = 1; 22 | 23 | let BridgeInstance; 24 | let ERC20MintableInstance; 25 | let ERC20HandlerInstance; 26 | 27 | let resourceID; 28 | let depositData; 29 | let depositProposalData; 30 | let depositProposalDataHash; 31 | let initialResourceIDs; 32 | let initialContractAddresses; 33 | let burnableContractAddresses; 34 | 35 | beforeEach(async () => { 36 | await Promise.all([ 37 | BridgeContract.new(domainID, [relayer1Address, relayer2Address], relayerThreshold, 0, 100).then(instance => BridgeInstance = instance), 38 | ERC20MintableContract.new("token", "TOK").then(instance => ERC20MintableInstance = instance) 39 | ]); 40 | 41 | resourceID = Helpers.createResourceID(ERC20MintableInstance.address, domainID); 42 | 43 | initialResourceIDs = [resourceID]; 44 | initialContractAddresses = [ERC20MintableInstance.address]; 45 | burnableContractAddresses = []; 46 | 47 | ERC20HandlerInstance = await ERC20HandlerContract.new(BridgeInstance.address); 48 | 49 | await Promise.all([ 50 | ERC20MintableInstance.mint(depositerAddress, initialTokenAmount), 51 | BridgeInstance.adminSetResource(ERC20HandlerInstance.address, resourceID, ERC20MintableInstance.address) 52 | ]); 53 | 54 | await ERC20MintableInstance.approve(ERC20HandlerInstance.address, depositAmount, { from: depositerAddress }); 55 | 56 | depositData = Helpers.createERCDepositData(depositAmount, 20, recipientAddress) 57 | depositProposalData = Helpers.createERCDepositData(depositAmount, 20, recipientAddress) 58 | depositProposalDataHash = Ethers.utils.keccak256(ERC20HandlerInstance.address + depositProposalData.substr(2)); 59 | }); 60 | 61 | it("[sanity] depositerAddress' balance should be equal to initialTokenAmount", async () => { 62 | const depositerBalance = await ERC20MintableInstance.balanceOf(depositerAddress); 63 | assert.strictEqual(depositerBalance.toNumber(), initialTokenAmount); 64 | }); 65 | 66 | it("[sanity] ERC20HandlerInstance.address should have an allowance of depositAmount from depositerAddress", async () => { 67 | const handlerAllowance = await ERC20MintableInstance.allowance(depositerAddress, ERC20HandlerInstance.address); 68 | assert.strictEqual(handlerAllowance.toNumber(), depositAmount); 69 | }); 70 | 71 | it("depositAmount of Destination ERC20 should be transferred to recipientAddress", async () => { 72 | // depositerAddress makes initial deposit of depositAmount 73 | await TruffleAssert.passes(BridgeInstance.deposit( 74 | domainID, 75 | resourceID, 76 | depositData, 77 | { from: depositerAddress } 78 | )); 79 | 80 | // Handler should have a balance of depositAmount 81 | const handlerBalance = await ERC20MintableInstance.balanceOf(ERC20HandlerInstance.address); 82 | assert.strictEqual(handlerBalance.toNumber(), depositAmount); 83 | 84 | // relayer1 creates the deposit proposal 85 | await TruffleAssert.passes(BridgeInstance.voteProposal( 86 | domainID, 87 | expectedDepositNonce, 88 | resourceID, 89 | depositProposalData, 90 | { from: relayer1Address } 91 | )); 92 | 93 | // relayer2 votes in favor of the deposit proposal 94 | // because the relayerThreshold is 2, the deposit proposal will go 95 | // into a finalized state 96 | // and then automatically executes the proposal 97 | await TruffleAssert.passes(BridgeInstance.voteProposal( 98 | domainID, 99 | expectedDepositNonce, 100 | resourceID, 101 | depositProposalData, 102 | { from: relayer2Address } 103 | )); 104 | 105 | // Assert ERC20 balance was transferred from depositerAddress 106 | const depositerBalance = await ERC20MintableInstance.balanceOf(depositerAddress); 107 | assert.strictEqual(depositerBalance.toNumber(), initialTokenAmount - depositAmount); 108 | 109 | // // Assert ERC20 balance was transferred to recipientAddress 110 | const recipientBalance = await ERC20MintableInstance.balanceOf(recipientAddress); 111 | assert.strictEqual(recipientBalance.toNumber(), depositAmount); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /test/e2e/erc721/sameChain.js: -------------------------------------------------------------------------------- 1 | const TruffleAssert = require('truffle-assertions'); 2 | const Ethers = require('ethers'); 3 | 4 | const Helpers = require('../../helpers'); 5 | 6 | const BridgeContract = artifacts.require("Bridge"); 7 | const ERC721MintableContract = artifacts.require("ERC721MinterBurnerPauser"); 8 | const ERC721HandlerContract = artifacts.require("ERC721Handler"); 9 | 10 | contract('E2E ERC721 - Same Chain', async accounts => { 11 | const relayerThreshold = 2; 12 | const domainID = 1; 13 | 14 | const depositerAddress = accounts[1]; 15 | const recipientAddress = accounts[2]; 16 | const relayer1Address = accounts[3]; 17 | const relayer2Address = accounts[4]; 18 | 19 | const tokenID = 1; 20 | const depositMetadata = "0xc0ff33"; 21 | const expectedDepositNonce = 1; 22 | 23 | let BridgeInstance; 24 | let ERC721MintableInstance; 25 | let ERC721HandlerInstance; 26 | let initialResourceIDs; 27 | let initialContractAddresses; 28 | let burnableContractAddresses; 29 | 30 | let resourceID; 31 | let depositData; 32 | let proposalData; 33 | let depositProposalDataHash; 34 | 35 | beforeEach(async () => { 36 | await Promise.all([ 37 | BridgeContract.new(domainID, [relayer1Address, relayer2Address], relayerThreshold, 0, 100).then(instance => BridgeInstance = instance), 38 | ERC721MintableContract.new("token", "TOK", "").then(instance => ERC721MintableInstance = instance) 39 | ]); 40 | 41 | resourceID = Helpers.createResourceID(ERC721MintableInstance.address, domainID); 42 | initialResourceIDs = [resourceID]; 43 | initialContractAddresses = [ERC721MintableInstance.address]; 44 | burnableContractAddresses = []; 45 | 46 | ERC721HandlerInstance = await ERC721HandlerContract.new(BridgeInstance.address); 47 | 48 | await Promise.all([ 49 | ERC721MintableInstance.mint(depositerAddress, tokenID, depositMetadata), 50 | BridgeInstance.adminSetResource(ERC721HandlerInstance.address, resourceID, ERC721MintableInstance.address) 51 | ]); 52 | 53 | await ERC721MintableInstance.approve(ERC721HandlerInstance.address, tokenID, { from: depositerAddress }); 54 | 55 | depositData = Helpers.createERCDepositData(tokenID, 20, recipientAddress); 56 | proposalData = Helpers.createERC721DepositProposalData(tokenID, 20, recipientAddress, depositMetadata.length, depositMetadata); 57 | depositProposalDataHash = Ethers.utils.keccak256(ERC721HandlerInstance.address + proposalData.substr(2)); 58 | }); 59 | 60 | it("[sanity] depositerAddress' should own tokenID", async () => { 61 | const tokenOwner = await ERC721MintableInstance.ownerOf(tokenID); 62 | assert.strictEqual(depositerAddress, tokenOwner); 63 | }); 64 | 65 | it("[sanity] ERC721HandlerInstance.address should have an allowance for tokenID from depositerAddress", async () => { 66 | const allowedAddress = await ERC721MintableInstance.getApproved(tokenID); 67 | assert.strictEqual(ERC721HandlerInstance.address, allowedAddress); 68 | }); 69 | 70 | it("depositAmount of Destination ERC721 should be transferred to recipientAddress", async () => { 71 | // depositerAddress makes initial deposit of depositAmount 72 | await TruffleAssert.passes(BridgeInstance.deposit( 73 | domainID, 74 | resourceID, 75 | depositData, 76 | { from: depositerAddress } 77 | )); 78 | 79 | // Handler should have a balance of depositAmount 80 | const tokenOwner = await ERC721MintableInstance.ownerOf(tokenID); 81 | assert.strictEqual(ERC721HandlerInstance.address, tokenOwner); 82 | 83 | // relayer1 creates the deposit proposal 84 | await TruffleAssert.passes(BridgeInstance.voteProposal( 85 | domainID, 86 | expectedDepositNonce, 87 | resourceID, 88 | proposalData, 89 | { from: relayer1Address } 90 | )); 91 | 92 | // relayer2 votes in favor of the deposit proposal 93 | // because the relayerThreshold is 2, the deposit proposal will go 94 | // into a finalized state 95 | // and then automatically executes the proposal 96 | await TruffleAssert.passes(BridgeInstance.voteProposal( 97 | domainID, 98 | expectedDepositNonce, 99 | resourceID, 100 | proposalData, 101 | { from: relayer2Address } 102 | )); 103 | 104 | // Assert ERC721 balance was transferred from depositerAddress 105 | const tokenOwnerAfterTransfer = await ERC721MintableInstance.ownerOf(tokenID); 106 | assert.strictEqual(recipientAddress, tokenOwnerAfterTransfer); 107 | }); 108 | }); 109 | -------------------------------------------------------------------------------- /test/gasBenchmarks/deployments.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | const BridgeContract = artifacts.require("Bridge"); 6 | const ERC20HandlerContract = artifacts.require("ERC20Handler"); 7 | const ERC721HandlerContract = artifacts.require("ERC721Handler"); 8 | const ERC1155HandlerContract = artifacts.require("ERC1155Handler"); 9 | const GenericHandlerContract = artifacts.require("GenericHandler"); 10 | const CentrifugeAssetContract = artifacts.require("CentrifugeAsset"); 11 | const HandlerHelpersContract = artifacts.require("HandlerHelpers"); 12 | const ERC20SafeContract = artifacts.require("ERC20Safe"); 13 | const ERC721SafeContract = artifacts.require("ERC721Safe"); 14 | const ERC1155SafeContract = artifacts.require("ERC1155Safe"); 15 | 16 | contract('Gas Benchmark - [contract deployments]', async () => { 17 | const domainID = 1; 18 | const relayerThreshold = 1; 19 | const centrifugeAssetMinCount = 1; 20 | const gasBenchmarks = []; 21 | 22 | let BridgeInstance; 23 | 24 | it('Should deploy all contracts and print benchmarks', async () => { 25 | let contractInstances = [await BridgeContract.new(domainID, [], relayerThreshold, 0, 100).then(instance => BridgeInstance = instance)]; 26 | contractInstances = contractInstances.concat( 27 | await Promise.all([ 28 | ERC20HandlerContract.new(BridgeInstance.address), 29 | ERC721HandlerContract.new(BridgeInstance.address), 30 | ERC1155HandlerContract.new(BridgeInstance.address), 31 | GenericHandlerContract.new(BridgeInstance.address), 32 | CentrifugeAssetContract.new(centrifugeAssetMinCount), 33 | HandlerHelpersContract.new(BridgeInstance.address), 34 | ERC20SafeContract.new(), 35 | ERC721SafeContract.new(), 36 | ERC1155SafeContract.new() 37 | ])); 38 | 39 | for (const contractInstance of contractInstances) { 40 | const txReceipt = await web3.eth.getTransactionReceipt(contractInstance.transactionHash); 41 | gasBenchmarks.push({ 42 | type: contractInstance.constructor._json.contractName, 43 | gasUsed: txReceipt.gasUsed 44 | }); 45 | } 46 | 47 | console.table(gasBenchmarks); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/gasBenchmarks/voteProposal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | const Ethers = require('ethers'); 6 | 7 | const Helpers = require('../helpers'); 8 | 9 | const BridgeContract = artifacts.require("Bridge"); 10 | const ERC20HandlerContract = artifacts.require("ERC20Handler"); 11 | const ERC20MintableContract = artifacts.require("ERC20PresetMinterPauser"); 12 | 13 | contract('Gas Benchmark - [Vote Proposal]', async (accounts) => { 14 | const domainID = 1; 15 | const relayerThreshold = 2; 16 | const relayer1Address = accounts[0]; 17 | const relayer2Address = accounts[1] 18 | const depositerAddress = accounts[2]; 19 | const recipientAddress = accounts[3]; 20 | const lenRecipientAddress = 20; 21 | const depositNonce = 1; 22 | const gasBenchmarks = []; 23 | 24 | const initialRelayers = [relayer1Address, relayer2Address]; 25 | const erc20TokenAmount = 100; 26 | 27 | let BridgeInstance; 28 | let ERC20MintableInstance; 29 | let ERC20HandlerInstance; 30 | 31 | let erc20ResourceID; 32 | 33 | const vote = (resourceID, depositNonce, depositData, relayer) => BridgeInstance.voteProposal(domainID, depositNonce, resourceID, depositData, { from: relayer }); 34 | 35 | before(async () => { 36 | await Promise.all([ 37 | BridgeContract.new(domainID, initialRelayers, relayerThreshold, 0, 100).then(instance => BridgeInstance = instance), 38 | ERC20MintableContract.new("token", "TOK").then(instance => ERC20MintableInstance = instance), 39 | ]); 40 | 41 | erc20ResourceID = Helpers.createResourceID(ERC20MintableInstance.address, domainID); 42 | 43 | await ERC20HandlerContract.new(BridgeInstance.address).then(instance => ERC20HandlerInstance = instance); 44 | 45 | await Promise.all([ 46 | ERC20MintableInstance.approve(ERC20HandlerInstance.address, erc20TokenAmount, { from: depositerAddress }), 47 | BridgeInstance.adminSetResource(ERC20HandlerInstance.address, erc20ResourceID, ERC20MintableInstance.address), 48 | ]); 49 | }); 50 | 51 | it('Should create proposal - relayerThreshold = 2, not finalized', async () => { 52 | const depositData = Helpers.createERCDepositData( 53 | erc20TokenAmount, 54 | lenRecipientAddress, 55 | recipientAddress); 56 | 57 | const voteTx = await vote(erc20ResourceID, depositNonce, depositData, relayer1Address); 58 | 59 | gasBenchmarks.push({ 60 | type: 'Vote Proposal - relayerThreshold = 2, Not Finalized', 61 | gasUsed: voteTx.receipt.gasUsed 62 | }); 63 | }); 64 | 65 | it('Should vote proposal - relayerThreshold = 2, finalized', async () => { 66 | const depositData = Helpers.createERCDepositData( 67 | erc20TokenAmount, 68 | lenRecipientAddress, 69 | recipientAddress); 70 | 71 | const voteTx = await vote(erc20ResourceID, depositNonce, depositData, relayer2Address); 72 | 73 | gasBenchmarks.push({ 74 | type: 'Vote Proposal - relayerThreshold = 2, Finalized', 75 | gasUsed: voteTx.receipt.gasUsed 76 | }); 77 | }); 78 | 79 | it('Should vote proposal - relayerThreshold = 1, finalized', async () => { 80 | const newDepositNonce = 2; 81 | await BridgeInstance.adminChangeRelayerThreshold(1); 82 | 83 | const depositData = Helpers.createERCDepositData( 84 | erc20TokenAmount, 85 | lenRecipientAddress, 86 | recipientAddress); 87 | const voteTx = await vote(erc20ResourceID, newDepositNonce, depositData, relayer2Address); 88 | 89 | gasBenchmarks.push({ 90 | type: 'Vote Proposal - relayerThreshold = 1, Finalized', 91 | gasUsed: voteTx.receipt.gasUsed 92 | }); 93 | }); 94 | 95 | it('Should print out benchmarks', () => console.table(gasBenchmarks)); 96 | }); 97 | -------------------------------------------------------------------------------- /test/handlers/erc1155/burnList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | 6 | const TruffleAssert = require('truffle-assertions'); 7 | const Ethers = require('ethers'); 8 | 9 | const BridgeContract = artifacts.require("Bridge"); 10 | const ERC1155MintableContract = artifacts.require("ERC1155PresetMinterPauser"); 11 | const ERC1155HandlerContract = artifacts.require("ERC1155Handler"); 12 | 13 | contract('ERC1155Handler - [Burn ERC1155]', async () => { 14 | const relayerThreshold = 2; 15 | const domainID = 1; 16 | 17 | let BridgeInstance; 18 | let ERC1155MintableInstance1; 19 | let ERC1155MintableInstance2; 20 | let resourceID1; 21 | let resourceID2; 22 | let initialResourceIDs; 23 | let initialContractAddresses; 24 | let burnableContractAddresses; 25 | 26 | beforeEach(async () => { 27 | await Promise.all([ 28 | BridgeContract.new(domainID, [], relayerThreshold, 0, 100).then(instance => BridgeInstance = instance), 29 | ERC1155MintableContract.new("TOK").then(instance => ERC1155MintableInstance1 = instance), 30 | ERC1155MintableContract.new("TOK").then(instance => ERC1155MintableInstance2 = instance) 31 | ]); 32 | 33 | resourceID1 = Ethers.utils.hexZeroPad((ERC1155MintableInstance1.address + Ethers.utils.hexlify(domainID).substr(2)), 32); 34 | resourceID2 = Ethers.utils.hexZeroPad((ERC1155MintableInstance2.address + Ethers.utils.hexlify(domainID).substr(2)), 32); 35 | initialResourceIDs = [resourceID1, resourceID2]; 36 | initialContractAddresses = [ERC1155MintableInstance1.address, ERC1155MintableInstance2.address]; 37 | burnableContractAddresses = [ERC1155MintableInstance1.address] 38 | }); 39 | 40 | it('[sanity] contract should be deployed successfully', async () => { 41 | await TruffleAssert.passes(ERC1155HandlerContract.new(BridgeInstance.address)); 42 | }); 43 | 44 | it('burnableContractAddresses should be marked true in _burnList', async () => { 45 | const ERC1155HandlerInstance = await ERC1155HandlerContract.new(BridgeInstance.address); 46 | 47 | for (i = 0; i < initialResourceIDs.length; i++) { 48 | await TruffleAssert.passes(BridgeInstance.adminSetResource(ERC1155HandlerInstance.address, initialResourceIDs[i], initialContractAddresses[i])); 49 | } 50 | 51 | for (i = 0; i < burnableContractAddresses.length; i++) { 52 | await TruffleAssert.passes(BridgeInstance.adminSetBurnable(ERC1155HandlerInstance.address, burnableContractAddresses[i])); 53 | } 54 | 55 | for (const burnableAddress of burnableContractAddresses) { 56 | const isBurnable = await ERC1155HandlerInstance._burnList.call(burnableAddress); 57 | assert.isTrue(isBurnable, "Contract wasn't successfully marked burnable"); 58 | } 59 | }); 60 | 61 | it('ERC1155MintableInstance2.address should not be marked true in _burnList', async () => { 62 | const ERC1155HandlerInstance = await ERC1155HandlerContract.new(BridgeInstance.address); 63 | 64 | for (i = 0; i < initialResourceIDs.length; i++) { 65 | await TruffleAssert.passes(BridgeInstance.adminSetResource(ERC1155HandlerInstance.address, initialResourceIDs[i], initialContractAddresses[i])); 66 | } 67 | 68 | for (i = 0; i < burnableContractAddresses.length; i++) { 69 | await TruffleAssert.passes(BridgeInstance.adminSetBurnable(ERC1155HandlerInstance.address, burnableContractAddresses[i])); 70 | } 71 | 72 | const isBurnable = await ERC1155HandlerInstance._burnList.call(ERC1155MintableInstance2.address); 73 | assert.isFalse(isBurnable, "Contract shouldn't be marked burnable"); 74 | }); 75 | 76 | it('ERC1155MintableInstance2.address should be marked true in _burnList after setBurnable is called', async () => { 77 | const ERC1155HandlerInstance = await ERC1155HandlerContract.new(BridgeInstance.address); 78 | 79 | for (i = 0; i < initialResourceIDs.length; i++) { 80 | await TruffleAssert.passes(BridgeInstance.adminSetResource(ERC1155HandlerInstance.address, initialResourceIDs[i], initialContractAddresses[i])); 81 | } 82 | 83 | for (i = 0; i < burnableContractAddresses.length; i++) { 84 | await TruffleAssert.passes(BridgeInstance.adminSetBurnable(ERC1155HandlerInstance.address, burnableContractAddresses[i])); 85 | } 86 | 87 | await BridgeInstance.adminSetBurnable(ERC1155HandlerInstance.address, ERC1155MintableInstance2.address); 88 | const isBurnable = await ERC1155HandlerInstance._burnList.call(ERC1155MintableInstance2.address); 89 | assert.isTrue(isBurnable, "Contract wasn't successfully marked burnable"); 90 | }); 91 | }); -------------------------------------------------------------------------------- /test/handlers/erc1155/deposit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | const TruffleAssert = require('truffle-assertions'); 6 | const Ethers = require('ethers'); 7 | 8 | const Helpers = require('../../helpers'); 9 | 10 | const BridgeContract = artifacts.require("Bridge"); 11 | const ERC1155MintableContract = artifacts.require("ERC1155PresetMinterPauser"); 12 | const ERC1155HandlerContract = artifacts.require("ERC1155Handler"); 13 | 14 | contract('ERC1155Handler - [Deposit ERC1155]', async (accounts) => { 15 | const relayerThreshold = 2; 16 | const domainID = 1; 17 | const expectedDepositNonce = 1; 18 | const depositerAddress = accounts[1]; 19 | const tokenID = 1; 20 | const tokenAmount = 100; 21 | 22 | let BridgeInstance; 23 | let ERC1155MintableInstance; 24 | let ERC1155HandlerInstance; 25 | 26 | let resourceID; 27 | let initialResourceIDs; 28 | let initialContractAddresses; 29 | let burnableContractAddresses; 30 | let depositData; 31 | 32 | beforeEach(async () => { 33 | await Promise.all([ 34 | BridgeContract.new(domainID, [], relayerThreshold, 0, 100).then(instance => BridgeInstance = instance), 35 | ERC1155MintableContract.new("TOK").then(instance => ERC1155MintableInstance = instance) 36 | ]) 37 | 38 | resourceID = Helpers.createResourceID(ERC1155MintableInstance.address, domainID); 39 | initialResourceIDs = [resourceID]; 40 | initialContractAddresses = [ERC1155MintableInstance.address]; 41 | burnableContractAddresses = [] 42 | 43 | await Promise.all([ 44 | ERC1155HandlerContract.new(BridgeInstance.address).then(instance => ERC1155HandlerInstance = instance), 45 | ERC1155MintableInstance.mintBatch(depositerAddress, [tokenID], [tokenAmount], "0x0") 46 | ]); 47 | 48 | await Promise.all([ 49 | ERC1155MintableInstance.setApprovalForAll(ERC1155HandlerInstance.address, true, { from: depositerAddress }), 50 | BridgeInstance.adminSetResource(ERC1155HandlerInstance.address, resourceID, ERC1155MintableInstance.address) 51 | ]); 52 | 53 | depositData = Helpers.createERC1155DepositData([tokenID], [tokenAmount]); 54 | }); 55 | 56 | it('[sanity] depositer owns tokenAmount of tokenID', async () => { 57 | const depositerBalance = await ERC1155MintableInstance.balanceOf(depositerAddress, tokenID); 58 | assert.equal(tokenAmount, depositerBalance); 59 | }); 60 | 61 | it('Deposit event is emitted with expected values', async () => { 62 | const depositTx = await BridgeInstance.deposit( 63 | domainID, 64 | resourceID, 65 | depositData, 66 | {from: depositerAddress} 67 | ); 68 | 69 | TruffleAssert.eventEmitted(depositTx, 'Deposit', (event) => { 70 | return event.destinationDomainID.toNumber() === domainID && 71 | event.resourceID === resourceID.toLowerCase() && 72 | event.depositNonce.toNumber() === expectedDepositNonce && 73 | event.data === Helpers.createERC1155DepositData([tokenID], [tokenAmount]).toLowerCase() && 74 | event.handlerResponse === null 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /test/handlers/erc1155/depositBurn.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | 6 | const TruffleAssert = require('truffle-assertions'); 7 | 8 | const Helpers = require('../../helpers'); 9 | 10 | const BridgeContract = artifacts.require("Bridge"); 11 | const ERC1155MintableContract = artifacts.require("ERC1155PresetMinterPauser"); 12 | const ERC1155HandlerContract = artifacts.require("ERC1155Handler"); 13 | 14 | contract('ERC1155Handler - [Deposit Burn ERC1155]', async (accounts) => { 15 | const relayerThreshold = 2; 16 | const domainID = 1; 17 | 18 | const depositerAddress = accounts[1]; 19 | 20 | const tokenID = 1; 21 | const tokenAmount = 100; 22 | 23 | let BridgeInstance; 24 | let ERC1155MintableInstance1; 25 | let ERC1155MintableInstance2; 26 | let ERC1155HandlerInstance; 27 | 28 | let resourceID1; 29 | let resourceID2; 30 | let initialResourceIDs; 31 | let initialContractAddresses; 32 | let burnableContractAddresses; 33 | 34 | beforeEach(async () => { 35 | await Promise.all([ 36 | BridgeContract.new(domainID, [], relayerThreshold, 0, 100).then(instance => BridgeInstance = instance), 37 | ERC1155MintableContract.new("TOK").then(instance => ERC1155MintableInstance1 = instance), 38 | ERC1155MintableContract.new("TOK").then(instance => ERC1155MintableInstance2 = instance) 39 | ]) 40 | 41 | resourceID1 = Helpers.createResourceID(ERC1155MintableInstance1.address, domainID); 42 | resourceID2 = Helpers.createResourceID(ERC1155MintableInstance2.address, domainID); 43 | initialResourceIDs = [resourceID1, resourceID2]; 44 | initialContractAddresses = [ERC1155MintableInstance1.address, ERC1155MintableInstance2.address]; 45 | burnableContractAddresses = [ERC1155MintableInstance1.address] 46 | 47 | await Promise.all([ 48 | ERC1155HandlerContract.new(BridgeInstance.address).then(instance => ERC1155HandlerInstance = instance), 49 | ERC1155MintableInstance1.mintBatch(depositerAddress, [tokenID], [tokenAmount], "0x0") 50 | ]); 51 | 52 | await Promise.all([ 53 | ERC1155MintableInstance1.setApprovalForAll(ERC1155HandlerInstance.address, true, { from: depositerAddress }), 54 | BridgeInstance.adminSetResource(ERC1155HandlerInstance.address, resourceID1, ERC1155MintableInstance1.address), 55 | BridgeInstance.adminSetResource(ERC1155HandlerInstance.address, resourceID2, ERC1155MintableInstance2.address), 56 | BridgeInstance.adminSetBurnable(ERC1155HandlerInstance.address, ERC1155MintableInstance1.address), 57 | ]); 58 | 59 | depositData = Helpers.createERC1155DepositData([tokenID], [tokenAmount]); 60 | }); 61 | 62 | it('[sanity] burnableContractAddresses should be marked true in _burnList', async () => { 63 | for (const burnableAddress of burnableContractAddresses) { 64 | const isBurnable = await ERC1155HandlerInstance._burnList.call(burnableAddress); 65 | assert.isTrue(isBurnable, "Contract wasn't successfully marked burnable"); 66 | } 67 | }); 68 | 69 | it('depositAmount of ERC1155MintableInstance1 tokens should have been burned', async () => { 70 | await BridgeInstance.deposit( 71 | domainID, 72 | resourceID1, 73 | depositData, 74 | { from: depositerAddress } 75 | ); 76 | 77 | const handlerBalance = await ERC1155MintableInstance1.balanceOf(ERC1155HandlerInstance.address, tokenID); 78 | assert.strictEqual(handlerBalance.toNumber(), 0); 79 | 80 | const depositerBalance = await ERC1155MintableInstance1.balanceOf(depositerAddress, tokenID); 81 | assert.strictEqual(depositerBalance.toNumber(), 0); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /test/handlers/erc20/burnList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | 6 | const TruffleAssert = require('truffle-assertions'); 7 | const Ethers = require('ethers'); 8 | 9 | const BridgeContract = artifacts.require("Bridge"); 10 | const ERC20MintableContract = artifacts.require("ERC20PresetMinterPauser"); 11 | const ERC20HandlerContract = artifacts.require("ERC20Handler"); 12 | 13 | contract('ERC20Handler - [Burn ERC20]', async () => { 14 | const relayerThreshold = 2; 15 | const domainID = 1; 16 | 17 | let RelayerInstance; 18 | let BridgeInstance; 19 | let ERC20MintableInstance1; 20 | let ERC20MintableInstance2; 21 | let resourceID1; 22 | let resourceID2; 23 | let initialResourceIDs; 24 | let initialContractAddresses; 25 | let burnableContractAddresses; 26 | 27 | beforeEach(async () => { 28 | await Promise.all([ 29 | BridgeContract.new(domainID, [], relayerThreshold, 0, 100).then(instance => BridgeInstance = instance), 30 | ERC20MintableContract.new("token", "TOK").then(instance => ERC20MintableInstance1 = instance), 31 | ERC20MintableContract.new("token", "TOK").then(instance => ERC20MintableInstance2 = instance) 32 | ]); 33 | 34 | resourceID1 = Ethers.utils.hexZeroPad((ERC20MintableInstance1.address + Ethers.utils.hexlify(domainID).substr(2)), 32); 35 | resourceID2 = Ethers.utils.hexZeroPad((ERC20MintableInstance2.address + Ethers.utils.hexlify(domainID).substr(2)), 32); 36 | initialResourceIDs = [resourceID1, resourceID2]; 37 | initialContractAddresses = [ERC20MintableInstance1.address, ERC20MintableInstance2.address]; 38 | burnableContractAddresses = [ERC20MintableInstance1.address] 39 | }); 40 | 41 | it('[sanity] contract should be deployed successfully', async () => { 42 | await TruffleAssert.passes(ERC20HandlerContract.new(BridgeInstance.address)); 43 | }); 44 | 45 | it('burnableContractAddresses should be marked true in _burnList', async () => { 46 | const ERC20HandlerInstance = await ERC20HandlerContract.new(BridgeInstance.address); 47 | 48 | for (i = 0; i < initialResourceIDs.length; i++) { 49 | await TruffleAssert.passes(BridgeInstance.adminSetResource(ERC20HandlerInstance.address, initialResourceIDs[i], initialContractAddresses[i])); 50 | } 51 | 52 | for (i = 0; i < burnableContractAddresses.length; i++) { 53 | await TruffleAssert.passes(BridgeInstance.adminSetBurnable(ERC20HandlerInstance.address, burnableContractAddresses[i])); 54 | } 55 | 56 | for (const burnableAddress of burnableContractAddresses) { 57 | const isBurnable = await ERC20HandlerInstance._burnList.call(burnableAddress); 58 | assert.isTrue(isBurnable, "Contract wasn't successfully marked burnable"); 59 | } 60 | }); 61 | 62 | it('ERC20MintableInstance2.address should not be marked true in _burnList', async () => { 63 | const ERC20HandlerInstance = await ERC20HandlerContract.new(BridgeInstance.address); 64 | 65 | for (i = 0; i < initialResourceIDs.length; i++) { 66 | await TruffleAssert.passes(BridgeInstance.adminSetResource(ERC20HandlerInstance.address, initialResourceIDs[i], initialContractAddresses[i])); 67 | } 68 | 69 | for (i = 0; i < burnableContractAddresses.length; i++) { 70 | await TruffleAssert.passes(BridgeInstance.adminSetBurnable(ERC20HandlerInstance.address, burnableContractAddresses[i])); 71 | } 72 | 73 | const isBurnable = await ERC20HandlerInstance._burnList.call(ERC20MintableInstance2.address); 74 | assert.isFalse(isBurnable, "Contract shouldn't be marked burnable"); 75 | }); 76 | 77 | it('ERC20MintableInstance2.address should be marked true in _burnList after setBurnable is called', async () => { 78 | const ERC20HandlerInstance = await ERC20HandlerContract.new(BridgeInstance.address); 79 | 80 | for (i = 0; i < initialResourceIDs.length; i++) { 81 | await TruffleAssert.passes(BridgeInstance.adminSetResource(ERC20HandlerInstance.address, initialResourceIDs[i], initialContractAddresses[i])); 82 | } 83 | 84 | for (i = 0; i < burnableContractAddresses.length; i++) { 85 | await TruffleAssert.passes(BridgeInstance.adminSetBurnable(ERC20HandlerInstance.address, burnableContractAddresses[i])); 86 | } 87 | 88 | await BridgeInstance.adminSetBurnable(ERC20HandlerInstance.address, ERC20MintableInstance2.address); 89 | const isBurnable = await ERC20HandlerInstance._burnList.call(ERC20MintableInstance2.address); 90 | assert.isTrue(isBurnable, "Contract wasn't successfully marked burnable"); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /test/handlers/erc20/constructor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | 6 | const TruffleAssert = require('truffle-assertions'); 7 | const Ethers = require('ethers'); 8 | 9 | const BridgeContract = artifacts.require("Bridge"); 10 | const ERC20MintableContract = artifacts.require("ERC20PresetMinterPauser"); 11 | const ERC20HandlerContract = artifacts.require("ERC20Handler"); 12 | 13 | contract('ERC20Handler - [constructor]', async () => { 14 | const relayerThreshold = 2; 15 | const domainID = 1; 16 | 17 | let BridgeInstance; 18 | let ERC20MintableInstance1; 19 | let ERC20MintableInstance2; 20 | let ERC20MintableInstance3; 21 | let initialResourceIDs; 22 | let initialContractAddresses; 23 | let burnableContractAddresses; 24 | 25 | beforeEach(async () => { 26 | await Promise.all([ 27 | BridgeContract.new(domainID, [], relayerThreshold, 0, 100).then(instance => BridgeInstance = instance), 28 | ERC20MintableContract.new("token", "TOK").then(instance => ERC20MintableInstance1 = instance), 29 | ERC20MintableContract.new("token", "TOK").then(instance => ERC20MintableInstance2 = instance), 30 | ERC20MintableContract.new("token", "TOK").then(instance => ERC20MintableInstance3 = instance) 31 | ]) 32 | 33 | initialResourceIDs = []; 34 | burnableContractAddresses = []; 35 | 36 | initialResourceIDs.push(Ethers.utils.hexZeroPad((ERC20MintableInstance1.address + Ethers.utils.hexlify(domainID).substr(2)), 32)); 37 | initialResourceIDs.push(Ethers.utils.hexZeroPad((ERC20MintableInstance2.address + Ethers.utils.hexlify(domainID).substr(2)), 32)); 38 | initialResourceIDs.push(Ethers.utils.hexZeroPad((ERC20MintableInstance3.address + Ethers.utils.hexlify(domainID).substr(2)), 32)); 39 | 40 | initialContractAddresses = [ERC20MintableInstance1.address, ERC20MintableInstance2.address, ERC20MintableInstance3.address]; 41 | }); 42 | 43 | it('[sanity] contract should be deployed successfully', async () => { 44 | await TruffleAssert.passes(ERC20HandlerContract.new(BridgeInstance.address)); 45 | }); 46 | 47 | it('initialResourceIDs should be parsed correctly and corresponding resourceID mappings should have expected values', async () => { 48 | const ERC20HandlerInstance = await ERC20HandlerContract.new(BridgeInstance.address); 49 | 50 | for (i = 0; i < initialResourceIDs.length; i++) { 51 | await TruffleAssert.passes(BridgeInstance.adminSetResource(ERC20HandlerInstance.address, initialResourceIDs[i], initialContractAddresses[i])); 52 | } 53 | 54 | for (const resourceID of initialResourceIDs) { 55 | const tokenAddress = `0x` + resourceID.substr(24,40); 56 | 57 | const retrievedTokenAddress = await ERC20HandlerInstance._resourceIDToTokenContractAddress.call(resourceID); 58 | assert.strictEqual(Ethers.utils.getAddress(tokenAddress).toLowerCase(), retrievedTokenAddress.toLowerCase()); 59 | 60 | const retrievedResourceID = await ERC20HandlerInstance._tokenContractAddressToResourceID.call(tokenAddress); 61 | assert.strictEqual(resourceID.toLowerCase(), retrievedResourceID.toLowerCase()); 62 | } 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/handlers/erc20/deposit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | const TruffleAssert = require('truffle-assertions'); 6 | const Ethers = require('ethers'); 7 | 8 | const Helpers = require('../../helpers'); 9 | 10 | const BridgeContract = artifacts.require("Bridge"); 11 | const ERC20MintableContract = artifacts.require("ERC20PresetMinterPauser"); 12 | const ERC20HandlerContract = artifacts.require("ERC20Handler"); 13 | 14 | contract('ERC20Handler - [Deposit ERC20]', async (accounts) => { 15 | const relayerThreshold = 2; 16 | const domainID = 1; 17 | const expectedDepositNonce = 1; 18 | const depositerAddress = accounts[1]; 19 | const tokenAmount = 100; 20 | 21 | let BridgeInstance; 22 | let ERC20MintableInstance; 23 | let ERC20HandlerInstance; 24 | 25 | let resourceID; 26 | let initialResourceIDs; 27 | let initialContractAddresses; 28 | let burnableContractAddresses; 29 | 30 | beforeEach(async () => { 31 | await Promise.all([ 32 | BridgeContract.new(domainID, [], relayerThreshold, 0, 100).then(instance => BridgeInstance = instance), 33 | ERC20MintableContract.new("token", "TOK").then(instance => ERC20MintableInstance = instance) 34 | ]); 35 | 36 | resourceID = Helpers.createResourceID(ERC20MintableInstance.address, domainID); 37 | initialResourceIDs = [resourceID]; 38 | initialContractAddresses = [ERC20MintableInstance.address]; 39 | burnableContractAddresses = [] 40 | 41 | await Promise.all([ 42 | ERC20HandlerContract.new(BridgeInstance.address).then(instance => ERC20HandlerInstance = instance), 43 | ERC20MintableInstance.mint(depositerAddress, tokenAmount) 44 | ]); 45 | 46 | await Promise.all([ 47 | ERC20MintableInstance.approve(ERC20HandlerInstance.address, tokenAmount, { from: depositerAddress }), 48 | BridgeInstance.adminSetResource(ERC20HandlerInstance.address, resourceID, ERC20MintableInstance.address) 49 | ]); 50 | }); 51 | 52 | it('[sanity] depositer owns tokenAmount of ERC20', async () => { 53 | const depositerBalance = await ERC20MintableInstance.balanceOf(depositerAddress); 54 | assert.equal(tokenAmount, depositerBalance); 55 | }); 56 | 57 | it('[sanity] ERC20HandlerInstance.address has an allowance of tokenAmount from depositerAddress', async () => { 58 | const handlerAllowance = await ERC20MintableInstance.allowance(depositerAddress, ERC20HandlerInstance.address); 59 | assert.equal(tokenAmount, handlerAllowance); 60 | }); 61 | 62 | it('Varied recipient address with length 40', async () => { 63 | const recipientAddress = accounts[0] + accounts[1].substr(2); 64 | const lenRecipientAddress = 40; 65 | 66 | const depositTx = await BridgeInstance.deposit( 67 | domainID, 68 | resourceID, 69 | Helpers.createERCDepositData( 70 | tokenAmount, 71 | lenRecipientAddress, 72 | recipientAddress), 73 | { from: depositerAddress } 74 | ); 75 | 76 | TruffleAssert.eventEmitted(depositTx, 'Deposit', (event) => { 77 | return event.destinationDomainID.toNumber() === domainID && 78 | event.resourceID === resourceID.toLowerCase() && 79 | event.depositNonce.toNumber() === expectedDepositNonce && 80 | event.user === depositerAddress && 81 | event.data === Helpers.createERCDepositData( 82 | tokenAmount, 83 | lenRecipientAddress, 84 | recipientAddress).toLowerCase() && 85 | event.handlerResponse === null 86 | }); 87 | }); 88 | 89 | it('Varied recipient address with length 32', async () => { 90 | const recipientAddress = Ethers.utils.keccak256(accounts[0]); 91 | const lenRecipientAddress = 32; 92 | 93 | const depositTx = await BridgeInstance.deposit( 94 | domainID, 95 | resourceID, 96 | Helpers.createERCDepositData( 97 | tokenAmount, 98 | lenRecipientAddress, 99 | recipientAddress), 100 | { from: depositerAddress } 101 | ); 102 | 103 | TruffleAssert.eventEmitted(depositTx, 'Deposit', (event) => { 104 | return event.destinationDomainID.toNumber() === domainID && 105 | event.resourceID === resourceID.toLowerCase() && 106 | event.depositNonce.toNumber() === expectedDepositNonce && 107 | event.user === depositerAddress && 108 | event.data === Helpers.createERCDepositData( 109 | tokenAmount, 110 | lenRecipientAddress, 111 | recipientAddress).toLowerCase() && 112 | event.handlerResponse === null 113 | }); 114 | }); 115 | 116 | it("When non-contract addresses are whitelisted in the handler, deposits which the addresses are set as a token address will be failed", async () => { 117 | const ZERO_Address = "0x0000000000000000000000000000000000000000"; 118 | const EOA_Address = accounts[1]; 119 | const resourceID_ZERO_Address = Helpers.createResourceID(ZERO_Address, domainID); 120 | const resourceID_EOA_Address = Helpers.createResourceID(EOA_Address, domainID); 121 | await BridgeInstance.adminSetResource(ERC20HandlerInstance.address, resourceID_ZERO_Address, ZERO_Address); 122 | await BridgeInstance.adminSetResource(ERC20HandlerInstance.address, resourceID_EOA_Address, EOA_Address); 123 | 124 | const recipientAddress = accounts[0] + accounts[1].substr(2); 125 | const lenRecipientAddress = 40; 126 | 127 | await TruffleAssert.reverts(BridgeInstance.deposit( 128 | domainID, 129 | resourceID_ZERO_Address, 130 | Helpers.createERCDepositData( 131 | tokenAmount, 132 | lenRecipientAddress, 133 | recipientAddress), 134 | { from: depositerAddress } 135 | ), "ERC20: not a contract"); 136 | 137 | await TruffleAssert.reverts(BridgeInstance.deposit( 138 | domainID, 139 | resourceID_EOA_Address, 140 | Helpers.createERCDepositData( 141 | tokenAmount, 142 | lenRecipientAddress, 143 | recipientAddress), 144 | { from: depositerAddress } 145 | ), "ERC20: not a contract"); 146 | }); 147 | }); 148 | 149 | -------------------------------------------------------------------------------- /test/handlers/erc20/depositBurn.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | 6 | const Ethers = require('ethers'); 7 | 8 | const Helpers = require('../../helpers'); 9 | 10 | const BridgeContract = artifacts.require("Bridge"); 11 | const ERC20MintableContract = artifacts.require("ERC20PresetMinterPauser"); 12 | const ERC20HandlerContract = artifacts.require("ERC20Handler"); 13 | 14 | contract('ERC20Handler - [Deposit Burn ERC20]', async (accounts) => { 15 | const relayerThreshold = 2; 16 | const domainID = 1; 17 | 18 | const depositerAddress = accounts[1]; 19 | const recipientAddress = accounts[2]; 20 | 21 | const initialTokenAmount = 100; 22 | const depositAmount = 10; 23 | 24 | let BridgeInstance; 25 | let ERC20MintableInstance1; 26 | let ERC20MintableInstance2; 27 | let ERC20HandlerInstance; 28 | 29 | let resourceID1; 30 | let resourceID2; 31 | let initialResourceIDs; 32 | let initialContractAddresses; 33 | let burnableContractAddresses; 34 | 35 | beforeEach(async () => { 36 | await Promise.all([ 37 | BridgeContract.new(domainID, [], relayerThreshold, 0, 100).then(instance => BridgeInstance = instance), 38 | ERC20MintableContract.new("token", "TOK").then(instance => ERC20MintableInstance1 = instance), 39 | ERC20MintableContract.new("token", "TOK").then(instance => ERC20MintableInstance2 = instance) 40 | ]) 41 | 42 | resourceID1 = Helpers.createResourceID(ERC20MintableInstance1.address, domainID); 43 | resourceID2 = Helpers.createResourceID(ERC20MintableInstance2.address, domainID); 44 | initialResourceIDs = [resourceID1, resourceID2]; 45 | initialContractAddresses = [ERC20MintableInstance1.address, ERC20MintableInstance2.address]; 46 | burnableContractAddresses = [ERC20MintableInstance1.address]; 47 | 48 | await Promise.all([ 49 | ERC20HandlerContract.new(BridgeInstance.address).then(instance => ERC20HandlerInstance = instance), 50 | ERC20MintableInstance1.mint(depositerAddress, initialTokenAmount) 51 | ]); 52 | 53 | await Promise.all([ 54 | ERC20MintableInstance1.approve(ERC20HandlerInstance.address, depositAmount, { from: depositerAddress }), 55 | BridgeInstance.adminSetResource(ERC20HandlerInstance.address, resourceID1, ERC20MintableInstance1.address), 56 | BridgeInstance.adminSetResource(ERC20HandlerInstance.address, resourceID2, ERC20MintableInstance2.address), 57 | BridgeInstance.adminSetBurnable(ERC20HandlerInstance.address, ERC20MintableInstance1.address) 58 | ]); 59 | 60 | depositData = Helpers.createERCDepositData(depositAmount, 20, recipientAddress); 61 | 62 | }); 63 | 64 | it('[sanity] burnableContractAddresses should be marked true in _burnList', async () => { 65 | for (const burnableAddress of burnableContractAddresses) { 66 | const isBurnable = await ERC20HandlerInstance._burnList.call(burnableAddress); 67 | assert.isTrue(isBurnable, "Contract wasn't successfully marked burnable"); 68 | } 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/handlers/erc20/isWhitelisted.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | 6 | const TruffleAssert = require('truffle-assertions'); 7 | const Ethers = require('ethers'); 8 | 9 | const BridgeContract = artifacts.require("Bridge"); 10 | const ERC20MintableContract = artifacts.require("ERC20PresetMinterPauser"); 11 | const ERC20HandlerContract = artifacts.require("ERC20Handler"); 12 | 13 | contract('ERC20Handler - [isWhitelisted]', async () => { 14 | const AbiCoder = new Ethers.utils.AbiCoder(); 15 | 16 | const relayerThreshold = 2; 17 | const domainID = 1; 18 | 19 | let BridgeInstance; 20 | let ERC20MintableInstance1; 21 | let ERC20MintableInstance2; 22 | let initialResourceIDs; 23 | let initialContractAddresses; 24 | let burnableContractAddresses; 25 | 26 | beforeEach(async () => { 27 | await Promise.all([ 28 | BridgeContract.new(domainID, [], relayerThreshold, 0, 100).then(instance => BridgeInstance = instance), 29 | ERC20MintableContract.new("token", "TOK").then(instance => ERC20MintableInstance1 = instance), 30 | ERC20MintableContract.new("token", "TOK").then(instance => ERC20MintableInstance2 = instance) 31 | ]) 32 | 33 | initialResourceIDs = []; 34 | resourceID1 = Ethers.utils.hexZeroPad((ERC20MintableInstance1.address + Ethers.utils.hexlify(domainID).substr(2)), 32); 35 | initialResourceIDs.push(resourceID1); 36 | initialContractAddresses = [ERC20MintableInstance1.address]; 37 | burnableContractAddresses = []; 38 | }); 39 | 40 | it('[sanity] contract should be deployed successfully', async () => { 41 | await TruffleAssert.passes(ERC20HandlerContract.new(BridgeInstance.address)); 42 | }); 43 | 44 | it('initialContractAddress should be whitelisted', async () => { 45 | const ERC20HandlerInstance = await ERC20HandlerContract.new(BridgeInstance.address); 46 | await BridgeInstance.adminSetResource(ERC20HandlerInstance.address, resourceID1, ERC20MintableInstance1.address); 47 | const isWhitelisted = await ERC20HandlerInstance._contractWhitelist.call(ERC20MintableInstance1.address); 48 | assert.isTrue(isWhitelisted, "Contract wasn't successfully whitelisted"); 49 | }); 50 | 51 | 52 | // as we are working with a mandatory whitelist, these tests are currently not necessary 53 | 54 | // it('initialContractAddress should not be whitelisted', async () => { 55 | // const ERC20HandlerInstance = await ERC20HandlerContract.new(BridgeInstance.address, initialResourceIDs, initialContractAddresses); 56 | // const isWhitelisted = await ERC20HandlerInstance._contractWhitelist.call(ERC20MintableInstance1.address); 57 | // assert.isFalse(isWhitelisted, "Contract should not have been whitelisted"); 58 | // }); 59 | 60 | // it('ERC20MintableInstance2.address should not be whitelisted', async () => { 61 | // const ERC20HandlerInstance = await ERC20HandlerContract.new(BridgeInstance.address, initialResourceIDs, initialContractAddresses); 62 | // const isWhitelisted = await ERC20HandlerInstance._contractWhitelist.call(ERC20MintableInstance2.address); 63 | // assert.isFalse(isWhitelisted, "Contract should not have been whitelisted"); 64 | // }); 65 | }); 66 | -------------------------------------------------------------------------------- /test/handlers/erc20/setResourceIDAndContractAddress.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | 6 | const TruffleAssert = require('truffle-assertions'); 7 | const Ethers = require('ethers'); 8 | 9 | const BridgeContract = artifacts.require("Bridge"); 10 | const ERC20MintableContract = artifacts.require("ERC20PresetMinterPauser"); 11 | const ERC20HandlerContract = artifacts.require("ERC20Handler"); 12 | 13 | contract('ERC20Handler - [setResourceIDAndContractAddress]', async () => { 14 | const AbiCoder = new Ethers.utils.AbiCoder(); 15 | 16 | const relayerThreshold = 2; 17 | const domainID = 1; 18 | 19 | let BridgeInstance; 20 | let ERC20MintableInstance1; 21 | let ERC20HandlerInstance; 22 | let initialResourceIDs; 23 | let initialContractAddresses; 24 | let burnableContractAddresses; 25 | 26 | beforeEach(async () => { 27 | BridgeInstance = await BridgeContract.new(domainID, [], relayerThreshold, 0, 100); 28 | ERC20MintableInstance1 = await ERC20MintableContract.new("token", "TOK"); 29 | 30 | initialResourceIDs = [Ethers.utils.hexZeroPad((ERC20MintableInstance1.address + Ethers.utils.hexlify(domainID).substr(2)), 32)]; 31 | initialContractAddresses = [ERC20MintableInstance1.address]; 32 | burnableContractAddresses = []; 33 | 34 | ERC20HandlerInstance = await ERC20HandlerContract.new(BridgeInstance.address); 35 | await BridgeInstance.adminSetResource(ERC20HandlerInstance.address, initialResourceIDs[0], initialContractAddresses[0]); 36 | }); 37 | 38 | it("[sanity] ERC20MintableInstance1's resourceID and contract address should be set correctly", async () => { 39 | const retrievedTokenAddress = await ERC20HandlerInstance._resourceIDToTokenContractAddress.call(initialResourceIDs[0]); 40 | assert.strictEqual(Ethers.utils.getAddress(ERC20MintableInstance1.address), retrievedTokenAddress); 41 | 42 | const retrievedResourceID = await ERC20HandlerInstance._tokenContractAddressToResourceID.call(ERC20MintableInstance1.address); 43 | assert.strictEqual(initialResourceIDs[0].toLowerCase(), retrievedResourceID.toLowerCase()); 44 | }); 45 | 46 | it('new resourceID and corresponding contract address should be set correctly', async () => { 47 | const ERC20MintableInstance2 = await ERC20MintableContract.new("token", "TOK"); 48 | const secondERC20ResourceID = Ethers.utils.hexZeroPad((ERC20MintableInstance2.address + Ethers.utils.hexlify(domainID).substr(2)), 32); 49 | 50 | await BridgeInstance.adminSetResource(ERC20HandlerInstance.address, secondERC20ResourceID, ERC20MintableInstance2.address); 51 | 52 | const retrievedTokenAddress = await ERC20HandlerInstance._resourceIDToTokenContractAddress.call(secondERC20ResourceID); 53 | assert.strictEqual(Ethers.utils.getAddress(ERC20MintableInstance2.address).toLowerCase(), retrievedTokenAddress.toLowerCase()); 54 | 55 | const retrievedResourceID = await ERC20HandlerInstance._tokenContractAddressToResourceID.call(ERC20MintableInstance2.address); 56 | assert.strictEqual(secondERC20ResourceID.toLowerCase(), retrievedResourceID.toLowerCase()); 57 | }); 58 | 59 | it('existing resourceID should be updated correctly with new token contract address', async () => { 60 | await BridgeInstance.adminSetResource(ERC20HandlerInstance.address, initialResourceIDs[0], ERC20MintableInstance1.address); 61 | 62 | const ERC20MintableInstance2 = await ERC20MintableContract.new("token", "TOK"); 63 | await BridgeInstance.adminSetResource(ERC20HandlerInstance.address, initialResourceIDs[0], ERC20MintableInstance2.address); 64 | 65 | const retrievedTokenAddress = await ERC20HandlerInstance._resourceIDToTokenContractAddress.call(initialResourceIDs[0]); 66 | assert.strictEqual(ERC20MintableInstance2.address, retrievedTokenAddress); 67 | 68 | const retrievedResourceID = await ERC20HandlerInstance._tokenContractAddressToResourceID.call(ERC20MintableInstance2.address); 69 | assert.strictEqual(initialResourceIDs[0].toLowerCase(), retrievedResourceID.toLowerCase()); 70 | }); 71 | 72 | it('existing resourceID should be updated correctly with new handler address', async () => { 73 | await BridgeInstance.adminSetResource(ERC20HandlerInstance.address, initialResourceIDs[0], ERC20MintableInstance1.address); 74 | 75 | const ERC20MintableInstance2 = await ERC20MintableContract.new("token", "TOK"); 76 | const secondERC20ResourceID = [Ethers.utils.hexZeroPad((ERC20MintableInstance2.address + Ethers.utils.hexlify(domainID).substr(2)), 32)]; 77 | ERC20HandlerInstance2 = await ERC20HandlerContract.new(BridgeInstance.address); 78 | 79 | await BridgeInstance.adminSetResource(ERC20HandlerInstance2.address, initialResourceIDs[0], ERC20MintableInstance2.address); 80 | 81 | const bridgeHandlerAddress = await BridgeInstance._resourceIDToHandlerAddress.call(initialResourceIDs[0]); 82 | assert.strictEqual(bridgeHandlerAddress.toLowerCase(), ERC20HandlerInstance2.address.toLowerCase()); 83 | }); 84 | 85 | it('existing resourceID should be replaced by new resourceID in handler', async () => { 86 | await BridgeInstance.adminSetResource(ERC20HandlerInstance.address, initialResourceIDs[0], ERC20MintableInstance1.address); 87 | 88 | const ERC20MintableInstance2 = await ERC20MintableContract.new("token", "TOK"); 89 | const secondERC20ResourceID = Ethers.utils.hexZeroPad((ERC20MintableInstance2.address + Ethers.utils.hexlify(domainID).substr(2)), 32); 90 | 91 | await BridgeInstance.adminSetResource(ERC20HandlerInstance.address, secondERC20ResourceID, ERC20MintableInstance1.address); 92 | 93 | const retrievedResourceID = await ERC20HandlerInstance._tokenContractAddressToResourceID.call(ERC20MintableInstance1.address); 94 | assert.strictEqual(secondERC20ResourceID.toLowerCase(), retrievedResourceID.toLowerCase()); 95 | 96 | const retrievedContractAddress = await ERC20HandlerInstance._resourceIDToTokenContractAddress.call(secondERC20ResourceID); 97 | assert.strictEqual(retrievedContractAddress.toLowerCase(), ERC20MintableInstance1.address.toLowerCase()); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /test/handlers/erc721/burnList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | 6 | const TruffleAssert = require('truffle-assertions'); 7 | const Ethers = require('ethers'); 8 | 9 | const Helpers =require('../../helpers'); 10 | 11 | const BridgeContract = artifacts.require("Bridge"); 12 | const ERC721MintableContract = artifacts.require("ERC721MinterBurnerPauser"); 13 | const ERC721HandlerContract = artifacts.require("ERC721Handler"); 14 | 15 | contract('ERC721Handler - [Burn ERC721]', async () => { 16 | const relayerThreshold = 2; 17 | const domainID = 1; 18 | 19 | let BridgeInstance; 20 | let ERC721MintableInstance1; 21 | let ERC721MintableInstance2; 22 | let resourceID1; 23 | let resourceID2; 24 | let initialResourceIDs; 25 | let initialContractAddresses; 26 | let burnableContractAddresses; 27 | 28 | beforeEach(async () => { 29 | await Promise.all([ 30 | BridgeContract.new(domainID, [], relayerThreshold, 0, 100).then(instance => BridgeInstance = instance), 31 | ERC721MintableContract.new("token", "TOK", "").then(instance => ERC721MintableInstance1 = instance), 32 | ERC721MintableContract.new("token", "TOK", "").then(instance => ERC721MintableInstance2 = instance) 33 | ]) 34 | 35 | resourceID1 = Helpers.createResourceID(ERC721MintableInstance1.address, domainID); 36 | resourceID2 = Helpers.createResourceID(ERC721MintableInstance2.address, domainID); 37 | initialResourceIDs = [resourceID1, resourceID2]; 38 | initialContractAddresses = [ERC721MintableInstance1.address, ERC721MintableInstance2.address]; 39 | burnableContractAddresses = [ERC721MintableInstance1.address] 40 | }); 41 | 42 | it('[sanity] contract should be deployed successfully', async () => { 43 | await TruffleAssert.passes(ERC721HandlerContract.new(BridgeInstance.address)); 44 | }); 45 | 46 | it('burnableContractAddresses should be marked true in _burnList', async () => { 47 | const ERC721HandlerInstance = await ERC721HandlerContract.new(BridgeInstance.address); 48 | 49 | for (i = 0; i < initialResourceIDs.length; i++) { 50 | await TruffleAssert.passes(BridgeInstance.adminSetResource(ERC721HandlerInstance.address, initialResourceIDs[i], initialContractAddresses[i])); 51 | } 52 | 53 | for (i = 0; i < burnableContractAddresses.length; i++) { 54 | await TruffleAssert.passes(BridgeInstance.adminSetBurnable(ERC721HandlerInstance.address, burnableContractAddresses[i])); 55 | } 56 | 57 | for (const burnableAddress of burnableContractAddresses) { 58 | const isBurnable = await ERC721HandlerInstance._burnList.call(burnableAddress); 59 | assert.isTrue(isBurnable, "Contract wasn't successfully marked burnable"); 60 | } 61 | }); 62 | 63 | it('ERC721MintableInstance2.address should not be marked true in _burnList', async () => { 64 | const ERC721HandlerInstance = await ERC721HandlerContract.new(BridgeInstance.address); 65 | 66 | for (i = 0; i < initialResourceIDs.length; i++) { 67 | await TruffleAssert.passes(BridgeInstance.adminSetResource(ERC721HandlerInstance.address, initialResourceIDs[i], initialContractAddresses[i])); 68 | } 69 | 70 | for (i = 0; i < burnableContractAddresses.length; i++) { 71 | await TruffleAssert.passes(BridgeInstance.adminSetBurnable(ERC721HandlerInstance.address, burnableContractAddresses[i])); 72 | } 73 | 74 | const isBurnable = await ERC721HandlerInstance._burnList.call(ERC721MintableInstance2.address); 75 | assert.isFalse(isBurnable, "Contract shouldn't be marked burnable"); 76 | }); 77 | 78 | it('ERC721MintableInstance2.address should be marked true in _burnList after setBurnable is called', async () => { 79 | const ERC721HandlerInstance = await ERC721HandlerContract.new(BridgeInstance.address); 80 | 81 | for (i = 0; i < initialResourceIDs.length; i++) { 82 | await TruffleAssert.passes(BridgeInstance.adminSetResource(ERC721HandlerInstance.address, initialResourceIDs[i], initialContractAddresses[i])); 83 | } 84 | 85 | for (i = 0; i < burnableContractAddresses.length; i++) { 86 | await TruffleAssert.passes(BridgeInstance.adminSetBurnable(ERC721HandlerInstance.address, burnableContractAddresses[i])); 87 | } 88 | 89 | await BridgeInstance.adminSetBurnable(ERC721HandlerInstance.address, ERC721MintableInstance2.address); 90 | const isBurnable = await ERC721HandlerInstance._burnList.call(ERC721MintableInstance2.address); 91 | assert.isTrue(isBurnable, "Contract wasn't successfully marked burnable"); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /test/handlers/erc721/deposit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | const TruffleAssert = require('truffle-assertions'); 6 | const Ethers = require('ethers'); 7 | 8 | const Helpers = require('../../helpers'); 9 | 10 | const BridgeContract = artifacts.require("Bridge"); 11 | const ERC721MintableContract = artifacts.require("ERC721MinterBurnerPauser"); 12 | const ERC721HandlerContract = artifacts.require("ERC721Handler"); 13 | 14 | contract('ERC721Handler - [Deposit ERC721]', async (accounts) => { 15 | const relayerThreshold = 2; 16 | const domainID = 1; 17 | const expectedDepositNonce = 1; 18 | const depositerAddress = accounts[1]; 19 | const tokenID = 1; 20 | 21 | let BridgeInstance; 22 | let ERC721MintableInstance; 23 | let ERC721HandlerInstance; 24 | 25 | let resourceID; 26 | let initialResourceIDs; 27 | let initialContractAddresses; 28 | let burnableContractAddresses; 29 | 30 | beforeEach(async () => { 31 | await Promise.all([ 32 | BridgeContract.new(domainID, [], relayerThreshold, 0, 100).then(instance => BridgeInstance = instance), 33 | ERC721MintableContract.new("token", "TOK", "").then(instance => ERC721MintableInstance = instance) 34 | ]) 35 | 36 | resourceID = Helpers.createResourceID(ERC721MintableInstance.address, domainID); 37 | initialResourceIDs = [resourceID]; 38 | initialContractAddresses = [ERC721MintableInstance.address]; 39 | burnableContractAddresses = [] 40 | 41 | await Promise.all([ 42 | ERC721HandlerContract.new(BridgeInstance.address).then(instance => ERC721HandlerInstance = instance), 43 | ERC721MintableInstance.mint(depositerAddress, tokenID, "") 44 | ]); 45 | 46 | await Promise.all([ 47 | ERC721MintableInstance.approve(ERC721HandlerInstance.address, tokenID, { from: depositerAddress }), 48 | BridgeInstance.adminSetResource(ERC721HandlerInstance.address, resourceID, ERC721MintableInstance.address) 49 | ]); 50 | }); 51 | 52 | it('[sanity] depositer owns ERC721 with tokenID', async () => { 53 | const tokenOwner = await ERC721MintableInstance.ownerOf(tokenID); 54 | assert.equal(depositerAddress, tokenOwner); 55 | }); 56 | 57 | it('[sanity] ERC721HandlerInstance.address has an allowance for tokenID', async () => { 58 | const tokenAllowee = await ERC721MintableInstance.getApproved(tokenID); 59 | assert.equal(ERC721HandlerInstance.address, tokenAllowee); 60 | }); 61 | 62 | it('Varied recipient address with length 40', async () => { 63 | const recipientAddress = accounts[0] + accounts[1].substr(2); 64 | const lenRecipientAddress = 40; 65 | 66 | const depositTx = await BridgeInstance.deposit( 67 | domainID, 68 | resourceID, 69 | Helpers.createERCDepositData( 70 | tokenID, 71 | lenRecipientAddress, 72 | recipientAddress), 73 | { from: depositerAddress } 74 | ); 75 | 76 | TruffleAssert.eventEmitted(depositTx, 'Deposit', (event) => { 77 | return event.destinationDomainID.toNumber() === domainID && 78 | event.resourceID === resourceID.toLowerCase() && 79 | event.depositNonce.toNumber() === expectedDepositNonce && 80 | event.user === depositerAddress && 81 | event.data === Helpers.createERCDepositData( 82 | tokenID, 83 | lenRecipientAddress, 84 | recipientAddress).toLowerCase() && 85 | event.handlerResponse === null 86 | }); 87 | }); 88 | 89 | it('Varied recipient address with length 32', async () => { 90 | const recipientAddress = Ethers.utils.keccak256(accounts[0]); 91 | const lenRecipientAddress = 32; 92 | 93 | const depositTx = await BridgeInstance.deposit( 94 | domainID, 95 | resourceID, 96 | Helpers.createERCDepositData( 97 | tokenID, 98 | lenRecipientAddress, 99 | recipientAddress), 100 | { from: depositerAddress } 101 | ); 102 | 103 | TruffleAssert.eventEmitted(depositTx, 'Deposit', (event) => { 104 | return event.destinationDomainID.toNumber() === domainID && 105 | event.resourceID === resourceID.toLowerCase() && 106 | event.depositNonce.toNumber() === expectedDepositNonce && 107 | event.user === depositerAddress && 108 | event.data === Helpers.createERCDepositData( 109 | tokenID, 110 | lenRecipientAddress, 111 | recipientAddress).toLowerCase() && 112 | event.handlerResponse === null 113 | }); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /test/handlers/erc721/depositBurn.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | 6 | const TruffleAssert = require('truffle-assertions'); 7 | 8 | const Helpers = require('../../helpers'); 9 | 10 | const BridgeContract = artifacts.require("Bridge"); 11 | const ERC721MintableContract = artifacts.require("ERC721MinterBurnerPauser"); 12 | const ERC721HandlerContract = artifacts.require("ERC721Handler"); 13 | 14 | contract('ERC721Handler - [Deposit Burn ERC721]', async (accounts) => { 15 | const relayerThreshold = 2; 16 | const domainID = 1; 17 | 18 | const depositerAddress = accounts[1]; 19 | const recipientAddress = accounts[2]; 20 | 21 | const tokenID = 1; 22 | 23 | let BridgeInstance; 24 | let ERC721MintableInstance1; 25 | let ERC721MintableInstance2; 26 | let ERC721HandlerInstance; 27 | 28 | let resourceID1; 29 | let resourceID2; 30 | let initialResourceIDs; 31 | let initialContractAddresses; 32 | let burnableContractAddresses; 33 | 34 | beforeEach(async () => { 35 | await Promise.all([ 36 | BridgeContract.new(domainID, [], relayerThreshold, 0, 100).then(instance => BridgeInstance = instance), 37 | ERC721MintableContract.new("token", "TOK", "").then(instance => ERC721MintableInstance1 = instance), 38 | ERC721MintableContract.new("token", "TOK", "").then(instance => ERC721MintableInstance2 = instance) 39 | ]) 40 | 41 | resourceID1 = Helpers.createResourceID(ERC721MintableInstance1.address, domainID); 42 | resourceID2 = Helpers.createResourceID(ERC721MintableInstance2.address, domainID); 43 | initialResourceIDs = [resourceID1, resourceID2]; 44 | initialContractAddresses = [ERC721MintableInstance1.address, ERC721MintableInstance2.address]; 45 | burnableContractAddresses = [ERC721MintableInstance1.address] 46 | 47 | await Promise.all([ 48 | ERC721HandlerContract.new(BridgeInstance.address).then(instance => ERC721HandlerInstance = instance), 49 | ERC721MintableInstance1.mint(depositerAddress, tokenID, "") 50 | ]); 51 | 52 | await Promise.all([ 53 | ERC721MintableInstance1.approve(ERC721HandlerInstance.address, tokenID, { from: depositerAddress }), 54 | BridgeInstance.adminSetResource(ERC721HandlerInstance.address, resourceID1, ERC721MintableInstance1.address), 55 | BridgeInstance.adminSetResource(ERC721HandlerInstance.address, resourceID2, ERC721MintableInstance2.address), 56 | BridgeInstance.adminSetBurnable(ERC721HandlerInstance.address, ERC721MintableInstance1.address), 57 | ]); 58 | 59 | depositData = Helpers.createERCDepositData(tokenID, 20, recipientAddress); 60 | }); 61 | 62 | it('[sanity] burnableContractAddresses should be marked true in _burnList', async () => { 63 | for (const burnableAddress of burnableContractAddresses) { 64 | const isBurnable = await ERC721HandlerInstance._burnList.call(burnableAddress); 65 | assert.isTrue(isBurnable, "Contract wasn't successfully marked burnable"); 66 | } 67 | }); 68 | 69 | it('[sanity] ERC721MintableInstance1 tokenID has been minted for depositerAddress', async () => { 70 | const tokenOwner = await ERC721MintableInstance1.ownerOf(tokenID); 71 | assert.strictEqual(tokenOwner, depositerAddress); 72 | }); 73 | 74 | it('depositAmount of ERC721MintableInstance1 tokens should have been burned', async () => { 75 | await BridgeInstance.deposit( 76 | domainID, 77 | resourceID1, 78 | depositData, 79 | { from: depositerAddress } 80 | ); 81 | 82 | const handlerBalance = await ERC721MintableInstance1.balanceOf(ERC721HandlerInstance.address); 83 | assert.strictEqual(handlerBalance.toNumber(), 0); 84 | 85 | const depositerBalance = await ERC721MintableInstance1.balanceOf(depositerAddress); 86 | assert.strictEqual(depositerBalance.toNumber(), 0); 87 | 88 | await TruffleAssert.reverts( 89 | ERC721MintableInstance1.ownerOf(tokenID), 90 | 'ERC721: owner query for nonexistent token'); 91 | }); 92 | it('depositAmount of ERC721MintableInstance1 tokens should NOT burn from NOT token owner', async () => { 93 | await TruffleAssert.reverts(BridgeInstance.deposit( 94 | domainID, 95 | resourceID1, 96 | depositData, 97 | { from: accounts[3] } 98 | ), 99 | 'Burn not from owner'); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /test/handlers/generic/constructor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | 6 | const TruffleAssert = require('truffle-assertions'); 7 | const Ethers = require('ethers'); 8 | 9 | const Helpers = require('../../helpers'); 10 | 11 | const BridgeContract = artifacts.require("Bridge"); 12 | const GenericHandlerContract = artifacts.require("GenericHandler"); 13 | const CentrifugeAssetContract = artifacts.require("CentrifugeAsset"); 14 | 15 | contract('GenericHandler - [constructor]', async () => { 16 | const relayerThreshold = 2; 17 | const domainID = 1; 18 | const centrifugeAssetMinCount = 1; 19 | const blankFunctionSig = '0x00000000'; 20 | const blankFunctionDepositerOffset = 0; 21 | const centrifugeAssetStoreFuncSig = 'store(bytes32)'; 22 | 23 | let BridgeInstance; 24 | let CentrifugeAssetInstance1; 25 | let CentrifugeAssetInstance2; 26 | let CentrifugeAssetInstance3; 27 | let initialResourceIDs; 28 | let initialContractAddresses; 29 | let initialDepositFunctionSignatures; 30 | let initialDepositFunctionDepositerOffsets; 31 | let initialExecuteFunctionSignatures; 32 | 33 | beforeEach(async () => { 34 | await Promise.all([ 35 | BridgeContract.new(domainID, [], relayerThreshold, 0, 100).then(instance => BridgeInstance = instance), 36 | CentrifugeAssetContract.new(centrifugeAssetMinCount).then(instance => CentrifugeAssetInstance1 = instance), 37 | CentrifugeAssetContract.new(centrifugeAssetMinCount).then(instance => CentrifugeAssetInstance2 = instance), 38 | CentrifugeAssetContract.new(centrifugeAssetMinCount).then(instance => CentrifugeAssetInstance3 = instance) 39 | ]); 40 | 41 | initialResourceIDs = [ 42 | Helpers.createResourceID(CentrifugeAssetInstance1.address, domainID), 43 | Helpers.createResourceID(CentrifugeAssetInstance2.address, domainID), 44 | Helpers.createResourceID(CentrifugeAssetInstance3.address, domainID) 45 | ]; 46 | initialContractAddresses = [CentrifugeAssetInstance1.address, CentrifugeAssetInstance2.address, CentrifugeAssetInstance3.address]; 47 | 48 | const executeProposalFuncSig = Ethers.utils.keccak256(Ethers.utils.hexlify(Ethers.utils.toUtf8Bytes(centrifugeAssetStoreFuncSig))).substr(0, 10); 49 | 50 | initialDepositFunctionSignatures = [blankFunctionSig, blankFunctionSig, blankFunctionSig]; 51 | initialDepositFunctionDepositerOffsets = [blankFunctionDepositerOffset, blankFunctionDepositerOffset, blankFunctionDepositerOffset]; 52 | initialExecuteFunctionSignatures = [executeProposalFuncSig, executeProposalFuncSig, executeProposalFuncSig]; 53 | }); 54 | 55 | it('[sanity] contract should be deployed successfully', async () => { 56 | TruffleAssert.passes( 57 | await GenericHandlerContract.new( 58 | BridgeInstance.address)); 59 | }); 60 | 61 | it('contract mappings were set with expected values', async () => { 62 | const GenericHandlerInstance = await GenericHandlerContract.new( 63 | BridgeInstance.address); 64 | 65 | for (let i = 0; i < initialResourceIDs.length; i++) { 66 | await BridgeInstance.adminSetGenericResource(GenericHandlerInstance.address, initialResourceIDs[i], initialContractAddresses[i], initialDepositFunctionSignatures[i], initialDepositFunctionDepositerOffsets[i], initialExecuteFunctionSignatures[i]); 67 | } 68 | 69 | for (let i = 0; i < initialResourceIDs.length; i++) { 70 | const retrievedTokenAddress = await GenericHandlerInstance._resourceIDToContractAddress.call(initialResourceIDs[i]); 71 | assert.strictEqual(initialContractAddresses[i].toLowerCase(), retrievedTokenAddress.toLowerCase()); 72 | 73 | const retrievedResourceID = await GenericHandlerInstance._contractAddressToResourceID.call(initialContractAddresses[i]); 74 | assert.strictEqual(initialResourceIDs[i].toLowerCase(), retrievedResourceID.toLowerCase()); 75 | 76 | const retrievedDepositFunctionSig = await GenericHandlerInstance._contractAddressToDepositFunctionSignature.call(initialContractAddresses[i]); 77 | assert.strictEqual(initialDepositFunctionSignatures[i].toLowerCase(), retrievedDepositFunctionSig.toLowerCase()); 78 | 79 | const retrievedDepositFunctionDepositerOffset = await GenericHandlerInstance._contractAddressToDepositFunctionDepositerOffset.call(initialContractAddresses[i]); 80 | assert.strictEqual(initialDepositFunctionDepositerOffsets[i], retrievedDepositFunctionDepositerOffset.toNumber()); 81 | 82 | const retrievedExecuteFunctionSig = await GenericHandlerInstance._contractAddressToExecuteFunctionSignature.call(initialContractAddresses[i]); 83 | assert.strictEqual(initialExecuteFunctionSignatures[i].toLowerCase(), retrievedExecuteFunctionSig.toLowerCase()); 84 | } 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/handlers/generic/executeProposal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | 6 | const TruffleAssert = require('truffle-assertions'); 7 | const Ethers = require('ethers'); 8 | 9 | const Helpers = require('../../helpers'); 10 | 11 | const BridgeContract = artifacts.require("Bridge"); 12 | const CentrifugeAssetContract = artifacts.require("CentrifugeAsset"); 13 | const GenericHandlerContract = artifacts.require("GenericHandler"); 14 | 15 | contract('GenericHandler - [Execute Proposal]', async (accounts) => { 16 | const relayerThreshold = 2; 17 | const domainID = 1; 18 | const expectedDepositNonce = 1; 19 | 20 | const depositerAddress = accounts[1]; 21 | const relayer1Address = accounts[2]; 22 | const relayer2Address = accounts[3]; 23 | 24 | const initialRelayers = [relayer1Address, relayer2Address]; 25 | 26 | const centrifugeAssetMinCount = 10; 27 | const hashOfCentrifugeAsset = Ethers.utils.keccak256('0xc0ffee'); 28 | 29 | let BridgeInstance; 30 | let CentrifugeAssetInstance; 31 | let initialResourceIDs; 32 | let initialContractAddresses; 33 | let initialDepositFunctionSignatures; 34 | let initialDepositFunctionDepositerOffsets; 35 | let initialExecuteFunctionSignatures; 36 | let GenericHandlerInstance; 37 | let resourceID; 38 | let depositData; 39 | 40 | beforeEach(async () => { 41 | await Promise.all([ 42 | BridgeContract.new(domainID, initialRelayers, relayerThreshold, 0, 100).then(instance => BridgeInstance = instance), 43 | CentrifugeAssetContract.new(centrifugeAssetMinCount).then(instance => CentrifugeAssetInstance = instance) 44 | ]); 45 | 46 | const centrifugeAssetFuncSig = Helpers.getFunctionSignature(CentrifugeAssetInstance, 'store'); 47 | 48 | resourceID = Helpers.createResourceID(CentrifugeAssetInstance.address, domainID); 49 | initialResourceIDs = [resourceID]; 50 | initialContractAddresses = [CentrifugeAssetInstance.address]; 51 | initialDepositFunctionSignatures = [Helpers.blankFunctionSig]; 52 | initialDepositFunctionDepositerOffsets = [Helpers.blankFunctionDepositerOffset]; 53 | initialExecuteFunctionSignatures = [centrifugeAssetFuncSig]; 54 | 55 | GenericHandlerInstance = await GenericHandlerContract.new( 56 | BridgeInstance.address); 57 | 58 | await BridgeInstance.adminSetGenericResource(GenericHandlerInstance.address, resourceID, initialContractAddresses[0], initialDepositFunctionSignatures[0], initialDepositFunctionDepositerOffsets[0], initialExecuteFunctionSignatures[0]); 59 | 60 | depositData = Helpers.createGenericDepositData(hashOfCentrifugeAsset); 61 | depositProposalDataHash = Ethers.utils.keccak256(GenericHandlerInstance.address + depositData.substr(2)); 62 | }); 63 | 64 | it('deposit can be executed successfully', async () => { 65 | await TruffleAssert.passes(BridgeInstance.deposit( 66 | domainID, 67 | resourceID, 68 | depositData, 69 | { from: depositerAddress } 70 | )); 71 | 72 | // relayer1 creates the deposit proposal 73 | await TruffleAssert.passes(BridgeInstance.voteProposal( 74 | domainID, 75 | expectedDepositNonce, 76 | resourceID, 77 | depositData, 78 | { from: relayer1Address } 79 | )); 80 | 81 | // relayer2 votes in favor of the deposit proposal 82 | // because the relayerThreshold is 2, the deposit proposal will go 83 | // into a finalized state 84 | // and then automatically executes the proposal 85 | await TruffleAssert.passes(BridgeInstance.voteProposal( 86 | domainID, 87 | expectedDepositNonce, 88 | resourceID, 89 | depositData, 90 | { from: relayer2Address } 91 | )); 92 | 93 | // Verifying asset was marked as stored in CentrifugeAssetInstance 94 | assert.isTrue(await CentrifugeAssetInstance._assetsStored.call(hashOfCentrifugeAsset)); 95 | }); 96 | 97 | it('AssetStored event should be emitted', async () => { 98 | await TruffleAssert.passes(BridgeInstance.deposit( 99 | domainID, 100 | resourceID, 101 | depositData, 102 | { from: depositerAddress } 103 | )); 104 | 105 | // relayer1 creates the deposit proposal 106 | await TruffleAssert.passes(BridgeInstance.voteProposal( 107 | domainID, 108 | expectedDepositNonce, 109 | resourceID, 110 | depositData, 111 | { from: relayer1Address } 112 | )); 113 | 114 | // relayer2 votes in favor of the deposit proposal 115 | // because the relayerThreshold is 2, the deposit proposal will go 116 | // into a finalized state 117 | // and then automatically executes the proposal 118 | const voteWithExecuteTx = await BridgeInstance.voteProposal( 119 | domainID, 120 | expectedDepositNonce, 121 | resourceID, 122 | depositData, 123 | { from: relayer2Address } 124 | ); 125 | 126 | const internalTx = await TruffleAssert.createTransactionResult(CentrifugeAssetInstance, voteWithExecuteTx.tx); 127 | TruffleAssert.eventEmitted(internalTx, 'AssetStored', event => { 128 | return event.asset === hashOfCentrifugeAsset; 129 | }); 130 | 131 | assert.isTrue(await CentrifugeAssetInstance._assetsStored.call(hashOfCentrifugeAsset), 132 | 'Centrifuge Asset was not successfully stored'); 133 | }); 134 | }); 135 | -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | 6 | const Ethers = require('ethers'); 7 | 8 | const blankFunctionSig = '0x00000000'; 9 | const blankFunctionDepositerOffset = 0; 10 | const AbiCoder = new Ethers.utils.AbiCoder; 11 | 12 | const toHex = (covertThis, padding) => { 13 | return Ethers.utils.hexZeroPad(Ethers.utils.hexlify(covertThis), padding); 14 | }; 15 | 16 | const abiEncode = (valueTypes, values) => { 17 | return AbiCoder.encode(valueTypes, values) 18 | }; 19 | 20 | const getFunctionSignature = (contractInstance, functionName) => { 21 | return contractInstance.abi.filter(abiProperty => abiProperty.name === functionName)[0].signature; 22 | }; 23 | 24 | const createCallData = (contractInstance, functionName, valueTypes, values) => { 25 | let signature = getFunctionSignature(contractInstance, functionName); 26 | let encodedABI = abiEncode(valueTypes, values); 27 | return signature + encodedABI.substr(2); 28 | }; 29 | 30 | const createERCDepositData = (tokenAmountOrID, lenRecipientAddress, recipientAddress) => { 31 | return '0x' + 32 | toHex(tokenAmountOrID, 32).substr(2) + // Token amount or ID to deposit (32 bytes) 33 | toHex(lenRecipientAddress, 32).substr(2) + // len(recipientAddress) (32 bytes) 34 | recipientAddress.substr(2); // recipientAddress (?? bytes) 35 | }; 36 | 37 | const createERCWithdrawData = (tokenAddress, recipientAddress, tokenAmountOrID) => { 38 | return '0x' + 39 | toHex(tokenAddress, 32).substr(2) + 40 | toHex(recipientAddress, 32).substr(2) + 41 | toHex(tokenAmountOrID, 32).substr(2); 42 | } 43 | 44 | const createERC1155DepositData = (tokenIDs, amounts) => { 45 | return abiEncode(["uint[]", "uint[]"], [tokenIDs, amounts]); 46 | } 47 | 48 | const createERC1155DepositProposalData = (tokenIDs, amounts, recipient, transferData) => { 49 | return abiEncode(["uint[]", "uint[]", "bytes", "bytes"], [tokenIDs, amounts, recipient, transferData]) 50 | } 51 | 52 | const createERC1155WithdrawData = (tokenAddress, recipient, tokenIDs, amounts, transferData) => { 53 | return abiEncode(["address", "address", "uint[]", "uint[]", "bytes"], [tokenAddress, recipient, tokenIDs, amounts, transferData]) 54 | } 55 | 56 | const createERC721DepositProposalData = ( 57 | tokenAmountOrID, lenRecipientAddress, 58 | recipientAddress, lenMetaData, metaData) => { 59 | return '0x' + 60 | toHex(tokenAmountOrID, 32).substr(2) + // Token amount or ID to deposit (32 bytes) 61 | toHex(lenRecipientAddress, 32).substr(2) + // len(recipientAddress) (32 bytes) 62 | recipientAddress.substr(2) + // recipientAddress (?? bytes) 63 | toHex(lenMetaData, 32).substr(2) + // len(metaData) (32 bytes) 64 | toHex(metaData, lenMetaData).substr(2) // metaData (?? bytes) 65 | }; 66 | 67 | const advanceBlock = () => { 68 | let provider = new Ethers.providers.JsonRpcProvider(); 69 | const time = Math.floor(Date.now() / 1000); 70 | return provider.send("evm_mine", [time]); 71 | } 72 | 73 | const createGenericDepositData = (hexMetaData) => { 74 | if (hexMetaData === null) { 75 | return '0x' + 76 | toHex(0, 32).substr(2) // len(metaData) (32 bytes) 77 | } 78 | const hexMetaDataLength = (hexMetaData.substr(2)).length / 2; 79 | return '0x' + 80 | toHex(hexMetaDataLength, 32).substr(2) + 81 | hexMetaData.substr(2) 82 | }; 83 | 84 | const createResourceID = (contractAddress, domainID) => { 85 | return toHex(contractAddress + toHex(domainID, 1).substr(2), 32) 86 | }; 87 | 88 | const assertObjectsMatch = (expectedObj, actualObj) => { 89 | for (const expectedProperty of Object.keys(expectedObj)) { 90 | assert.property(actualObj, expectedProperty, `actualObj does not have property: ${expectedProperty}`); 91 | 92 | let expectedValue = expectedObj[expectedProperty]; 93 | let actualValue = actualObj[expectedProperty]; 94 | 95 | // If expectedValue is not null, we can expected actualValue to not be null as well 96 | if (expectedValue !== null) { 97 | // Handling mixed case ETH addresses 98 | // If expectedValue is a string, we can expected actualValue to be a string as well 99 | if (expectedValue.toLowerCase !== undefined) { 100 | expectedValue = expectedValue.toLowerCase(); 101 | actualValue = actualValue.toLowerCase(); 102 | } 103 | 104 | // Handling BigNumber.js instances 105 | if (actualValue.toNumber !== undefined) { 106 | actualValue = actualValue.toNumber(); 107 | } 108 | 109 | // Truffle seems to return uint/ints as strings 110 | // Also handles when Truffle returns hex number when expecting uint/int 111 | if (typeof expectedValue === 'number' && typeof actualValue === 'string' || 112 | Ethers.utils.isHexString(actualValue) && typeof expectedValue === 'number') { 113 | actualValue = parseInt(actualValue); 114 | } 115 | } 116 | 117 | assert.deepEqual(expectedValue, actualValue, `expectedValue: ${expectedValue} does not match actualValue: ${actualValue}`); 118 | } 119 | }; 120 | //uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(domainID); 121 | const nonceAndId = (nonce, id) => { 122 | return Ethers.utils.hexZeroPad(Ethers.utils.hexlify(nonce), 8) + Ethers.utils.hexZeroPad(Ethers.utils.hexlify(id), 1).substr(2) 123 | } 124 | 125 | module.exports = { 126 | advanceBlock, 127 | blankFunctionSig, 128 | blankFunctionDepositerOffset, 129 | toHex, 130 | abiEncode, 131 | getFunctionSignature, 132 | createCallData, 133 | createERCDepositData, 134 | createERCWithdrawData, 135 | createERC1155DepositData, 136 | createERC1155DepositProposalData, 137 | createERC1155WithdrawData, 138 | createGenericDepositData, 139 | createERC721DepositProposalData, 140 | createResourceID, 141 | assertObjectsMatch, 142 | nonceAndId 143 | }; 144 | -------------------------------------------------------------------------------- /test/safeCast.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | const TruffleAssert = require('truffle-assertions'); 6 | 7 | const SafeCaster = artifacts.require("SafeCaster"); 8 | 9 | contract('Utils - [SafeCast]', async accounts => { 10 | let SafeCasterInstance; 11 | 12 | const BN = (num) => web3.utils.toBN(num); 13 | 14 | beforeEach(async () => { 15 | SafeCasterInstance = await SafeCaster.new(); 16 | }); 17 | 18 | it('toUint200 should revert if passed value greater than 2**200 - 1', async () => { 19 | return TruffleAssert.reverts(SafeCasterInstance.toUint200(BN(2).pow(BN(200))), "value does not fit in 200 bits"); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 ChainSafe Systems 3 | * SPDX-License-Identifier: LGPL-3.0-only 4 | */ 5 | 6 | /** 7 | * Use this file to configure your truffle project. It's seeded with some 8 | * common settings for different networks and features like migrations, 9 | * compilation and testing. Uncomment the ones you need or modify 10 | * them to suit your project as necessary. 11 | * 12 | * More information about configuration can be found at: 13 | * 14 | * truffleframework.com/docs/advanced/configuration 15 | * 16 | * To deploy via Infura you'll need a wallet provider (like truffle-hdwallet-provider) 17 | * to sign your transactions before they're sent to a remote public node. Infura accounts 18 | * are available for free at: infura.io/register. 19 | * 20 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 21 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 22 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 23 | * 24 | */ 25 | 26 | // const HDWalletProvider = require('truffle-hdwallet-provider'); 27 | // const infuraKey = "fj4jll3k....."; 28 | // 29 | // const fs = require('fs'); 30 | // const mnemonic = fs.readFileSync(".secret").toString().trim(); 31 | 32 | module.exports = { 33 | /** 34 | * Networks define how you connect to your ethereum client and let you set the 35 | * defaults web3 uses to send transactions. If you don't specify one truffle 36 | * will spin up a development blockchain for you on port 9545 when you 37 | * run `develop` or `test`. You can ask a truffle command to use a specific 38 | * network from the command line, e.g 39 | * 40 | * $ truffle test --network 41 | */ 42 | 43 | plugins: ["solidity-coverage"], 44 | networks: { 45 | // Useful for testing. The `development` name is special - truffle uses it by default 46 | // if it's defined here and no other network is specified at the command line. 47 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal 48 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 49 | // options below to some value. 50 | 51 | geth: { 52 | host: "127.0.0.1", // Localhost (default: none) 53 | port: 8545, // Standard Ethereum port (default: none) 54 | network_id: "5", // Any network (default: none) 55 | }, 56 | 57 | test: { 58 | host: "127.0.0.1", // Localhost (default: none) 59 | port: 8545, // Standard Ethereum port (default: none) 60 | network_id: "*", // Any network (default: none) 61 | disableConfirmationListener: true, 62 | }, 63 | // Another network with more advanced options... 64 | // advanced: { 65 | // port: 8777, // Custom port 66 | // network_id: 1342, // Custom network 67 | // gas: 8500000, // Gas sent with each transaction (default: ~6700000) 68 | // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) 69 | // from:
, // Account to send txs from (default: accounts[0]) 70 | // websockets: true // Enable EventEmitter interface for web3 (default: false) 71 | // }, 72 | 73 | // Useful for deploying to a public network. 74 | // NB: It's important to wrap the provider as a function. 75 | // ropsten: { 76 | // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`), 77 | // network_id: 3, // Ropsten's id 78 | // gas: 5500000, // Ropsten has a lower block limit than mainnet 79 | // confirmations: 2, // # of confs to wait between deployments. (default: 0) 80 | // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) 81 | // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) 82 | // }, 83 | 84 | // Useful for private networks 85 | // private: { 86 | // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), 87 | // network_id: 2111, // This network is yours, in the cloud. 88 | // production: true // Treats this network as if it was a public net. (default: false) 89 | // } 90 | }, 91 | 92 | // Set default mocha options here, use special reporters etc. 93 | mocha: { 94 | // timeout: 100000 95 | }, 96 | 97 | // Configure your compilers 98 | compilers: { 99 | solc: { 100 | version: "0.8.11", // Fetch exact version from solc-bin (default: truffle's version) 101 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 102 | settings: { // See the solidity docs for advice about optimization and evmVersion 103 | optimizer: { 104 | enabled: true, 105 | runs: 200 106 | }, 107 | // evmVersion: "byzantium" 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "module": "esnext", 5 | "target": "es5", 6 | "lib": ["es6", "dom", "es2016", "es2017"], 7 | "sourceMap": true, 8 | "allowJs": false, 9 | "jsx": "react", 10 | "strict": true, 11 | "declaration": true, 12 | "moduleResolution": "node", 13 | "forceConsistentCasingInFileNames": true, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true, 17 | "strictNullChecks": true, 18 | "suppressImplicitAnyIndexErrors": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true 21 | }, 22 | "include": ["src"], 23 | "exclude": ["node_modules/"] 24 | } 25 | --------------------------------------------------------------------------------