├── .env.template ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .solhint.json ├── .solhintignore ├── LICENSE ├── README.md ├── contracts ├── DynamicMerkleTree.sol ├── MerklizedERC20.sol ├── MerklizedStaking.sol ├── TestERC20.sol ├── TestMerklizedERC20.sol └── app │ ├── Bridge.sol │ ├── TestBridge.sol │ └── l2_bridge │ ├── L2BridgeDestination.sol │ ├── L2BridgeLib.sol │ ├── L2BridgeSource.sol │ ├── TestL2BridgeSource.sol │ ├── arbitrum │ ├── ArbitrumBridgeDestination.sol │ ├── ArbitrumBridgeSource.sol │ └── ArbitrumL1Bridge.sol │ └── optimism │ ├── OptimismBridgeDestination.sol │ ├── OptimismBridgeSource.sol │ ├── OptimismL1Bridge.sol │ └── iAbs_BaseCrossDomainMessenger.sol ├── hardhat.config.js ├── package.json ├── packages └── arb-shared-dependencies │ ├── contracts │ ├── AddressAliasHelper.sol │ ├── ArbAddressTable.sol │ ├── ArbSys.sol │ ├── Bridge.sol │ ├── BytesLib.sol │ ├── Cloneable.sol │ ├── DebugPrint.sol │ ├── ICloneable.sol │ ├── Inbox.sol │ ├── Outbox.sol │ ├── ProxyUtil.sol │ └── Whitelist.sol │ ├── hardhat.config.js │ ├── index.js │ └── package.json ├── scripts ├── arb_bridge_deploy.js ├── arb_bridge_env_sample ├── deploy.js └── opt_bridge_deploy.js ├── test ├── bridge-test.js ├── l2bridge-test.js ├── merklized-erc20-test.js └── merklized-staking-test.js └── yarn.lock /.env.template: -------------------------------------------------------------------------------- 1 | ETHERSCAN_API_KEY=ABC123ABC123ABC123ABC123ABC123ABC1 2 | ROPSTEN_URL=https://eth-ropsten.alchemyapi.io/v2/ 3 | PRIVATE_KEY=0xabc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: false, 4 | es2021: true, 5 | mocha: true, 6 | node: true, 7 | }, 8 | extends: [ 9 | "standard", 10 | "plugin:prettier/recommended", 11 | "plugin:node/recommended", 12 | ], 13 | parserOptions: { 14 | ecmaVersion: 12, 15 | }, 16 | overrides: [ 17 | { 18 | files: ["hardhat.config.js"], 19 | globals: { task: true }, 20 | }, 21 | { 22 | files: ["scripts/**"], 23 | rules: { "no-process-exit": "off" }, 24 | }, 25 | { 26 | files: ["hardhat.config.js", "scripts/**", "test/**"], 27 | rules: { "node/no-unpublished-require": "off" }, 28 | }, 29 | ], 30 | }; 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # IDEs and editors 33 | .idea 34 | .project 35 | .classpath 36 | .c9/ 37 | *.launch 38 | .settings/ 39 | *.sublime-workspace 40 | 41 | # IDE - VSCode 42 | .vscode/* 43 | !.vscode/settings.json 44 | !.vscode/tasks.json 45 | !.vscode/launch.json 46 | !.vscode/extensions.json 47 | 48 | # misc 49 | .sass-cache 50 | connect.lock 51 | typings 52 | 53 | # Logs 54 | logs 55 | *.log 56 | npm-debug.log* 57 | yarn-debug.log* 58 | yarn-error.log* 59 | 60 | 61 | # Dependency directories 62 | node_modules/ 63 | jspm_packages/ 64 | 65 | # Optional npm cache directory 66 | .npm 67 | 68 | # Optional eslint cache 69 | .eslintcache 70 | 71 | # Optional REPL history 72 | .node_repl_history 73 | 74 | # Output of 'npm pack' 75 | *.tgz 76 | 77 | # Yarn Integrity file 78 | .yarn-integrity 79 | 80 | # dotenv environment variables file 81 | .env 82 | 83 | # next.js build output 84 | .next 85 | 86 | # Lerna 87 | lerna-debug.log 88 | 89 | # System Files 90 | .DS_Store 91 | Thumbs.db 92 | 93 | 94 | node_modules 95 | .env 96 | 97 | #Hardhat files 98 | cache 99 | artifacts 100 | 101 | node_modules 102 | .env 103 | 104 | #Hardhat files 105 | cache 106 | artifacts 107 | bin 108 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage* 5 | gasReporterOutput.json 6 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "compiler-version": ["error", "^0.5.0"], 5 | "func-visibility": ["warn", { "ignoreConstructors": true }] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 QuarkChain 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # On-Chain Dynamic Merkle Tree 2 | 3 | This project implements an on-chain dynamic Merkle tree library with some examples. The key features are: 4 | - efficient updating/appending a node in the tree with O(1) storage write cost; 5 | - example javascript code to generate Merkle proof for updating/appending (in test/); 6 | - examples of Merkelized Staking and Merkelized ERC20. 7 | 8 | 9 | # To Play 10 | 11 | Try running some of the following tasks: 12 | 13 | ```shell 14 | npx hardhat accounts 15 | npx hardhat compile 16 | npx hardhat clean 17 | npx hardhat test 18 | npx hardhat node 19 | npx hardhat help 20 | REPORT_GAS=true npx hardhat test 21 | npx hardhat coverage 22 | npx hardhat run scripts/deploy.js 23 | node scripts/deploy.js 24 | npx eslint '**/*.js' 25 | npx eslint '**/*.js' --fix 26 | npx prettier '**/*.{json,sol,md}' --check 27 | npx prettier '**/*.{json,sol,md}' --write 28 | npx solhint 'contracts/**/*.sol' 29 | npx solhint 'contracts/**/*.sol' --fix 30 | ``` 31 | 32 | # Cross L2 Bridge Example 33 | 34 | This project implements an token transfer between two arbitrum instances using Dynamic Merkle Tree 35 | 36 | 37 | ## To Play 38 | 39 | Try running some of the following tasks: 40 | 41 | ```shell 42 | npm run arb_bridge_deploy 43 | ``` 44 | 45 | ## Deployed Contracts on Arbitrum Testnet 46 | 47 | - L1 Bridge: [0x972096D19c43aAdFaBA7C433bF33f34be568330F](https://rinkeby.etherscan.io/address/0x972096D19c43aAdFaBA7C433bF33f34be568330F#code) 48 | - L2 Bridge Source: [0xc31dB9BC7d5bfD1e0aEBe936b20E025831bCb3Be](https://testnet.arbiscan.io/address/0xc31dB9BC7d5bfD1e0aEBe936b20E025831bCb3Be#code) 49 | - L2 Bridge Destination: [0xa4f7e85327BE5648488844A85a00197af9ef136a](https://testnet.arbiscan.io/address/0xa4f7e85327BE5648488844A85a00197af9ef136a#code) 50 | - L2 Token Source: [0x1158F18eFe4DAF4e71b31b077b340927A8A9f5Ef](https://testnet.arbiscan.io/address/0x1158F18eFe4DAF4e71b31b077b340927A8A9f5Ef#code) 51 | - L2 Token Destination: [0x68e7155dF845635DF488fdED81BFC76C8210FBB2](https://testnet.arbiscan.io/address/0x68e7155dF845635DF488fdED81BFC76C8210FBB2#code) 52 | 53 | 54 | ## Deployed Contracts on Optimism Testnet 55 | 56 | - L1 Bridge: [0x5095135E861845dee965141fEA9061F38C85c699](https://kovan.etherscan.io/address/0x5095135E861845dee965141fEA9061F38C85c699#code) 57 | - L2 Bridge Source: [0x5095135E861845dee965141fEA9061F38C85c699](https://kovan-optimistic.etherscan.io/address/0x5095135E861845dee965141fEA9061F38C85c699#code) 58 | - L2 Bridge Destination: [0xcC3C762734E54F65c7597Db7c479164fC6C3dFA0](https://kovan-optimistic.etherscan.io/address/0xcC3C762734E54F65c7597Db7c479164fC6C3dFA0#code) 59 | - L2 Token Source: [0xd36AD433b356b304442d23b884bed706Cdf49583](https://kovan-optimistic.etherscan.io/address/0xd36AD433b356b304442d23b884bed706Cdf49583#code) 60 | - L2 Token Destination: [0xB515eb506C43C468F616fa0Ad9BD4B94B90B71e9](https://kovan-optimistic.etherscan.io/address/0xB515eb506C43C468F616fa0Ad9BD4B94B90B71e9#code) 61 | 62 | 63 | # Disclaimer 64 | The code is not audited. USE AT YOUR OWN RISK. 65 | 66 | -------------------------------------------------------------------------------- /contracts/DynamicMerkleTree.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "hardhat/console.sol"; 5 | 6 | library DynamicMerkleTree { 7 | function calcRootHash( 8 | uint256 _idx, 9 | uint256 _len, 10 | bytes32 _leafHash, 11 | bytes32[] memory _proof 12 | ) internal pure returns (bytes32 _rootHash) { 13 | if (_len == 0) { 14 | return bytes32(0); 15 | } 16 | 17 | uint256 _proofIdx = 0; 18 | bytes32 _nodeHash = _leafHash; 19 | 20 | while (_len > 1) { 21 | uint256 _peerIdx = (_idx / 2) * 2; 22 | bytes32 _peerHash = bytes32(0); 23 | if (_peerIdx == _idx) { 24 | _peerIdx += 1; 25 | } 26 | if (_peerIdx < _len) { 27 | _peerHash = _proof[_proofIdx]; 28 | _proofIdx += 1; 29 | } 30 | 31 | bytes32 _parentHash = bytes32(0); 32 | if (_peerIdx >= _len && _idx >= _len) { 33 | // pass, _parentHash = bytes32(0) 34 | } else if (_peerIdx > _idx) { 35 | _parentHash = keccak256(abi.encodePacked(_nodeHash, _peerHash)); 36 | } else { 37 | _parentHash = keccak256(abi.encodePacked(_peerHash, _nodeHash)); 38 | } 39 | 40 | _len = (_len - 1) / 2 + 1; 41 | _idx = _idx / 2; 42 | _nodeHash = _parentHash; 43 | } 44 | 45 | return _nodeHash; 46 | } 47 | 48 | function calcRootHashWithoutLength( 49 | uint256 _idx, 50 | bytes32 _leafHash, 51 | bytes32[] memory _proof 52 | ) internal pure returns (bytes32 _rootHash) { 53 | bytes32 _nodeHash = _leafHash; 54 | 55 | for (uint256 i = 0; i < _proof.length; i++) { 56 | uint256 _peerIdx = (_idx / 2) * 2; 57 | bytes32 _peerHash = _proof[i]; 58 | bytes32 _parentHash = bytes32(0); 59 | if (_peerIdx > _idx) { 60 | _parentHash = keccak256(abi.encodePacked(_nodeHash, _peerHash)); 61 | } else { 62 | _parentHash = keccak256(abi.encodePacked(_peerHash, _nodeHash)); 63 | } 64 | 65 | _idx = _idx / 2; 66 | _nodeHash = _parentHash; 67 | } 68 | 69 | return _nodeHash; 70 | } 71 | 72 | function verify( 73 | uint256 _idx, 74 | uint256 _len, 75 | bytes32 _root, 76 | bytes32 _oldLeafHash, 77 | bytes32[] memory _proof 78 | ) internal pure returns (bool) { 79 | return calcRootHash(_idx, _len, _oldLeafHash, _proof) == _root; 80 | } 81 | 82 | function update( 83 | uint256 _idx, 84 | uint256 _len, 85 | bytes32 _oldRoot, 86 | bytes32 _oldLeafHash, 87 | bytes32 _newLeafHash, 88 | bytes32[] memory _proof 89 | ) internal pure returns (bytes32 _newRoot) { 90 | require( 91 | verify(_idx, _len, _oldRoot, _oldLeafHash, _proof), 92 | "ERR_PROOF" 93 | ); 94 | return calcRootHash(_idx, _len, _newLeafHash, _proof); 95 | } 96 | 97 | function append( 98 | uint256 _len, 99 | bytes32 _oldRoot, 100 | bytes32 _leafHash, 101 | bytes32[] memory _proof 102 | ) internal pure returns (bytes32 _newRoot) { 103 | if (_len > 0) { 104 | if ((_len & (_len - 1)) == 0) { 105 | // 2^n, a new layer will be added. 106 | require(_proof[0] == _oldRoot, "ERR_PROOF"); 107 | } else { 108 | require( 109 | verify(_len, _len, _oldRoot, bytes32(0), _proof), 110 | "ERR_PROOF" 111 | ); 112 | } 113 | } 114 | 115 | return calcRootHash(_len, _len + 1, _leafHash, _proof); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /contracts/MerklizedERC20.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "hardhat/console.sol"; 5 | 6 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 7 | import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; 8 | import "@openzeppelin/contracts/utils/Context.sol"; 9 | 10 | import "./DynamicMerkleTree.sol"; 11 | 12 | contract MerklizedERC20 is Context, IERC20, IERC20Metadata { 13 | mapping(address => uint256) private _balances; 14 | 15 | mapping(address => uint256) private _indices1; 16 | 17 | uint256 private _totalSupply; 18 | 19 | string private _name; 20 | string private _symbol; 21 | 22 | uint256 public len; 23 | bytes32 public rootHash; 24 | 25 | struct TreeNode { 26 | address addr; 27 | uint256 balance; 28 | } 29 | 30 | /** 31 | * @dev Sets the values for {name} and {symbol}. 32 | * 33 | * The default value of {decimals} is 18. To select a different value for 34 | * {decimals} you should overload it. 35 | * 36 | * All two of these values are immutable: they can only be set once during 37 | * construction. 38 | */ 39 | constructor(string memory name_, string memory symbol_) { 40 | _name = name_; 41 | _symbol = symbol_; 42 | } 43 | 44 | /** 45 | * @dev Returns the name of the token. 46 | */ 47 | function name() public view virtual override returns (string memory) { 48 | return _name; 49 | } 50 | 51 | /** 52 | * @dev Returns the symbol of the token, usually a shorter version of the 53 | * name. 54 | */ 55 | function symbol() public view virtual override returns (string memory) { 56 | return _symbol; 57 | } 58 | 59 | /** 60 | * @dev Returns the number of decimals used to get its user representation. 61 | * For example, if `decimals` equals `2`, a balance of `505` tokens should 62 | * be displayed to a user as `5.05` (`505 / 10 ** 2`). 63 | * 64 | * Tokens usually opt for a value of 18, imitating the relationship between 65 | * Ether and Wei. This is the value {ERC20} uses, unless this function is 66 | * overridden; 67 | * 68 | * NOTE: This information is only used for _display_ purposes: it in 69 | * no way affects any of the arithmetic of the contract, including 70 | * {IERC20-balanceOf} and {IERC20-transfer}. 71 | */ 72 | function decimals() public view virtual override returns (uint8) { 73 | return 18; 74 | } 75 | 76 | /** 77 | * @dev See {IERC20-totalSupply}. 78 | */ 79 | function totalSupply() public view virtual override returns (uint256) { 80 | return _totalSupply; 81 | } 82 | 83 | /** 84 | * @dev See {IERC20-balanceOf}. 85 | */ 86 | function balanceOf(address account) 87 | public 88 | view 89 | virtual 90 | override 91 | returns (uint256) 92 | { 93 | return _balances[account]; 94 | } 95 | 96 | function transfer(address recipient, uint256 amount) 97 | public 98 | virtual 99 | override 100 | returns (bool) 101 | { 102 | return false; 103 | } 104 | 105 | function transferTo( 106 | address recipient, 107 | uint256 amount, 108 | bytes32[] memory proof0, 109 | bytes32[] memory proof1 110 | ) public returns (bool) { 111 | _transfer(msg.sender, recipient, amount, proof0, proof1); 112 | return true; 113 | } 114 | 115 | /** 116 | * @dev See {IERC20-allowance}. 117 | */ 118 | function allowance(address owner, address spender) 119 | public 120 | view 121 | virtual 122 | override 123 | returns (uint256) 124 | { 125 | return 0; 126 | } 127 | 128 | /** 129 | * @dev See {IERC20-approve}. 130 | * 131 | * Requirements: 132 | * 133 | * - `spender` cannot be the zero address. 134 | */ 135 | function approve(address spender, uint256 amount) 136 | public 137 | virtual 138 | override 139 | returns (bool) 140 | { 141 | return false; 142 | } 143 | 144 | /** 145 | * @dev See {IERC20-transferFrom}. 146 | * 147 | * Emits an {Approval} event indicating the updated allowance. This is not 148 | * required by the EIP. See the note at the beginning of {ERC20}. 149 | * 150 | * Requirements: 151 | * 152 | * - `sender` and `recipient` cannot be the zero address. 153 | * - `sender` must have a balance of at least `amount`. 154 | * - the caller must have allowance for ``sender``'s tokens of at least 155 | * `amount`. 156 | */ 157 | function transferFrom( 158 | address sender, 159 | address recipient, 160 | uint256 amount 161 | ) public virtual override returns (bool) { 162 | return false; 163 | } 164 | 165 | function _updateBalance( 166 | address addr, 167 | uint256 newAmount, 168 | bytes32[] memory proof 169 | ) private { 170 | uint256 _idx1 = _indices1[addr]; 171 | if (_idx1 == 0) { 172 | TreeNode memory node = TreeNode({addr: addr, balance: newAmount}); 173 | 174 | rootHash = DynamicMerkleTree.append( 175 | len, 176 | rootHash, 177 | keccak256(abi.encode(node)), 178 | proof 179 | ); 180 | len = len + 1; 181 | _indices1[addr] = len; 182 | _balances[addr] = newAmount; 183 | } else { 184 | TreeNode memory oldNode = TreeNode({ 185 | addr: addr, 186 | balance: _balances[addr] 187 | }); 188 | 189 | _balances[addr] = newAmount; 190 | 191 | TreeNode memory newNode = TreeNode({ 192 | addr: addr, 193 | balance: newAmount 194 | }); 195 | 196 | rootHash = DynamicMerkleTree.update( 197 | _idx1 - 1, 198 | len, 199 | rootHash, 200 | keccak256(abi.encode(oldNode)), 201 | keccak256(abi.encode(newNode)), 202 | proof 203 | ); 204 | } 205 | } 206 | 207 | /** 208 | * @dev Moves `amount` of tokens from `sender` to `recipient`. 209 | * 210 | * This internal function is equivalent to {transfer}, and can be used to 211 | * e.g. implement automatic token fees, slashing mechanisms, etc. 212 | * 213 | * Emits a {Transfer} event. 214 | * 215 | * Requirements: 216 | * 217 | * - `sender` cannot be the zero address. 218 | * - `recipient` cannot be the zero address. 219 | * - `sender` must have a balance of at least `amount`. 220 | */ 221 | function _transfer( 222 | address sender, 223 | address recipient, 224 | uint256 amount, 225 | bytes32[] memory proof0, 226 | bytes32[] memory proof1 227 | ) internal virtual { 228 | require(sender != address(0), "ERC20: transfer from the zero address"); 229 | require(recipient != address(0), "ERC20: transfer to the zero address"); 230 | 231 | _beforeTokenTransfer(sender, recipient, amount); 232 | 233 | uint256 senderBalance = _balances[sender]; 234 | require( 235 | senderBalance >= amount, 236 | "ERC20: transfer amount exceeds balance" 237 | ); 238 | unchecked { 239 | _updateBalance(sender, senderBalance - amount, proof0); 240 | } 241 | _updateBalance(recipient, _balances[recipient] + amount, proof1); 242 | 243 | emit Transfer(sender, recipient, amount); 244 | 245 | _afterTokenTransfer(sender, recipient, amount); 246 | } 247 | 248 | /** @dev Creates `amount` tokens and assigns them to `account`, increasing 249 | * the total supply. 250 | * 251 | * Emits a {Transfer} event with `from` set to the zero address. 252 | * 253 | * Requirements: 254 | * 255 | * - `account` cannot be the zero address. 256 | */ 257 | function _mint( 258 | address account, 259 | uint256 amount, 260 | bytes32[] memory proof 261 | ) internal virtual { 262 | require(account != address(0), "ERC20: mint to the zero address"); 263 | 264 | _beforeTokenTransfer(address(0), account, amount); 265 | 266 | _totalSupply += amount; 267 | _updateBalance(account, _balances[account] + amount, proof); 268 | emit Transfer(address(0), account, amount); 269 | 270 | _afterTokenTransfer(address(0), account, amount); 271 | } 272 | 273 | /** 274 | * @dev Destroys `amount` tokens from `account`, reducing the 275 | * total supply. 276 | * 277 | * Emits a {Transfer} event with `to` set to the zero address. 278 | * 279 | * Requirements: 280 | * 281 | * - `account` cannot be the zero address. 282 | * - `account` must have at least `amount` tokens. 283 | */ 284 | function _burn( 285 | address account, 286 | uint256 amount, 287 | bytes32[] memory proof 288 | ) internal virtual { 289 | require(account != address(0), "ERC20: burn from the zero address"); 290 | 291 | _beforeTokenTransfer(account, address(0), amount); 292 | 293 | uint256 accountBalance = _balances[account]; 294 | require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); 295 | unchecked { 296 | _updateBalance(account, _balances[account] - amount, proof); 297 | } 298 | _totalSupply -= amount; 299 | 300 | emit Transfer(account, address(0), amount); 301 | 302 | _afterTokenTransfer(account, address(0), amount); 303 | } 304 | 305 | /** 306 | * @dev Hook that is called before any transfer of tokens. This includes 307 | * minting and burning. 308 | * 309 | * Calling conditions: 310 | * 311 | * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens 312 | * will be transferred to `to`. 313 | * - when `from` is zero, `amount` tokens will be minted for `to`. 314 | * - when `to` is zero, `amount` of ``from``'s tokens will be burned. 315 | * - `from` and `to` are never both zero. 316 | * 317 | * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. 318 | */ 319 | function _beforeTokenTransfer( 320 | address from, 321 | address to, 322 | uint256 amount 323 | ) internal virtual {} 324 | 325 | /** 326 | * @dev Hook that is called after any transfer of tokens. This includes 327 | * minting and burning. 328 | * 329 | * Calling conditions: 330 | * 331 | * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens 332 | * has been transferred to `to`. 333 | * - when `from` is zero, `amount` tokens have been minted for `to`. 334 | * - when `to` is zero, `amount` of ``from``'s tokens have been burned. 335 | * - `from` and `to` are never both zero. 336 | * 337 | * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. 338 | */ 339 | function _afterTokenTransfer( 340 | address from, 341 | address to, 342 | uint256 amount 343 | ) internal virtual {} 344 | } 345 | -------------------------------------------------------------------------------- /contracts/MerklizedStaking.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "hardhat/console.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 7 | 8 | import "./DynamicMerkleTree.sol"; 9 | 10 | contract MerklizedStaking { 11 | using SafeERC20 for IERC20; 12 | 13 | struct TreeNode { 14 | address addr; 15 | uint256 balance; 16 | } 17 | 18 | mapping(address => uint256) private _indices1; // 1-based index 19 | 20 | mapping(address => uint256) private _balances; 21 | 22 | uint256 public len; 23 | 24 | IERC20 public token; 25 | 26 | bytes32 public rootHash; 27 | 28 | constructor(IERC20 _token) { 29 | token = _token; 30 | } 31 | 32 | function stake(uint256 _amount, bytes32[] memory _proof) public { 33 | token.safeTransferFrom(msg.sender, address(this), _amount); 34 | 35 | uint256 _idx1 = _indices1[msg.sender]; 36 | if (_idx1 == 0) { 37 | TreeNode memory node = TreeNode({ 38 | addr: msg.sender, 39 | balance: _amount 40 | }); 41 | 42 | rootHash = DynamicMerkleTree.append( 43 | len, 44 | rootHash, 45 | keccak256(abi.encode(node)), 46 | _proof 47 | ); 48 | len = len + 1; 49 | _indices1[msg.sender] = len; 50 | _balances[msg.sender] = _amount; 51 | } else { 52 | TreeNode memory oldNode = TreeNode({ 53 | addr: msg.sender, 54 | balance: _balances[msg.sender] 55 | }); 56 | 57 | _balances[msg.sender] += _amount; 58 | 59 | TreeNode memory newNode = TreeNode({ 60 | addr: msg.sender, 61 | balance: _balances[msg.sender] 62 | }); 63 | 64 | rootHash = DynamicMerkleTree.update( 65 | _idx1 - 1, 66 | len, 67 | rootHash, 68 | keccak256(abi.encode(oldNode)), 69 | keccak256(abi.encode(newNode)), 70 | _proof 71 | ); 72 | } 73 | } 74 | 75 | function unstake(bytes32[] memory _proof) public { 76 | uint256 _idx1 = _indices1[msg.sender]; 77 | 78 | if (_idx1 == 0) { 79 | return; // not exist 80 | } 81 | 82 | uint256 amount = _balances[msg.sender]; 83 | TreeNode memory oldNode = TreeNode({addr: msg.sender, balance: amount}); 84 | 85 | _balances[msg.sender] = 0; 86 | 87 | TreeNode memory newNode = TreeNode({addr: msg.sender, balance: 0}); 88 | 89 | rootHash = DynamicMerkleTree.update( 90 | _idx1 - 1, 91 | len, 92 | rootHash, 93 | keccak256(abi.encode(oldNode)), 94 | keccak256(abi.encode(newNode)), 95 | _proof 96 | ); 97 | 98 | token.safeTransfer(msg.sender, amount); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /contracts/TestERC20.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "hardhat/console.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | 7 | contract TestERC20 is ERC20 { 8 | constructor() ERC20("Test", "TEST") {} 9 | 10 | function mint(address addr, uint256 amount) public { 11 | _mint(addr, amount); 12 | } 13 | } 14 | 15 | contract TestERC20WithName is ERC20 { 16 | constructor(string memory name) ERC20(name, name) {} 17 | 18 | function mint(address addr, uint256 amount) public { 19 | _mint(addr, amount); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /contracts/TestMerklizedERC20.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "hardhat/console.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | 7 | import "./MerklizedERC20.sol"; 8 | 9 | contract TestMerklizedERC20 is MerklizedERC20 { 10 | constructor() MerklizedERC20("Test", "TEST") {} 11 | 12 | function mint( 13 | address addr, 14 | uint256 amount, 15 | bytes32[] memory proof 16 | ) public { 17 | _mint(addr, amount, proof); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contracts/app/Bridge.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 6 | 7 | import "../DynamicMerkleTree.sol"; 8 | 9 | library BridgeLib { 10 | struct TransferData { 11 | address tokenAddress; 12 | address destination; 13 | uint256 amount; 14 | uint256 startTime; 15 | uint256 feeRampup; 16 | } 17 | 18 | struct TransferInitiated { 19 | TransferData data; 20 | address self; 21 | } 22 | } 23 | 24 | contract BridgeSource { 25 | using SafeERC20 for IERC20; 26 | 27 | uint256 public nextTransferId; 28 | 29 | bytes32 public stateRoot; 30 | 31 | uint256 public constant CONTRACT_FEE_BASIS_POINTS = 5; 32 | 33 | constructor() {} 34 | 35 | function withdraw( 36 | address sourceTokenAddress, 37 | BridgeLib.TransferData memory transferData, 38 | bytes32[] memory proof 39 | ) public { 40 | // safemath not needed for solidity 8 41 | uint256 amountPlusFee = (transferData.amount * 42 | (10000 + CONTRACT_FEE_BASIS_POINTS)) / 10000; 43 | IERC20(sourceTokenAddress).safeTransferFrom( 44 | msg.sender, 45 | address(this), 46 | amountPlusFee 47 | ); 48 | 49 | BridgeLib.TransferInitiated memory transferInitiated = BridgeLib 50 | .TransferInitiated({data: transferData, self: address(this)}); 51 | 52 | stateRoot = DynamicMerkleTree.append( 53 | nextTransferId, 54 | stateRoot, 55 | keccak256(abi.encode(transferInitiated)), 56 | proof 57 | ); 58 | nextTransferId += 1; 59 | 60 | // TOOD: send the root to message box? 61 | // TODO: send the fee to bounty pool 62 | } 63 | } 64 | 65 | contract BridgeDestination { 66 | using SafeERC20 for IERC20; 67 | 68 | struct TransferKey { 69 | BridgeLib.TransferData transferData; 70 | uint256 transferId; 71 | } 72 | 73 | mapping(bytes32 => address) public ownerMap; 74 | 75 | mapping(bytes32 => bool) public validatedStateRoots; 76 | 77 | constructor() {} 78 | 79 | function changeOwner(TransferKey memory tkey, address newOwner) public { 80 | bytes32 key = keccak256(abi.encode(tkey)); 81 | require(ownerMap[key] == address(0x0), "owned"); 82 | require( 83 | msg.sender == tkey.transferData.destination, 84 | "not from destination" 85 | ); 86 | ownerMap[key] = newOwner; 87 | } 88 | 89 | function transferOwnership(TransferKey memory tkey, address newOwner) 90 | public 91 | { 92 | bytes32 key = keccak256(abi.encode(tkey)); 93 | 94 | require(ownerMap[key] == msg.sender, "not from owner"); 95 | ownerMap[key] = newOwner; 96 | } 97 | 98 | function getLPFee(BridgeLib.TransferData memory transferData) 99 | public 100 | view 101 | returns (uint256) 102 | { 103 | // TODO: 104 | return 0; 105 | } 106 | 107 | function buy(TransferKey memory tkey) public { 108 | uint256 amount = tkey.transferData.amount - getLPFee(tkey.transferData); 109 | IERC20(tkey.transferData.tokenAddress).safeTransferFrom( 110 | msg.sender, 111 | tkey.transferData.destination, 112 | amount 113 | ); 114 | 115 | bytes32 key = keccak256(abi.encode(tkey)); 116 | require(ownerMap[key] == address(0), "already bought or withdrawn"); 117 | ownerMap[key] = msg.sender; 118 | } 119 | 120 | function verifyStateRoot(bytes32 stateRoot, bytes32[] memory stateRootProof) 121 | internal 122 | pure 123 | virtual 124 | { 125 | assert(false); 126 | } 127 | 128 | function withdraw( 129 | TransferKey memory tkey, 130 | bytes32[] memory stateRootProof, 131 | bytes32 stateRoot, 132 | address sourceContract, 133 | uint256 transferLen, 134 | bytes32[] memory recordProof 135 | ) public { 136 | if (!validatedStateRoots[stateRoot]) { 137 | verifyStateRoot(stateRoot, stateRootProof); 138 | // TODO: prove stateRoot is in stateRootProof 139 | validatedStateRoots[stateRoot] = true; 140 | } 141 | 142 | BridgeLib.TransferInitiated memory transferInitiated = BridgeLib 143 | .TransferInitiated({data: tkey.transferData, self: sourceContract}); 144 | 145 | DynamicMerkleTree.verify( 146 | tkey.transferId, 147 | transferLen, 148 | stateRoot, 149 | keccak256(abi.encode(transferInitiated)), 150 | recordProof 151 | ); 152 | 153 | bytes32 key = keccak256(abi.encode(tkey)); 154 | require(ownerMap[key] != address(2**160 - 1), "already withdrawn"); 155 | if (ownerMap[key] == address(0)) { 156 | IERC20(tkey.transferData.tokenAddress).safeTransfer( 157 | tkey.transferData.destination, 158 | tkey.transferData.amount 159 | ); 160 | } else { 161 | IERC20(tkey.transferData.tokenAddress).safeTransfer( 162 | ownerMap[key], 163 | tkey.transferData.amount 164 | ); 165 | } 166 | 167 | ownerMap[key] = address(2**160 - 1); // -1, not used any more 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /contracts/app/TestBridge.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "./Bridge.sol"; 5 | 6 | contract TestBridgeDestination is BridgeDestination { 7 | function verifyStateRoot(bytes32 stateRoot, bytes32[] memory stateRootProof) 8 | internal 9 | pure 10 | override 11 | { 12 | // pass 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contracts/app/l2_bridge/L2BridgeDestination.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 6 | 7 | import "../../DynamicMerkleTree.sol"; 8 | 9 | import "./L2BridgeLib.sol"; 10 | 11 | contract L2BridgeDestination { 12 | using SafeERC20 for IERC20; 13 | 14 | bytes32 public receiptRoot; 15 | uint256 public nextReceiptId; 16 | mapping(bytes32 => bool) public transferBought; // storage root should be enough, but we use dynamic Merkle tree to simplify 17 | mapping(uint256 => bytes32) public boughtList; // not necessarily on-chain, but can simplify off-chain proof generation 18 | 19 | constructor() {} 20 | 21 | function getLPFee(L2BridgeLib.TransferData memory transferData) 22 | public 23 | view 24 | returns (uint256) 25 | { 26 | uint256 currentTime = block.timestamp; 27 | if (currentTime < transferData.startTime) { 28 | return 0; 29 | } else if ( 30 | currentTime >= transferData.startTime + transferData.feeRampup 31 | ) { 32 | return transferData.fee; 33 | } else { 34 | return 35 | (transferData.fee * (currentTime - transferData.startTime)) / 36 | transferData.feeRampup; 37 | } 38 | } 39 | 40 | /* 41 | * buy the transfer token at source by exchange the corresponding destination token. 42 | */ 43 | function buy( 44 | L2BridgeLib.TransferData memory transferData, 45 | bytes32[] memory appendProof 46 | ) public { 47 | bytes32 key = keccak256(abi.encode(transferData)); 48 | require(!transferBought[key], "already bought"); 49 | transferBought[key] = true; 50 | 51 | uint256 amount = transferData.amount - getLPFee(transferData); 52 | IERC20(transferData.dstTokenAddress).safeTransferFrom( 53 | msg.sender, 54 | transferData.destination, 55 | amount 56 | ); 57 | 58 | // construct receipt and append it to Merkle tree 59 | L2BridgeLib.TransferReceipt memory receipt = L2BridgeLib 60 | .TransferReceipt({transferDataHash: key, lp: msg.sender}); 61 | 62 | bytes32 receiptHash = keccak256(abi.encode(receipt)); 63 | boughtList[nextReceiptId] = receiptHash; 64 | receiptRoot = DynamicMerkleTree.append( 65 | nextReceiptId, 66 | receiptRoot, 67 | receiptHash, 68 | appendProof 69 | ); 70 | nextReceiptId += 1; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /contracts/app/l2_bridge/L2BridgeLib.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | library L2BridgeLib { 5 | struct TransferData { 6 | address srcTokenAddress; 7 | address dstTokenAddress; 8 | address destination; 9 | uint256 amount; 10 | uint256 fee; 11 | uint256 startTime; 12 | uint256 feeRampup; 13 | uint256 expiration; 14 | } 15 | 16 | struct TransferReceipt { 17 | bytes32 transferDataHash; 18 | address lp; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/app/l2_bridge/L2BridgeSource.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 6 | 7 | import "../../DynamicMerkleTree.sol"; 8 | 9 | import "./L2BridgeLib.sol"; 10 | 11 | contract L2BridgeSource { 12 | using SafeERC20 for IERC20; 13 | 14 | uint256 public constant XFER_NEW = 0; 15 | uint256 public constant XFER_PENDING = 1; 16 | uint256 public constant XFER_EXPIRED = 2; 17 | uint256 public constant XFER_DONE = 3; 18 | 19 | mapping(bytes32 => uint256) public transferStatus; 20 | 21 | bytes32 public receiptRoot; 22 | 23 | constructor() {} 24 | 25 | /* 26 | * deposit the user's fund and request to exchange token at destination. 27 | */ 28 | function deposit(L2BridgeLib.TransferData memory transferData) public { 29 | bytes32 key = keccak256(abi.encode(transferData)); 30 | require(transferStatus[key] == XFER_NEW, "not new"); 31 | 32 | IERC20(transferData.srcTokenAddress).safeTransferFrom( 33 | msg.sender, 34 | address(this), 35 | transferData.amount 36 | ); 37 | 38 | transferStatus[key] = XFER_PENDING; 39 | } 40 | 41 | /* 42 | * refund the user's fund after expiration (no LP exchanges the token at dest.). 43 | */ 44 | function refund(L2BridgeLib.TransferData memory transferData) public { 45 | bytes32 key = keccak256(abi.encode(transferData)); 46 | require(transferStatus[key] == XFER_PENDING, "not pending"); 47 | require(transferData.expiration < block.timestamp, "not expire"); 48 | 49 | IERC20(transferData.srcTokenAddress).safeTransfer( 50 | transferData.destination, 51 | transferData.amount 52 | ); 53 | 54 | transferStatus[key] = XFER_EXPIRED; 55 | } 56 | 57 | /* 58 | * withdraw the user's fund by LP after providing the liquidity at destination with 59 | * confirmed receipt root. 60 | */ 61 | function withdraw( 62 | L2BridgeLib.TransferData memory transferData, 63 | uint256 receiptIdx, 64 | uint256 receiptLen, 65 | bytes32[] memory receiptProof 66 | ) public { 67 | bytes32 key = keccak256(abi.encode(transferData)); 68 | require(transferStatus[key] == XFER_PENDING, "not pending"); 69 | require(receiptIdx < receiptLen, "invalid idx"); 70 | 71 | // construct receipt and verify whether it is completed. 72 | L2BridgeLib.TransferReceipt memory receipt = L2BridgeLib 73 | .TransferReceipt({transferDataHash: key, lp: msg.sender}); 74 | 75 | require( 76 | DynamicMerkleTree.verify( 77 | receiptIdx, 78 | receiptLen, 79 | receiptRoot, 80 | keccak256(abi.encode(receipt)), 81 | receiptProof 82 | ), 83 | "fail to prove" 84 | ); 85 | 86 | IERC20(transferData.srcTokenAddress).safeTransfer( 87 | msg.sender, 88 | transferData.amount 89 | ); 90 | 91 | transferStatus[key] = XFER_DONE; 92 | } 93 | 94 | /// @notice should be overrided by specific L2 implementations. 95 | function updateReceiptRoot(bytes32 newRoot) public virtual { 96 | receiptRoot = newRoot; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /contracts/app/l2_bridge/TestL2BridgeSource.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "./L2BridgeSource.sol"; 5 | 6 | contract TestL2BridgeSource is L2BridgeSource { 7 | function getReceiptHash(L2BridgeLib.TransferData memory transferData) 8 | public 9 | pure 10 | returns (bytes32) 11 | { 12 | return keccak256(abi.encode(transferData)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contracts/app/l2_bridge/arbitrum/ArbitrumBridgeDestination.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "arb-shared-dependencies/contracts/ArbSys.sol"; 5 | import "arb-shared-dependencies/contracts/AddressAliasHelper.sol"; 6 | 7 | import "../L2BridgeDestination.sol"; 8 | import "../L2BridgeSource.sol"; 9 | 10 | contract ArbitrumBridgeDestination is L2BridgeDestination { 11 | ArbSys constant arbsys = ArbSys(address(100)); 12 | address public l1Target; 13 | 14 | event L2ToL1TxCreated(uint256 indexed withdrawalId); 15 | 16 | constructor(address _l1Target) { 17 | l1Target = _l1Target; 18 | } 19 | 20 | /// @notice only owner can call 21 | function updateL1Target(address _l1Target) public { 22 | l1Target = _l1Target; 23 | } 24 | 25 | function updateReceiptRootToL1() public returns (uint256) { 26 | bytes memory data = abi.encodeWithSelector( 27 | L2BridgeSource.updateReceiptRoot.selector, 28 | receiptRoot 29 | ); 30 | 31 | uint256 withdrawalId = arbsys.sendTxToL1(l1Target, data); 32 | 33 | emit L2ToL1TxCreated(withdrawalId); 34 | return withdrawalId; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /contracts/app/l2_bridge/arbitrum/ArbitrumBridgeSource.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "arb-shared-dependencies/contracts/ArbSys.sol"; 5 | import "arb-shared-dependencies/contracts/AddressAliasHelper.sol"; 6 | 7 | import "../L2BridgeSource.sol"; 8 | 9 | contract ArbitrumBridgeSource is L2BridgeSource { 10 | ArbSys constant arbsys = ArbSys(address(100)); 11 | address public l1Target; 12 | 13 | event L2ToL1TxCreated(uint256 indexed withdrawalId); 14 | 15 | constructor(address _l1Target) { 16 | l1Target = _l1Target; 17 | } 18 | 19 | /// @notice only owner can call 20 | function updateL1Target(address _l1Target) public { 21 | l1Target = _l1Target; 22 | } 23 | 24 | /// @notice only l1Target can update 25 | function updateReceiptRoot(bytes32 newRoot) public override { 26 | // To check that message came from L1, we check that the sender is the L1 contract's L2 alias. 27 | require( 28 | msg.sender == AddressAliasHelper.applyL1ToL2Alias(l1Target), 29 | "only updateable by L1" 30 | ); 31 | L2BridgeSource.updateReceiptRoot(newRoot); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/app/l2_bridge/arbitrum/ArbitrumL1Bridge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "arb-shared-dependencies/contracts/Inbox.sol"; 5 | import "arb-shared-dependencies/contracts/Outbox.sol"; 6 | 7 | import "../L2BridgeSource.sol"; 8 | 9 | contract ArbitrumL1Bridge { 10 | address public l2Source; 11 | address public l2Target; 12 | IInbox public inbox; 13 | bytes32 public receiptRoot; 14 | 15 | event RetryableTicketCreated(uint256 indexed ticketId); 16 | 17 | constructor( 18 | address _l2Source, 19 | address _l2Target, 20 | address _inbox 21 | ) { 22 | l2Source = _l2Source; 23 | l2Target = _l2Target; 24 | inbox = IInbox(_inbox); 25 | } 26 | 27 | /// @notice only owner can call 28 | function updateL2Target(address _l2Target) public { 29 | l2Target = _l2Target; 30 | } 31 | 32 | /// @notice only owner can call 33 | function updateL2Source(address _l2Source) public { 34 | l2Source = _l2Source; 35 | } 36 | 37 | /// @notice test only. 38 | function setReceiptRootHashInL2Test( 39 | bytes32 newReceiptRoot, 40 | uint256 maxSubmissionCost, 41 | uint256 maxGas, 42 | uint256 gasPriceBid 43 | ) public payable returns (uint256) { 44 | bytes memory data = abi.encodeWithSelector( 45 | L2BridgeSource.updateReceiptRoot.selector, 46 | newReceiptRoot 47 | ); 48 | 49 | uint256 ticketID = inbox.createRetryableTicket{value: msg.value}( 50 | l2Target, 51 | 0, 52 | maxSubmissionCost, 53 | msg.sender, 54 | msg.sender, 55 | maxGas, 56 | gasPriceBid, 57 | data 58 | ); 59 | 60 | emit RetryableTicketCreated(ticketID); 61 | return ticketID; 62 | } 63 | 64 | function setReceiptRootHashInL2( 65 | uint256 maxSubmissionCost, 66 | uint256 maxGas, 67 | uint256 gasPriceBid 68 | ) public payable returns (uint256) { 69 | bytes memory data = abi.encodeWithSelector( 70 | L2BridgeSource.updateReceiptRoot.selector, 71 | receiptRoot 72 | ); 73 | 74 | uint256 ticketID = inbox.createRetryableTicket{value: msg.value}( 75 | l2Target, 76 | 0, 77 | maxSubmissionCost, 78 | msg.sender, 79 | msg.sender, 80 | maxGas, 81 | gasPriceBid, 82 | data 83 | ); 84 | 85 | emit RetryableTicketCreated(ticketID); 86 | return ticketID; 87 | } 88 | 89 | /// @notice only l2Target can update 90 | function updateReceiptRoot(bytes32 newRoot) public { 91 | IOutbox outbox = IOutbox(inbox.bridge().activeOutbox()); 92 | address l2Sender = outbox.l2ToL1Sender(); 93 | require( 94 | l2Sender == l2Source, 95 | "receipt root only updateable by source L2" 96 | ); 97 | 98 | receiptRoot = newRoot; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /contracts/app/l2_bridge/optimism/OptimismBridgeDestination.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "../L2BridgeDestination.sol"; 5 | import "../L2BridgeSource.sol"; 6 | 7 | import "./iAbs_BaseCrossDomainMessenger.sol"; 8 | 9 | contract OptimismBridgeDestination is L2BridgeDestination { 10 | address public l1Target; 11 | iAbs_BaseCrossDomainMessenger public messenger = 12 | iAbs_BaseCrossDomainMessenger( 13 | 0x4200000000000000000000000000000000000007 14 | ); 15 | 16 | event L2ToL1TxCreated(bytes32 root); 17 | 18 | constructor(address _l1Target) { 19 | l1Target = _l1Target; 20 | } 21 | 22 | /// @notice only owner can call 23 | function updateL1Target(address _l1Target) public { 24 | l1Target = _l1Target; 25 | } 26 | 27 | /// @notice only owner can call 28 | function updateMessenger(address _messenger) public { 29 | messenger = iAbs_BaseCrossDomainMessenger(_messenger); 30 | } 31 | 32 | function updateReceiptRootToL1(uint32 maxGas) public { 33 | bytes memory data = abi.encodeWithSelector( 34 | L2BridgeSource.updateReceiptRoot.selector, 35 | receiptRoot 36 | ); 37 | 38 | messenger.sendMessage(l1Target, data, maxGas); 39 | 40 | emit L2ToL1TxCreated(receiptRoot); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /contracts/app/l2_bridge/optimism/OptimismBridgeSource.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "../L2BridgeSource.sol"; 5 | 6 | import "./iAbs_BaseCrossDomainMessenger.sol"; 7 | 8 | contract OptimismBridgeSource is L2BridgeSource { 9 | address public l1Target; 10 | iAbs_BaseCrossDomainMessenger public messenger = 11 | iAbs_BaseCrossDomainMessenger( 12 | 0x4200000000000000000000000000000000000007 13 | ); 14 | 15 | event L2ToL1TxCreated(uint256 indexed withdrawalId); 16 | 17 | constructor(address _l1Target) { 18 | l1Target = _l1Target; 19 | } 20 | 21 | /// @notice only owner can call 22 | function updateL1Target(address _l1Target) public { 23 | l1Target = _l1Target; 24 | } 25 | 26 | /// @notice only owner can call 27 | function updateMessenger(address _messenger) public { 28 | messenger = iAbs_BaseCrossDomainMessenger(_messenger); 29 | } 30 | 31 | /// @notice only l1Target can update 32 | function updateReceiptRoot(bytes32 newRoot) public override { 33 | // To check that message came from L1, we check that the sender is the L1 contract's L2 alias. 34 | require(msg.sender == address(messenger), "only message can call"); 35 | require( 36 | messenger.xDomainMessageSender() == l1Target, 37 | "only updateable by L1" 38 | ); 39 | L2BridgeSource.updateReceiptRoot(newRoot); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /contracts/app/l2_bridge/optimism/OptimismL1Bridge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "../L2BridgeSource.sol"; 5 | 6 | import "./iAbs_BaseCrossDomainMessenger.sol"; 7 | 8 | contract OptimismL1Bridge { 9 | address public l2Source; 10 | address public l2Target; 11 | iAbs_BaseCrossDomainMessenger public messenger; 12 | bytes32 public receiptRoot; 13 | 14 | event MessageSent(address indexed target, bytes32 root, uint256 maxGas); 15 | 16 | constructor( 17 | address _l2Source, 18 | address _l2Target, 19 | address _messenger 20 | ) { 21 | l2Source = _l2Source; 22 | l2Target = _l2Target; 23 | messenger = iAbs_BaseCrossDomainMessenger(_messenger); 24 | } 25 | 26 | /// @notice only owner can call 27 | function updateL2Target(address _l2Target) public { 28 | l2Target = _l2Target; 29 | } 30 | 31 | /// @notice only owner can call 32 | function updateL2Source(address _l2Source) public { 33 | l2Source = _l2Source; 34 | } 35 | 36 | /// @notice test only. 37 | function setReceiptRootHashInL2Test(bytes32 newReceiptRoot, uint32 maxGas) 38 | public 39 | payable 40 | { 41 | bytes memory data = abi.encodeWithSelector( 42 | L2BridgeSource.updateReceiptRoot.selector, 43 | newReceiptRoot 44 | ); 45 | 46 | messenger.sendMessage(l2Target, data, maxGas); 47 | 48 | emit MessageSent(l2Target, newReceiptRoot, maxGas); 49 | } 50 | 51 | function setReceiptRootHashInL2(uint32 maxGas) public payable { 52 | bytes memory data = abi.encodeWithSelector( 53 | L2BridgeSource.updateReceiptRoot.selector, 54 | receiptRoot 55 | ); 56 | 57 | messenger.sendMessage(l2Target, data, maxGas); 58 | 59 | emit MessageSent(l2Target, receiptRoot, maxGas); 60 | } 61 | 62 | /// @notice only l2Target can update 63 | function updateReceiptRoot(bytes32 newRoot) public { 64 | iAbs_BaseCrossDomainMessenger messenger = iAbs_BaseCrossDomainMessenger( 65 | msg.sender 66 | ); 67 | address l2Sender = messenger.xDomainMessageSender(); 68 | require( 69 | l2Sender == l2Source, 70 | "receipt root only updateable by source L2" 71 | ); 72 | 73 | receiptRoot = newRoot; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /contracts/app/l2_bridge/optimism/iAbs_BaseCrossDomainMessenger.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >0.5.0 <=0.8.0; 3 | pragma experimental ABIEncoderV2; 4 | 5 | /** 6 | * @title iAbs_BaseCrossDomainMessenger 7 | */ 8 | interface iAbs_BaseCrossDomainMessenger { 9 | /********** 10 | * Events * 11 | **********/ 12 | 13 | event SentMessage(bytes message); 14 | event RelayedMessage(bytes32 msgHash); 15 | event FailedRelayedMessage(bytes32 msgHash); 16 | 17 | /************* 18 | * Variables * 19 | *************/ 20 | 21 | function xDomainMessageSender() external view returns (address); 22 | 23 | /******************** 24 | * Public Functions * 25 | ********************/ 26 | 27 | /** 28 | * Sends a cross domain message to the target messenger. 29 | * @param _target Target contract address. 30 | * @param _message Message to send to the target. 31 | * @param _gasLimit Gas limit for the provided message. 32 | */ 33 | function sendMessage( 34 | address _target, 35 | bytes calldata _message, 36 | uint32 _gasLimit 37 | ) external; 38 | } 39 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | 3 | require("@nomiclabs/hardhat-etherscan"); 4 | require("@nomiclabs/hardhat-waffle"); 5 | require("@nomiclabs/hardhat-web3"); 6 | require("hardhat-gas-reporter"); 7 | require("solidity-coverage"); 8 | 9 | // This is a sample Hardhat task. To learn how to create your own go to 10 | // https://hardhat.org/guides/create-task.html 11 | task("accounts", "Prints the list of accounts", async (taskArgs, hre) => { 12 | const accounts = await hre.ethers.getSigners(); 13 | 14 | for (const account of accounts) { 15 | console.log(account.address); 16 | } 17 | }); 18 | 19 | // You need to export an object to set up your config 20 | // Go to https://hardhat.org/config/ to learn more 21 | 22 | /** 23 | * @type import('hardhat/config').HardhatUserConfig 24 | */ 25 | module.exports = { 26 | solidity: "0.8.0", 27 | networks: { 28 | hardhat: { 29 | initialBaseFeePerGas: 0, // workaround from https://github.com/sc-forks/solidity-coverage/issues/652#issuecomment-896330136 . Remove when that issue is closed. 30 | }, 31 | rinkeby: { 32 | url: process.env.RINKEBY_URL || "", 33 | accounts: 34 | process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [], 35 | }, 36 | kovan: { 37 | url: process.env.KOVAN_URL || "", 38 | accounts: 39 | process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [], 40 | }, 41 | arbitrum: { 42 | url: process.env.ARBITRUM_URL || "", 43 | accounts: 44 | process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [], 45 | }, 46 | ropsten: { 47 | url: process.env.ROPSTEN_URL || "", 48 | accounts: 49 | process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [], 50 | }, 51 | optimism: { 52 | url: process.env.OPTIMISM_URL || "", 53 | accounts: 54 | process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [], 55 | }, 56 | }, 57 | gasReporter: { 58 | enabled: process.env.REPORT_GAS !== undefined, 59 | currency: "USD", 60 | }, 61 | etherscan: { 62 | apiKey: process.env.ETHERSCAN_API_KEY, 63 | }, 64 | mocha: { 65 | grep: process.env.MOCHA_GREP || "", 66 | }, 67 | }; 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DynamicMerkleTree", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "devDependencies": { 7 | "@nomiclabs/hardhat-ethers": "^2.0.2", 8 | "@nomiclabs/hardhat-etherscan": "^2.1.8", 9 | "@nomiclabs/hardhat-waffle": "^2.0.1", 10 | "@nomiclabs/hardhat-web3": "^2.0.0", 11 | "@openzeppelin/contracts": "4.3.2", 12 | "arb-ts": "^1.0.2", 13 | "chai": "^4.3.4", 14 | "dotenv": "^10.0.0", 15 | "eslint-config-prettier": "^8.3.0", 16 | "ethereum-waffle": "^3.4.0", 17 | "ethereumjs-util": "^7.1.3", 18 | "ethers": "^5.4.7", 19 | "hardhat": "^2.6.4", 20 | "hardhat-gas-reporter": "^1.0.4", 21 | "prettier": "^2.4.1", 22 | "prettier-check": "^2.0.0", 23 | "prettier-plugin-solidity": "^1.0.0-beta.18", 24 | "solhint-plugin-prettier": "^0.0.5", 25 | "solidity-coverage": "^0.7.17", 26 | "solidity-rlp": "^2.0.5" 27 | }, 28 | "scripts": { 29 | "compile": "hardhat compile", 30 | "test": "hardhat test", 31 | "arb_bridge_deploy": "hardhat run scripts/arb_bridge_deploy.js", 32 | "opt_bridge_deploy": "hardhat run scripts/opt_bridge_deploy.js", 33 | "arb_bridge_exec": "hardhat run scripts/arb_bridge_exec.js", 34 | "prettier:check": "prettier-check contracts/**/*.sol", 35 | "prettier:fix": "prettier --write contracts/**/*.sol test/**/*.js scripts/**/*.js" 36 | }, 37 | "workspaces": { 38 | "packages": [ 39 | "packages/arb-shared-dependencies" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/arb-shared-dependencies/contracts/AddressAliasHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | /* 4 | * Copyright 2019-2021, Offchain Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | pragma solidity >=0.7.0; 20 | 21 | library AddressAliasHelper { 22 | uint160 constant offset = uint160(0x1111000000000000000000000000000000001111); 23 | 24 | /// @notice Utility function that converts the address in the L1 that submitted a tx to 25 | /// the inbox to the msg.sender viewed in the L2 26 | /// @param l1Address the address in the L1 that triggered the tx to L2 27 | /// @return l2Address L2 address as viewed in msg.sender 28 | function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) { 29 | l2Address = address(uint160(l1Address) + offset); 30 | } 31 | 32 | /// @notice Utility function that converts the msg.sender viewed in the L2 to the 33 | /// address in the L1 that submitted a tx to the inbox 34 | /// @param l2Address L2 address as viewed in msg.sender 35 | /// @return l1Address the address in the L1 that triggered the tx to L2 36 | function undoL1ToL2Alias(address l2Address) internal pure returns (address l1Address) { 37 | l1Address = address(uint160(l2Address) - offset); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/arb-shared-dependencies/contracts/ArbAddressTable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.7.0; 2 | 3 | /** @title Precompiled contract that exists in every Arbitrum chain at 0x0000000000000000000000000000000000000066. 4 | * Allows registering / retrieving addresses at uint indices, saving calldata. 5 | */ 6 | interface ArbAddressTable { 7 | /** 8 | * @notice Register an address in the address table 9 | * @param addr address to register 10 | * @return index of the address (existing index, or newly created index if not already registered) 11 | */ 12 | function register(address addr) external returns(uint); 13 | 14 | /** 15 | * @param addr address to lookup 16 | * @return index of an address in the address table (revert if address isn't in the table) 17 | */ 18 | function lookup(address addr) external view returns(uint); 19 | 20 | /** 21 | * @notice Check whether an address exists in the address table 22 | * @param addr address to check for presence in table 23 | * @return true if address is in table 24 | */ 25 | function addressExists(address addr) external view returns(bool); 26 | 27 | /** 28 | * @return size of address table (= first unused index) 29 | */ 30 | function size() external view returns(uint); 31 | 32 | /** 33 | * @param index index to lookup address 34 | * @return address at a given index in address table (revert if index is beyond end of table) 35 | */ 36 | function lookupIndex(uint index) external view returns(address); 37 | 38 | /** 39 | * @notice read a compressed address from a bytes buffer 40 | * @param buf bytes buffer containing an address 41 | * @param offset offset of target address 42 | * @return resulting address and updated offset into the buffer (revert if buffer is too short) 43 | */ 44 | function decompress(bytes calldata buf, uint offset) external pure returns(address, uint); 45 | 46 | /** 47 | * @notice compress an address and return the result 48 | * @param addr address to comppress 49 | * @return compressed address bytes 50 | */ 51 | function compress(address addr) external returns(bytes memory); 52 | } 53 | 54 | -------------------------------------------------------------------------------- /packages/arb-shared-dependencies/contracts/ArbSys.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.7.0; 2 | 3 | /** 4 | * @title Precompiled contract that exists in every Arbitrum chain at address(100), 0x0000000000000000000000000000000000000064. Exposes a variety of system-level functionality. 5 | */ 6 | interface ArbSys { 7 | /** 8 | * @notice Get internal version number identifying an ArbOS build 9 | * @return version number as int 10 | */ 11 | function arbOSVersion() external pure returns (uint); 12 | 13 | /** 14 | * @notice Get Arbitrum block number (distinct from L1 block number; Arbitrum genesis block has block number 0) 15 | * @return block number as int 16 | */ 17 | function arbBlockNumber() external view returns (uint); 18 | 19 | /** 20 | * @notice Send given amount of Eth to dest from sender. 21 | * This is a convenience function, which is equivalent to calling sendTxToL1 with empty calldataForL1. 22 | * @param destination recipient address on L1 23 | * @return unique identifier for this L2-to-L1 transaction. 24 | */ 25 | function withdrawEth(address destination) external payable returns(uint); 26 | 27 | /** 28 | * @notice Send a transaction to L1 29 | * @param destination recipient address on L1 30 | * @param calldataForL1 (optional) calldata for L1 contract call 31 | * @return a unique identifier for this L2-to-L1 transaction. 32 | */ 33 | function sendTxToL1(address destination, bytes calldata calldataForL1) external payable returns(uint); 34 | 35 | 36 | /** 37 | * @notice get the number of transactions issued by the given external account or the account sequence number of the given contract 38 | * @param account target account 39 | * @return the number of transactions issued by the given external account or the account sequence number of the given contract 40 | */ 41 | function getTransactionCount(address account) external view returns(uint256); 42 | 43 | /** 44 | * @notice get the value of target L2 storage slot 45 | * This function is only callable from address 0 to prevent contracts from being able to call it 46 | * @param account target account 47 | * @param index target index of storage slot 48 | * @return stotage value for the given account at the given index 49 | */ 50 | function getStorageAt(address account, uint256 index) external view returns (uint256); 51 | 52 | /** 53 | * @notice check if current call is coming from l1 54 | * @return true if the caller of this was called directly from L1 55 | */ 56 | function isTopLevelCall() external view returns (bool); 57 | 58 | event EthWithdrawal(address indexed destAddr, uint amount); 59 | 60 | event L2ToL1Transaction(address caller, address indexed destination, uint indexed uniqueId, 61 | uint indexed batchNumber, uint indexInBatch, 62 | uint arbBlockNum, uint ethBlockNum, uint timestamp, 63 | uint callvalue, bytes data); 64 | } -------------------------------------------------------------------------------- /packages/arb-shared-dependencies/contracts/Bridge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | /* 4 | * Copyright 2021, Offchain Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | pragma solidity >=0.6.11; 20 | 21 | interface IBridge { 22 | event MessageDelivered( 23 | uint256 indexed messageIndex, 24 | bytes32 indexed beforeInboxAcc, 25 | address inbox, 26 | uint8 kind, 27 | address sender, 28 | bytes32 messageDataHash 29 | ); 30 | 31 | event BridgeCallTriggered( 32 | address indexed outbox, 33 | address indexed destAddr, 34 | uint256 amount, 35 | bytes data 36 | ); 37 | 38 | event InboxToggle(address indexed inbox, bool enabled); 39 | 40 | event OutboxToggle(address indexed outbox, bool enabled); 41 | 42 | function deliverMessageToInbox( 43 | uint8 kind, 44 | address sender, 45 | bytes32 messageDataHash 46 | ) external payable returns (uint256); 47 | 48 | function executeCall( 49 | address destAddr, 50 | uint256 amount, 51 | bytes calldata data 52 | ) external returns (bool success, bytes memory returnData); 53 | 54 | // These are only callable by the admin 55 | function setInbox(address inbox, bool enabled) external; 56 | 57 | function setOutbox(address inbox, bool enabled) external; 58 | 59 | // View functions 60 | 61 | function activeOutbox() external view returns (address); 62 | 63 | function allowedInboxes(address inbox) external view returns (bool); 64 | 65 | function allowedOutboxes(address outbox) external view returns (bool); 66 | 67 | function inboxAccs(uint256 index) external view returns (bytes32); 68 | 69 | function messageCount() external view returns (uint256); 70 | } -------------------------------------------------------------------------------- /packages/arb-shared-dependencies/contracts/BytesLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | /* 4 | * @title Solidity Bytes Arrays Utils 5 | * @author Gonçalo Sá 6 | * 7 | * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity. 8 | * The library lets you concatenate, slice and type cast bytes arrays both in memory and storage. 9 | */ 10 | 11 | pragma solidity >=0.6.11; 12 | 13 | /* solhint-disable no-inline-assembly */ 14 | library BytesLib { 15 | function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) { 16 | require(_bytes.length >= (_start + 20), "Read out of bounds"); 17 | address tempAddress; 18 | 19 | assembly { 20 | tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) 21 | } 22 | 23 | return tempAddress; 24 | } 25 | 26 | function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) { 27 | require(_bytes.length >= (_start + 1), "Read out of bounds"); 28 | uint8 tempUint; 29 | 30 | assembly { 31 | tempUint := mload(add(add(_bytes, 0x1), _start)) 32 | } 33 | 34 | return tempUint; 35 | } 36 | 37 | function toUint(bytes memory _bytes, uint256 _start) internal pure returns (uint256) { 38 | require(_bytes.length >= (_start + 32), "Read out of bounds"); 39 | uint256 tempUint; 40 | 41 | assembly { 42 | tempUint := mload(add(add(_bytes, 0x20), _start)) 43 | } 44 | 45 | return tempUint; 46 | } 47 | 48 | function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) { 49 | require(_bytes.length >= (_start + 32), "Read out of bounds"); 50 | bytes32 tempBytes32; 51 | 52 | assembly { 53 | tempBytes32 := mload(add(add(_bytes, 0x20), _start)) 54 | } 55 | 56 | return tempBytes32; 57 | } 58 | } 59 | /* solhint-enable no-inline-assembly */ -------------------------------------------------------------------------------- /packages/arb-shared-dependencies/contracts/Cloneable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | /* 4 | * Copyright 2019-2020, Offchain Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | pragma solidity >=0.6.11; 20 | 21 | import "./ICloneable.sol"; 22 | 23 | contract Cloneable is ICloneable { 24 | string private constant NOT_CLONE = "NOT_CLONE"; 25 | 26 | bool private isMasterCopy; 27 | 28 | constructor() public { 29 | isMasterCopy = true; 30 | } 31 | 32 | function isMaster() external view override returns (bool) { 33 | return isMasterCopy; 34 | } 35 | 36 | function safeSelfDestruct(address payable dest) internal { 37 | require(!isMasterCopy, NOT_CLONE); 38 | selfdestruct(dest); 39 | } 40 | } -------------------------------------------------------------------------------- /packages/arb-shared-dependencies/contracts/DebugPrint.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | /* 4 | * Copyright 2019, Offchain Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | pragma solidity >=0.6.11; 20 | 21 | library DebugPrint { 22 | function char(bytes1 b) private pure returns (bytes1 c) { 23 | if (uint8(b) < 10) { 24 | return bytes1(uint8(b) + 0x30); 25 | } else { 26 | return bytes1(uint8(b) + 0x57); 27 | } 28 | } 29 | 30 | function bytes32string(bytes32 b32) internal pure returns (string memory out) { 31 | bytes memory s = new bytes(64); 32 | 33 | for (uint256 i = 0; i < 32; i++) { 34 | bytes1 b = bytes1(b32[i]); 35 | bytes1 hi = bytes1(uint8(b) / 16); 36 | bytes1 lo = bytes1(uint8(b) - 16 * uint8(hi)); 37 | s[i * 2] = char(hi); 38 | s[i * 2 + 1] = char(lo); 39 | } 40 | 41 | out = string(s); 42 | } 43 | 44 | // Taken from https://github.com/oraclize/ethereum-api/blob/master/oraclizeAPI_0.5.sol 45 | function uint2str(uint256 _iParam) internal pure returns (string memory _uintAsString) { 46 | uint256 _i = _iParam; 47 | if (_i == 0) { 48 | return "0"; 49 | } 50 | uint256 j = _i; 51 | uint256 len; 52 | while (j != 0) { 53 | len++; 54 | j /= 10; 55 | } 56 | bytes memory bstr = new bytes(len); 57 | uint256 k = len - 1; 58 | while (_i != 0) { 59 | bstr[k--] = bytes1(uint8(48 + (_i % 10))); 60 | _i /= 10; 61 | } 62 | return string(bstr); 63 | } 64 | } -------------------------------------------------------------------------------- /packages/arb-shared-dependencies/contracts/ICloneable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | /* 4 | * Copyright 2019, Offchain Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | pragma solidity >=0.6.11; 20 | 21 | interface ICloneable { 22 | function isMaster() external view returns (bool); 23 | } -------------------------------------------------------------------------------- /packages/arb-shared-dependencies/contracts/Inbox.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity >=0.7.0; 3 | 4 | 5 | interface IInbox { 6 | 7 | 8 | function sendL2Message(bytes calldata messageData) external returns (uint256); 9 | 10 | function sendUnsignedTransaction( 11 | uint256 maxGas, 12 | uint256 gasPriceBid, 13 | uint256 nonce, 14 | address destAddr, 15 | uint256 amount, 16 | bytes calldata data 17 | ) external returns (uint256); 18 | 19 | function sendContractTransaction( 20 | uint256 maxGas, 21 | uint256 gasPriceBid, 22 | address destAddr, 23 | uint256 amount, 24 | bytes calldata data 25 | ) external returns (uint256); 26 | 27 | function sendL1FundedUnsignedTransaction( 28 | uint256 maxGas, 29 | uint256 gasPriceBid, 30 | uint256 nonce, 31 | address destAddr, 32 | bytes calldata data 33 | ) external payable returns (uint256); 34 | 35 | function sendL1FundedContractTransaction( 36 | uint256 maxGas, 37 | uint256 gasPriceBid, 38 | address destAddr, 39 | bytes calldata data 40 | ) external payable returns (uint256); 41 | 42 | function createRetryableTicket( 43 | address destAddr, 44 | uint256 arbTxCallValue, 45 | uint256 maxSubmissionCost, 46 | address submissionRefundAddress, 47 | address valueRefundAddress, 48 | uint256 maxGas, 49 | uint256 gasPriceBid, 50 | bytes calldata data 51 | ) external payable returns (uint256); 52 | 53 | function depositEth(uint256 maxSubmissionCost) external payable returns (uint256); 54 | 55 | function bridge() external view returns (IBridge); 56 | } 57 | 58 | 59 | interface IBridge { 60 | event MessageDelivered( 61 | uint256 indexed messageIndex, 62 | bytes32 indexed beforeInboxAcc, 63 | address inbox, 64 | uint8 kind, 65 | address sender, 66 | bytes32 messageDataHash 67 | ); 68 | 69 | function deliverMessageToInbox( 70 | uint8 kind, 71 | address sender, 72 | bytes32 messageDataHash 73 | ) external payable returns (uint256); 74 | 75 | function executeCall( 76 | address destAddr, 77 | uint256 amount, 78 | bytes calldata data 79 | ) external returns (bool success, bytes memory returnData); 80 | 81 | // These are only callable by the admin 82 | function setInbox(address inbox, bool enabled) external; 83 | 84 | function setOutbox(address inbox, bool enabled) external; 85 | 86 | // View functions 87 | 88 | function activeOutbox() external view returns (address); 89 | 90 | function allowedInboxes(address inbox) external view returns (bool); 91 | 92 | function allowedOutboxes(address outbox) external view returns (bool); 93 | 94 | function inboxAccs(uint256 index) external view returns (bytes32); 95 | 96 | function messageCount() external view returns (uint256); 97 | } 98 | 99 | interface IMessageProvider { 100 | event InboxMessageDelivered(uint256 indexed messageNum, bytes data); 101 | 102 | event InboxMessageDeliveredFromOrigin(uint256 indexed messageNum); 103 | } 104 | -------------------------------------------------------------------------------- /packages/arb-shared-dependencies/contracts/Outbox.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | /* 4 | * Copyright 2021, Offchain Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | pragma solidity >=0.7.0; 20 | 21 | interface IOutbox { 22 | event OutboxEntryCreated( 23 | uint256 indexed batchNum, 24 | uint256 outboxIndex, 25 | bytes32 outputRoot, 26 | uint256 numInBatch 27 | ); 28 | 29 | function l2ToL1Sender() external view returns (address); 30 | 31 | function l2ToL1Block() external view returns (uint256); 32 | 33 | function l2ToL1EthBlock() external view returns (uint256); 34 | 35 | function l2ToL1Timestamp() external view returns (uint256); 36 | 37 | function processOutgoingMessages(bytes calldata sendsData, uint256[] calldata sendLengths) 38 | external; 39 | } -------------------------------------------------------------------------------- /packages/arb-shared-dependencies/contracts/ProxyUtil.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | /* 4 | * Copyright 2021, Offchain Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | pragma solidity >=0.6.11; 20 | 21 | library ProxyUtil { 22 | function getProxyAdmin() internal view returns (address admin) { 23 | // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.4.0/contracts/proxy/TransparentUpgradeableProxy.sol#L48 24 | // Storage slot with the admin of the proxy contract. 25 | // This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is 26 | bytes32 slot = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; 27 | assembly { 28 | admin := sload(slot) 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /packages/arb-shared-dependencies/contracts/Whitelist.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | /* 4 | * Copyright 2021, Offchain Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | pragma solidity >=0.6.11; 20 | 21 | abstract contract WhitelistConsumer { 22 | address public whitelist; 23 | 24 | event WhitelistSourceUpdated(address newSource); 25 | 26 | modifier onlyWhitelisted() { 27 | if (whitelist != address(0)) { 28 | require(Whitelist(whitelist).isAllowed(msg.sender), "NOT_WHITELISTED"); 29 | } 30 | _; 31 | } 32 | 33 | function updateWhitelistSource(address newSource) external { 34 | require(msg.sender == whitelist, "NOT_FROM_LIST"); 35 | whitelist = newSource; 36 | emit WhitelistSourceUpdated(newSource); 37 | } 38 | } 39 | 40 | contract Whitelist { 41 | address public owner; 42 | mapping(address => bool) public isAllowed; 43 | 44 | event OwnerUpdated(address newOwner); 45 | event WhitelistUpgraded(address newWhitelist, address[] targets); 46 | 47 | constructor() public { 48 | owner = msg.sender; 49 | } 50 | 51 | modifier onlyOwner() { 52 | require(msg.sender == owner, "ONLY_OWNER"); 53 | _; 54 | } 55 | 56 | function setOwner(address newOwner) external onlyOwner { 57 | owner = newOwner; 58 | emit OwnerUpdated(newOwner); 59 | } 60 | 61 | function setWhitelist(address[] memory user, bool[] memory val) external onlyOwner { 62 | require(user.length == val.length, "INVALID_INPUT"); 63 | 64 | for (uint256 i = 0; i < user.length; i++) { 65 | isAllowed[user[i]] = val[i]; 66 | } 67 | } 68 | 69 | // set new whitelist to address(0) to disable whitelist 70 | function triggerConsumers(address newWhitelist, address[] memory targets) external onlyOwner { 71 | for (uint256 i = 0; i < targets.length; i++) { 72 | WhitelistConsumer(targets[i]).updateWhitelistSource(newWhitelist); 73 | } 74 | emit WhitelistUpgraded(newWhitelist, targets); 75 | } 76 | } -------------------------------------------------------------------------------- /packages/arb-shared-dependencies/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | module.exports = { 3 | solidity: { 4 | compilers: [ 5 | { 6 | version: "0.8.2", 7 | settings: {}, 8 | }, 9 | 10 | { 11 | version: "0.7.2", 12 | settings: {}, 13 | }, 14 | { 15 | version: "0.6.12", 16 | settings: {}, 17 | }, 18 | { 19 | version: "0.6.11", 20 | settings: {}, 21 | }, 22 | ], 23 | }, 24 | networks: { 25 | l1: { 26 | gas: 2100000, 27 | gasLimit: 0, 28 | url: process.env['L1RPC'] || '', 29 | accounts: process.env['DEVNET_PRIVKEY'] ? [process.env['DEVNET_PRIVKEY']] : [], 30 | 31 | }, 32 | l2: { 33 | url: process.env['L2RPC'] || '', 34 | accounts: process.env['DEVNET_PRIVKEY'] ? [process.env['DEVNET_PRIVKEY']] : [], 35 | }, 36 | }, 37 | } 38 | -------------------------------------------------------------------------------- /packages/arb-shared-dependencies/index.js: -------------------------------------------------------------------------------- 1 | const hardhatConfig = require('./hardhat.config.js') 2 | require('dotenv').config() 3 | 4 | const wait = (ms = 0) => { 5 | return new Promise(res => setTimeout(res, ms || 0)) 6 | } 7 | 8 | const arbLog = async text => { 9 | let str = '🔵' 10 | for (let i = 0; i < 25; i++) { 11 | await wait(40) 12 | if (i == 12) { 13 | str = `🔵${'🔵'.repeat(i)}🔵` 14 | } else { 15 | str = `🔵${' '.repeat(i * 2)}🔵` 16 | } 17 | while (str.length < 60) { 18 | str = ` ${str} ` 19 | } 20 | str = str 21 | 22 | console.log(str) 23 | } 24 | 25 | console.log('Arbitrum Demo:', text) 26 | await wait(2000) 27 | 28 | console.log('Lets') 29 | await wait(1000) 30 | 31 | console.log('Go ➡️') 32 | await wait(1000) 33 | console.log('...🚀') 34 | await wait(1000) 35 | console.log('') 36 | } 37 | 38 | const requireEnvVariables = envVars => { 39 | for (const envVar of envVars) { 40 | if (!process.env[envVar]) { 41 | throw new Error(`Error: set your '${envVar}' environmental variable `) 42 | } 43 | } 44 | console.log('Environmental variables properly set 👍') 45 | } 46 | module.exports = { 47 | arbLog, 48 | hardhatConfig, 49 | requireEnvVariables, 50 | } 51 | -------------------------------------------------------------------------------- /packages/arb-shared-dependencies/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arb-shared-dependencies", 3 | "license": "Apache-2.0", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "build": "hardhat compile" 7 | }, 8 | "main": "index.js", 9 | "devDependencies": { 10 | "@nomiclabs/hardhat-ethers": "^2.0.2", 11 | "arb-ts": "1.0.0-beta.4", 12 | "eslint": "^7.30.0", 13 | "ethers": "^5.1.2", 14 | "hardhat": "^2.2.0", 15 | "prettier": "^2.3.2" 16 | }, 17 | "dependencies": { 18 | "dotenv": "^8.2.0", 19 | "eslint-plugin-prettier": "^3.4.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /scripts/arb_bridge_deploy.js: -------------------------------------------------------------------------------- 1 | const hre = require("hardhat"); 2 | const ethers = require("ethers"); 3 | const { Bridge } = require("arb-ts"); 4 | const { hexDataLength } = require("@ethersproject/bytes"); 5 | const { arbLog, requireEnvVariables } = require("arb-shared-dependencies"); 6 | requireEnvVariables(["DEVNET_PRIVKEY", "L2RPC", "L1RPC", "INBOX_ADDR"]); 7 | 8 | /** 9 | * Instantiate wallets and providers for bridge 10 | */ 11 | 12 | const walletPrivateKey = process.env.DEVNET_PRIVKEY; 13 | 14 | const l1Provider = new ethers.providers.JsonRpcProvider(process.env.L1RPC); 15 | const l2Provider = new ethers.providers.JsonRpcProvider(process.env.L2RPC); 16 | const signer = new ethers.Wallet(walletPrivateKey); 17 | 18 | const l1Signer = signer.connect(l1Provider); 19 | const l2Signer = signer.connect(l2Provider); 20 | 21 | const main = async () => { 22 | await arbLog("Cross-chain L2 Bridge"); 23 | /** 24 | * Use wallets to create an arb-ts bridge instance to use its convenience methods 25 | */ 26 | const bridge = await Bridge.init(l1Signer, l2Signer); 27 | 28 | /** 29 | * We deploy L1 Bridge to L1, L2 Bridge (source and destination) to L2. 30 | */ 31 | 32 | const L1Bridge = await ( 33 | await hre.ethers.getContractFactory("ArbitrumL1Bridge") 34 | ).connect(l1Signer); // 35 | console.log("Deploying L1 Bridge 👋"); 36 | const l1Bridge = await L1Bridge.deploy( 37 | ethers.constants.AddressZero, // temp l2 addr 38 | ethers.constants.AddressZero, // temp l2 addr 39 | process.env.INBOX_ADDR 40 | ); 41 | await l1Bridge.deployed(); 42 | console.log(`deployed to ${l1Bridge.address}`); 43 | 44 | const L2BridgeSrc = await ( 45 | await hre.ethers.getContractFactory("ArbitrumBridgeSource") 46 | ).connect(l2Signer); 47 | 48 | console.log("Deploying L2 Bridge Source 👋👋"); 49 | 50 | const l2BridgeSrc = await L2BridgeSrc.deploy( 51 | ethers.constants.AddressZero // temp l1 addr 52 | ); 53 | await l2BridgeSrc.deployed(); 54 | console.log(`deployed to ${l2BridgeSrc.address}`); 55 | 56 | const L2BridgeDst = await ( 57 | await hre.ethers.getContractFactory("ArbitrumBridgeDestination") 58 | ).connect(l2Signer); 59 | 60 | console.log("Deploying L2 Bridge Destination 👋👋"); 61 | 62 | const l2BridgeDst = await L2BridgeDst.deploy( 63 | ethers.constants.AddressZero // temp l1 addr 64 | ); 65 | await l2BridgeDst.deployed(); 66 | console.log(`deployed to ${l2BridgeDst.address}`); 67 | 68 | const updateL1TxForSrc = await l1Bridge.updateL2Source(l2BridgeSrc.address); 69 | await updateL1TxForSrc.wait(); 70 | const updateL1TxForDst = await l1Bridge.updateL2Target(l2BridgeDst.address); 71 | await updateL1TxForDst.wait(); 72 | 73 | const updateL2SrcTx = await l2BridgeSrc.updateL1Target(l1Bridge.address); 74 | await updateL2SrcTx.wait(); 75 | 76 | const updateL2DstTx = await l2BridgeDst.updateL1Target(l1Bridge.address); 77 | await updateL2DstTx.wait(); 78 | 79 | const L2Token = await ( 80 | await hre.ethers.getContractFactory("TestERC20WithName") 81 | ).connect(l2Signer); 82 | 83 | console.log("Deploying L2 Source Token 👋👋"); 84 | 85 | const l2TokenSrc = await L2Token.deploy("SRC_TOKEN"); 86 | await l2TokenSrc.deployed(); 87 | console.log(`deployed to ${l2TokenSrc.address}`); 88 | 89 | console.log("Deploying L2 Destination Token 👋👋"); 90 | 91 | const l2TokenDst = await L2Token.deploy("DST_TOKEN"); 92 | await l2TokenDst.deployed(); 93 | console.log(`deployed to ${l2TokenDst.address}`); 94 | 95 | console.log("Counterpart contract addresses set in all contracts 👍"); 96 | }; 97 | 98 | main() 99 | .then(() => process.exit(0)) 100 | .catch((error) => { 101 | console.error(error); 102 | process.exit(1); 103 | }); 104 | -------------------------------------------------------------------------------- /scripts/arb_bridge_env_sample: -------------------------------------------------------------------------------- 1 | # This is a sample .env file for use in local development. 2 | # Duplicate this file as .env here 3 | 4 | # Your Private key 5 | DEVNET_PRIVKEY="0x your key here" 6 | 7 | # Hosted Aggregator Node (JSON-RPC Endpoint) 8 | L2RPC="https://rinkeby.arbitrum.io/rpc" 9 | 10 | # Ethereum RPC i.e., for rinkeby https://rinkeby.infura.io/v3/ 11 | L1RPC="" 12 | 13 | 14 | # Address of the Arbitrum Inbox on the L1 chain 15 | INBOX_ADDR=0x578bade599406a8fe3d24fd7f7211c0911f5b29e -------------------------------------------------------------------------------- /scripts/deploy.js: -------------------------------------------------------------------------------- 1 | // We require the Hardhat Runtime Environment explicitly here. This is optional 2 | // but useful for running the script in a standalone fashion through `node