From f3af417a4618e143cd49c8b7693ff56d2205e741 Mon Sep 17 00:00:00 2001 From: Simon Dosch Date: Tue, 12 Sep 2023 17:56:25 +0200 Subject: [PATCH 01/19] RFC220 matic migration --- .../root/depositManager/DepositManager.sol | 58 ++++++++-------- package-lock.json | 68 ++++++++++++++----- 2 files changed, 82 insertions(+), 44 deletions(-) diff --git a/contracts/root/depositManager/DepositManager.sol b/contracts/root/depositManager/DepositManager.sol index b3be23ad2..a12e121ea 100644 --- a/contracts/root/depositManager/DepositManager.sol +++ b/contracts/root/depositManager/DepositManager.sol @@ -14,6 +14,9 @@ import {StateSender} from "../stateSyncer/StateSender.sol"; import {GovernanceLockable} from "../../common/mixin/GovernanceLockable.sol"; import {RootChain} from "../RootChain.sol"; +interface IPolygonMigration { + function migrate(uint256 amount) external; +} contract DepositManager is DepositManagerStorage, IDepositManager, ERC721Holder { using SafeMath for uint256; @@ -36,17 +39,30 @@ contract DepositManager is DepositManagerStorage, IDepositManager, ERC721Holder depositEther(); } + function migrateMatic(uint256 _amount) external onlyGovernance { + _migrateMatic(_amount); + } + + function _migrateMatic(uint256 _amount) private { + IERC20 matic = IERC20(registry.contractMap(keccak256("matic"))); + + // check that _amount is not too high + require(matic.balanceOf(address(this)) > _amount, "amount exceeds this contract's MATIC balance"); + + // approve + matic.approve(registry.contractMap(keccak256("polygonMigration")), _amount); + + // call migrate function + IPolygonMigration(registry.contractMap(keccak256("polygonMigration"))).migrate(_amount); + } + function updateMaxErc20Deposit(uint256 maxDepositAmount) public onlyGovernance { require(maxDepositAmount != 0); emit MaxErc20DepositUpdate(maxErc20Deposit, maxDepositAmount); maxErc20Deposit = maxDepositAmount; } - function transferAssets( - address _token, - address _user, - uint256 _amountOrNFTId - ) external isPredicateAuthorized { + function transferAssets(address _token, address _user, uint256 _amountOrNFTId) external isPredicateAuthorized { address wethToken = registry.getWethTokenAddress(); if (registry.isERC721(_token)) { IERC721(_token).transferFrom(address(this), _user, _amountOrNFTId); @@ -104,21 +120,19 @@ contract DepositManager is DepositManagerStorage, IDepositManager, ERC721Holder stateSender = StateSender(_stateSender); } - function depositERC20ForUser( - address _token, - address _user, - uint256 _amount - ) public { + function depositERC20ForUser(address _token, address _user, uint256 _amount) public { require(_amount <= maxErc20Deposit, "exceed maximum deposit amount"); IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount); + + // new: auto-migrate MATIC to POL + if (_token == registry.contractMap(keccak256("matic"))) { + _migrateMatic(_amount); + } + _safeCreateDepositBlock(_user, _token, _amount); } - function depositERC721ForUser( - address _token, - address _user, - uint256 _tokenId - ) public { + function depositERC721ForUser(address _token, address _user, uint256 _tokenId) public { require(registry.isTokenMappedAndIsErc721(_token), "not erc721"); _safeTransferERC721(msg.sender, _token, _tokenId); @@ -138,20 +152,10 @@ contract DepositManager is DepositManagerStorage, IDepositManager, ERC721Holder address _token, uint256 _amountOrToken ) internal onlyWhenUnlocked isTokenMapped(_token) { - _createDepositBlock( - _user, - _token, - _amountOrToken, - rootChain.updateDepositId(1) /* returns _depositId */ - ); + _createDepositBlock(_user, _token, _amountOrToken, rootChain.updateDepositId(1) /* returns _depositId */); } - function _createDepositBlock( - address _user, - address _token, - uint256 _amountOrToken, - uint256 _depositId - ) internal { + function _createDepositBlock(address _user, address _token, uint256 _amountOrToken, uint256 _depositId) internal { deposits[_depositId] = DepositBlock(keccak256(abi.encodePacked(_user, _token, _amountOrToken)), now); stateSender.syncState(childChain, abi.encode(_user, _token, _amountOrToken, _depositId)); emit NewDepositBlock(_user, _token, _amountOrToken, _depositId); diff --git a/package-lock.json b/package-lock.json index e6e96d4cf..9cfd67a88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1217,8 +1217,20 @@ "dev": true, "requires": { "underscore": "1.9.1", - "web3-core-helpers": "1.2.1", - "websocket": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400" + "web3-core-helpers": "1.2.1" + }, + "dependencies": { + "websocket": { + "version": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400", + "from": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400", + "requires": { + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "nan": "^2.14.0", + "typedarray-to-buffer": "^3.1.5", + "yaeti": "^0.0.6" + } + } } }, "web3-shh": { @@ -1236,7 +1248,6 @@ "websocket": { "version": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400", "from": "github:web3-js/WebSocket-Node#polyfill/globalThis", - "dev": true, "requires": { "debug": "^2.2.0", "es5-ext": "^0.10.50", @@ -1703,8 +1714,20 @@ "dev": true, "requires": { "underscore": "1.9.1", - "web3-core-helpers": "1.2.1", - "websocket": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400" + "web3-core-helpers": "1.2.1" + }, + "dependencies": { + "websocket": { + "version": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400", + "from": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400", + "requires": { + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "nan": "^2.14.0", + "typedarray-to-buffer": "^3.1.5", + "yaeti": "^0.0.6" + } + } } }, "web3-shh": { @@ -1722,7 +1745,6 @@ "websocket": { "version": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400", "from": "github:web3-js/WebSocket-Node#polyfill/globalThis", - "dev": true, "requires": { "debug": "^2.2.0", "es5-ext": "^0.10.50", @@ -1786,6 +1808,14 @@ "integrity": "sha512-yzBInQFhdY8kaZmqoL2+3U5dSTMrKaYcb561VU+lDzAYvqt+2lojvBEy+hmpSNuXnPTx7m9+04CzWYOUqWME2A==", "dev": true }, + "@web3-js/scrypt-shim": { + "version": "github:web3-js/scrypt-shim#aafdadda13e660e25e1c525d1f5b2443f5eb1ebb", + "from": "github:web3-js/scrypt-shim#aafdadda13e660e25e1c525d1f5b2443f5eb1ebb", + "requires": { + "scryptsy": "^2.1.0", + "semver": "^6.3.0" + } + }, "elliptic": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", @@ -2189,8 +2219,7 @@ "dev": true, "requires": { "underscore": "1.9.1", - "web3-core-helpers": "1.2.1", - "websocket": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400" + "web3-core-helpers": "1.2.1" } }, "web3-shh": { @@ -2219,6 +2248,17 @@ "underscore": "1.9.1", "utf8": "3.0.0" } + }, + "websocket": { + "version": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400", + "from": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400", + "requires": { + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "nan": "^2.14.0", + "typedarray-to-buffer": "^3.1.5", + "yaeti": "^0.0.6" + } } } }, @@ -2531,14 +2571,12 @@ "dev": true, "requires": { "underscore": "1.9.1", - "web3-core-helpers": "1.2.2", - "websocket": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400" + "web3-core-helpers": "1.2.2" }, "dependencies": { "websocket": { "version": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400", - "from": "github:web3-js/WebSocket-Node#polyfill/globalThis", - "dev": true, + "from": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400", "requires": { "debug": "^2.2.0", "es5-ext": "^0.10.50", @@ -2588,7 +2626,6 @@ "websocket": { "version": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400", "from": "github:web3-js/WebSocket-Node#polyfill/globalThis", - "dev": true, "requires": { "debug": "^2.2.0", "es5-ext": "^0.10.50", @@ -5913,7 +5950,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -15014,7 +15050,6 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, "requires": { "is-typedarray": "^1.0.0" } @@ -16399,8 +16434,7 @@ "yaeti": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=", - "dev": true + "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=" }, "yallist": { "version": "3.1.1", From e041d7615b2c2343aa1445ebd0751ff8c20cbcef Mon Sep 17 00:00:00 2001 From: Simon Dosch Date: Mon, 18 Sep 2023 11:25:37 +0200 Subject: [PATCH 02/19] new behaviour for POL integration --- contracts/common/tokens/TestToken.sol | 37 +++++ .../root/depositManager/DepositManager.sol | 41 +++-- contracts/test/PolygonMigrationTest.sol | 34 ++++ package-lock.json | 43 +++-- package.json | 4 +- test/helpers/artifacts.js | 1 + .../root/DepositManagerUpdate.test.js | 152 ++++++++++++++++++ truffle-config.js | 2 +- 8 files changed, 288 insertions(+), 26 deletions(-) create mode 100644 contracts/test/PolygonMigrationTest.sol create mode 100644 test/integration/root/DepositManagerUpdate.test.js diff --git a/contracts/common/tokens/TestToken.sol b/contracts/common/tokens/TestToken.sol index 7dffc0068..7996ee73a 100644 --- a/contracts/common/tokens/TestToken.sol +++ b/contracts/common/tokens/TestToken.sol @@ -1,8 +1,11 @@ pragma solidity ^0.5.2; import "openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol"; +import "openzeppelin-solidity/contracts/utils/Address.sol"; contract TestToken is ERC20Mintable { + using Address for address; + // detailed ERC20 string public name; string public symbol; @@ -15,4 +18,38 @@ contract TestToken is ERC20Mintable { uint256 value = 10**10 * (10**18); mint(msg.sender, value); } + + function safeTransfer(IERC20 token, address to, uint256 value) public { + callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) public { + callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must equal true). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. + + // A Solidity high level call has three parts: + // 1. The target address is checked to verify it contains contract code + // 2. The call itself is made, and success asserted + // 3. The return value is decoded, which in turn checks the size of the returned data. + + require(address(token).isContract()); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = address(token).call(data); + require(success); + + if (returndata.length > 0) { // Return data is optional + require(abi.decode(returndata, (bool))); + } + } } diff --git a/contracts/root/depositManager/DepositManager.sol b/contracts/root/depositManager/DepositManager.sol index a12e121ea..6a3d088d1 100644 --- a/contracts/root/depositManager/DepositManager.sol +++ b/contracts/root/depositManager/DepositManager.sol @@ -18,12 +18,14 @@ interface IPolygonMigration { function migrate(uint256 amount) external; } + contract DepositManager is DepositManagerStorage, IDepositManager, ERC721Holder { using SafeMath for uint256; using SafeERC20 for IERC20; modifier isTokenMapped(address _token) { - require(registry.isTokenMapped(_token), "TOKEN_NOT_SUPPORTED"); + // new: exception for POL token + require(registry.isTokenMapped(_token) || _token == registry.contractMap(keccak256("pol")), "TOKEN_NOT_SUPPORTED"); _; } @@ -39,6 +41,7 @@ contract DepositManager is DepositManagerStorage, IDepositManager, ERC721Holder depositEther(); } + // new: governance function to migrate MATIC to POL function migrateMatic(uint256 _amount) external onlyGovernance { _migrateMatic(_amount); } @@ -47,7 +50,7 @@ contract DepositManager is DepositManagerStorage, IDepositManager, ERC721Holder IERC20 matic = IERC20(registry.contractMap(keccak256("matic"))); // check that _amount is not too high - require(matic.balanceOf(address(this)) > _amount, "amount exceeds this contract's MATIC balance"); + require(matic.balanceOf(address(this)) >= _amount, "amount exceeds this contract's MATIC balance"); // approve matic.approve(registry.contractMap(keccak256("polygonMigration")), _amount); @@ -64,13 +67,22 @@ contract DepositManager is DepositManagerStorage, IDepositManager, ERC721Holder function transferAssets(address _token, address _user, uint256 _amountOrNFTId) external isPredicateAuthorized { address wethToken = registry.getWethTokenAddress(); - if (registry.isERC721(_token)) { - IERC721(_token).transferFrom(address(this), _user, _amountOrNFTId); - } else if (_token == wethToken) { - WETH t = WETH(_token); + + // so we don't assign to a function var + address memory token = _token; + + // new: pay out POL when MATIC is withdrawn + if (_token == registry.contractMap(keccak256("matic"))) { + token = registry.contractMap(keccak256("pol")); + } + + if (registry.isERC721(token)) { + IERC721(token).transferFrom(address(this), _user, _amountOrNFTId); + } else if (token == wethToken) { + WETH t = WETH(token); t.withdraw(_amountOrNFTId, _user); } else { - require(IERC20(_token).transfer(_user, _amountOrNFTId), "TRANSFER_FAILED"); + require(IERC20(token).transfer(_user, _amountOrNFTId), "TRANSFER_FAILED"); } } @@ -124,11 +136,6 @@ contract DepositManager is DepositManagerStorage, IDepositManager, ERC721Holder require(_amount <= maxErc20Deposit, "exceed maximum deposit amount"); IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount); - // new: auto-migrate MATIC to POL - if (_token == registry.contractMap(keccak256("matic"))) { - _migrateMatic(_amount); - } - _safeCreateDepositBlock(_user, _token, _amount); } @@ -156,6 +163,16 @@ contract DepositManager is DepositManagerStorage, IDepositManager, ERC721Holder } function _createDepositBlock(address _user, address _token, uint256 _amountOrToken, uint256 _depositId) internal { + // new: auto-migrate MATIC to POL + if (_token == registry.contractMap(keccak256("matic"))) { + _migrateMatic(_amountOrToken); + } + + // new: bridge POL as MATIC, child chain behaviour does not change + if(_token == registry.contractMap(keccak256("pol"))) { + _token == registry.contractMap(keccak256("matic")); + } + deposits[_depositId] = DepositBlock(keccak256(abi.encodePacked(_user, _token, _amountOrToken)), now); stateSender.syncState(childChain, abi.encode(_user, _token, _amountOrToken, _depositId)); emit NewDepositBlock(_user, _token, _amountOrToken, _depositId); diff --git a/contracts/test/PolygonMigrationTest.sol b/contracts/test/PolygonMigrationTest.sol new file mode 100644 index 000000000..6089be8ce --- /dev/null +++ b/contracts/test/PolygonMigrationTest.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.5.2; + +import {IERC20} from "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol"; + +// this impl was shortened for testing purposes +// full impl at https://github.com/0xPolygon/indicia/blob/main/src/PolygonMigration.sol +contract PolygonMigrationTest { + using SafeERC20 for IERC20; + + event Migrated(address indexed account, uint256 amount); + + IERC20 public polygon; + IERC20 public matic; + + function setTokenAddresses(address matic_, address polygon_) external { + if (matic_ == address(0)) revert(); + matic = IERC20(matic_); + + if (polygon_ == address(0)) revert(); + polygon = IERC20(polygon_); + } + + /// @notice This function allows for migrating MATIC tokens to POL tokens + /// @dev The function does not do any validation since the migration is a one-way process + /// @param amount Amount of MATIC to migrate + function migrate(uint256 amount) external { + emit Migrated(msg.sender, amount); + + matic.safeTransferFrom(msg.sender, address(this), amount); + polygon.safeTransfer(msg.sender, amount); + } +} diff --git a/package-lock.json b/package-lock.json index 9cfd67a88..170cbe8a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1217,12 +1217,14 @@ "dev": true, "requires": { "underscore": "1.9.1", - "web3-core-helpers": "1.2.1" + "web3-core-helpers": "1.2.1", + "websocket": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400" }, "dependencies": { "websocket": { "version": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400", - "from": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400", + "from": "github:web3-js/WebSocket-Node#polyfill/globalThis", + "dev": true, "requires": { "debug": "^2.2.0", "es5-ext": "^0.10.50", @@ -1714,12 +1716,14 @@ "dev": true, "requires": { "underscore": "1.9.1", - "web3-core-helpers": "1.2.1" + "web3-core-helpers": "1.2.1", + "websocket": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400" }, "dependencies": { "websocket": { "version": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400", - "from": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400", + "from": "github:web3-js/WebSocket-Node#polyfill/globalThis", + "dev": true, "requires": { "debug": "^2.2.0", "es5-ext": "^0.10.50", @@ -2219,7 +2223,22 @@ "dev": true, "requires": { "underscore": "1.9.1", - "web3-core-helpers": "1.2.1" + "web3-core-helpers": "1.2.1", + "websocket": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400" + }, + "dependencies": { + "websocket": { + "version": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400", + "from": "github:web3-js/WebSocket-Node#polyfill/globalThis", + "dev": true, + "requires": { + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "nan": "^2.14.0", + "typedarray-to-buffer": "^3.1.5", + "yaeti": "^0.0.6" + } + } } }, "web3-shh": { @@ -2571,12 +2590,14 @@ "dev": true, "requires": { "underscore": "1.9.1", - "web3-core-helpers": "1.2.2" + "web3-core-helpers": "1.2.2", + "websocket": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400" }, "dependencies": { "websocket": { "version": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400", - "from": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400", + "from": "github:web3-js/WebSocket-Node#polyfill/globalThis", + "dev": true, "requires": { "debug": "^2.2.0", "es5-ext": "^0.10.50", @@ -8590,7 +8611,7 @@ "get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", "dev": true }, "get-intrinsic": { @@ -11185,9 +11206,9 @@ } }, "pathval": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, "pbkdf2": { diff --git a/package.json b/package.json index 45f62a3db..e9920b485 100644 --- a/package.json +++ b/package.json @@ -70,9 +70,9 @@ "solidity-coverage": "^0.7.4", "solium": "^1.1.8", "truffle": "5.0.34", + "truffle-contract-size": "^1.0.1", "web3": "^1.0.0-beta.33", - "web3-eth-abi": "^1.0.0-beta.51", - "truffle-contract-size": "^1.0.1" + "web3-eth-abi": "^1.0.0-beta.51" }, "dependencies": { "@truffle/hdwallet-provider": "^1.4.0", diff --git a/test/helpers/artifacts.js b/test/helpers/artifacts.js index 041c64422..2226128d3 100644 --- a/test/helpers/artifacts.js +++ b/test/helpers/artifacts.js @@ -52,6 +52,7 @@ export const ExitNFT = artifacts.require('ExitNFT') // Misc export const GnosisSafeProxy = artifacts.require('GnosisSafeProxy') export const GnosisSafe = artifacts.require('GnosisSafe') +export const PolygonMigrationTest = artifacts.require('PolygonMigrationTest') // child chain export const childContracts = { diff --git a/test/integration/root/DepositManagerUpdate.test.js b/test/integration/root/DepositManagerUpdate.test.js new file mode 100644 index 000000000..d69583d11 --- /dev/null +++ b/test/integration/root/DepositManagerUpdate.test.js @@ -0,0 +1,152 @@ +import deployer from '../../helpers/deployer.js' +import * as utils from '../../helpers/utils.js' +import chai from 'chai' +import chaiAsPromised from 'chai-as-promised' +import * as artifacts from '../../helpers/artifacts.js' +import StatefulUtils from '../../helpers/StatefulUtils' + +const predicateTestUtils = require('./predicates/predicateTestUtils') +const ethUtils = require('ethereumjs-util') +const crypto = require('crypto') + +chai + .use(chaiAsPromised) + .should() + +contract('DepositManager Update @skip-on-coverage', async function(accounts) { + let depositManager, childContracts, registry, governance, e20, polygonMigrationTest, pol, statefulUtils, contracts + const amount = web3.utils.toBN('10').pow(web3.utils.toBN('18')) + + describe('test POL and MATIC behaviours', async function() { + before(async() => { + statefulUtils = new StatefulUtils() + }) + + beforeEach(async function() { + contracts = await deployer.freshDeploy(accounts[0]) + contracts.ERC20Predicate = await deployer.deployErc20Predicate() + depositManager = contracts.depositManager + registry = contracts.registry + governance = contracts.governance + childContracts = await deployer.initializeChildChain(accounts[0]) + + e20 = await deployer.deployMaticToken() + await governance.update( + registry.address, + registry.contract.methods.updateContractMap(ethUtils.keccak256('matic'), e20.rootERC20.address).encodeABI() + ) + + // deploy PolygonMigration test impl + polygonMigrationTest = await artifacts.PolygonMigrationTest.new() + + await governance.update( + registry.address, + registry.contract.methods.updateContractMap(ethUtils.keccak256('polygonMigration'), polygonMigrationTest.address).encodeABI() + ) + + pol = await artifacts.TestToken.new('Polygon Ecosystem Token', 'POL') + + await governance.update( + registry.address, + registry.contract.methods.updateContractMap(ethUtils.keccak256('pol'), pol.address).encodeABI() + ) + + await polygonMigrationTest.contract.methods.setTokenAddresses(e20.rootERC20.address, pol.address).send({ + from: accounts[0] + }) + + // mint POL to PolygonMigrationTest + await pol.contract.methods.mint(polygonMigrationTest.address, amount.toString()).send( + { from: accounts[0] } + ) + }) + + it('converts MATIC to POL using governance function', async() => { + // mint MATIC to depositManager + await e20.rootERC20.contract.methods.mint(depositManager.address, amount.toString()).send( + { from: accounts[0] } + ) + + // call migrateMatic using governance + await governance.update( + depositManager.address, + depositManager.contract.methods.migrateMatic(amount.toString()).encodeABI() + ) + + // check that MATIC balance has been converted to POL + const currentBalance = await pol.contract.methods.balanceOf(depositManager.address).call() + utils.assertBigNumberEquality(currentBalance, amount) + }) + + it('migrates to POL when depositing MATIC', async() => { + // deposit some MATIC + const bob = '0x' + crypto.randomBytes(20).toString('hex') + await utils.deposit( + depositManager, + childContracts.childChain, + e20.rootERC20, + bob, + amount, + { rootDeposit: true, erc20: true } + ) + + // check that MATIC balance has been converted to POL + const currentBalance = await pol.contract.methods.balanceOf(depositManager.address).call() + utils.assertBigNumberEquality(currentBalance, amount) + + // assert deposit on child chain + utils.assertBigNumberEquality(await e20.childToken.balanceOf(bob), amount) + }) + + it('bridges MATIC when depositing POL', async() => { + const bob = '0x' + crypto.randomBytes(20).toString('hex') + + // using the utils function more granularly here so we can call fireDepositFromMainToMatic with the correct token address + const depositBlockId = await utils.depositOnRoot( + depositManager, + pol, + bob, + amount, + { rootDeposit: true, erc20: true } + ) + await utils.fireDepositFromMainToMatic(childContracts.childChain, '0xa', bob, e20.rootERC20.address, amount, depositBlockId) + + // deposit on child chain is technically still in MATIC + utils.assertBigNumberEquality(await e20.childToken.balanceOf(bob), amount) + }) + + it('returns POL when withdrawing MATIC', async() => { + // no POL on this account + utils.assertBigNumberEquality(await pol.balanceOf(accounts[1]), 0) + + // deposit some MATIC + await utils.deposit( + depositManager, + childContracts.childChain, + e20.rootERC20, + accounts[1], + amount, + { rootDeposit: true, erc20: true } + ) + + // withdraw again + const { receipt } = await e20.childToken.withdraw(amount, { from: accounts[1], value: amount }) + + // submit checkpoint + let { block, blockProof, headerNumber, reference } = await statefulUtils.submitCheckpoint(contracts.rootChain, receipt, accounts) + + // call ERC20Predicate + await utils.startExitWithBurntTokens( + contracts.ERC20Predicate, + { headerNumber, blockProof, blockNumber: block.number, blockTimestamp: block.timestamp, reference, logIndex: 1 }, + accounts[1] + ) + + // process Exits for MATIC + await predicateTestUtils.processExits(contracts.withdrawManager, e20.rootERC20.address) + + // POL was received + utils.assertBigNumberEquality(await pol.balanceOf(accounts[1]), amount) + }) + }) +}) diff --git a/truffle-config.js b/truffle-config.js index 4f722cead..9196ba94f 100644 --- a/truffle-config.js +++ b/truffle-config.js @@ -47,7 +47,7 @@ module.exports = { MNEMONIC, `https://rpc-mumbai.matic.today` ), - network_id: '80001', + network_id: '80001' }, goerli: { provider: function() { From 116bed5571c9a51c0e8397dd611374ad0c7b687f Mon Sep 17 00:00:00 2001 From: Simon Dosch Date: Tue, 19 Sep 2023 09:52:35 +0200 Subject: [PATCH 03/19] gas optimisation --- .soliumrc.json | 2 +- .../root/depositManager/DepositManager.sol | 35 ++++++++++--------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/.soliumrc.json b/.soliumrc.json index 03d5f6097..264b4a602 100644 --- a/.soliumrc.json +++ b/.soliumrc.json @@ -14,7 +14,7 @@ "no-unused-vars": 1, "quotes": 1, "error-reason": 0, - "indentation": ["error", 2], + "indentation": ["error", 4], "arg-overflow": ["error", 8], "whitespace": 1, "deprecated-suicide": 1, diff --git a/contracts/root/depositManager/DepositManager.sol b/contracts/root/depositManager/DepositManager.sol index 6a3d088d1..7067691f1 100644 --- a/contracts/root/depositManager/DepositManager.sol +++ b/contracts/root/depositManager/DepositManager.sol @@ -18,14 +18,16 @@ interface IPolygonMigration { function migrate(uint256 amount) external; } - contract DepositManager is DepositManagerStorage, IDepositManager, ERC721Holder { using SafeMath for uint256; using SafeERC20 for IERC20; modifier isTokenMapped(address _token) { // new: exception for POL token - require(registry.isTokenMapped(_token) || _token == registry.contractMap(keccak256("pol")), "TOKEN_NOT_SUPPORTED"); + require( + registry.isTokenMapped(_token) || _token == registry.contractMap(keccak256("pol")), + "TOKEN_NOT_SUPPORTED" + ); _; } @@ -68,21 +70,21 @@ contract DepositManager is DepositManagerStorage, IDepositManager, ERC721Holder function transferAssets(address _token, address _user, uint256 _amountOrNFTId) external isPredicateAuthorized { address wethToken = registry.getWethTokenAddress(); - // so we don't assign to a function var - address memory token = _token; - - // new: pay out POL when MATIC is withdrawn - if (_token == registry.contractMap(keccak256("matic"))) { - token = registry.contractMap(keccak256("pol")); - } - - if (registry.isERC721(token)) { - IERC721(token).transferFrom(address(this), _user, _amountOrNFTId); - } else if (token == wethToken) { - WETH t = WETH(token); + if (registry.isERC721(_token)) { + IERC721(_token).transferFrom(address(this), _user, _amountOrNFTId); + } else if (_token == wethToken) { + WETH t = WETH(_token); t.withdraw(_amountOrNFTId, _user); } else { - require(IERC20(token).transfer(_user, _amountOrNFTId), "TRANSFER_FAILED"); + // new: pay out POL when MATIC is withdrawn + if (_token == registry.contractMap(keccak256("matic"))) { + require( + IERC20(registry.contractMap(keccak256("pol"))).transfer(_user, _amountOrNFTId), + "TRANSFER_FAILED" + ); + } else { + require(IERC20(_token).transfer(_user, _amountOrNFTId), "TRANSFER_FAILED"); + } } } @@ -167,9 +169,8 @@ contract DepositManager is DepositManagerStorage, IDepositManager, ERC721Holder if (_token == registry.contractMap(keccak256("matic"))) { _migrateMatic(_amountOrToken); } - // new: bridge POL as MATIC, child chain behaviour does not change - if(_token == registry.contractMap(keccak256("pol"))) { + else if (_token == registry.contractMap(keccak256("pol"))) { _token == registry.contractMap(keccak256("matic")); } From d90805de9c88cb60f3851842a21598e0555be6aa Mon Sep 17 00:00:00 2001 From: Simon Dosch Date: Tue, 19 Sep 2023 10:24:35 +0200 Subject: [PATCH 04/19] add DepositManager update script --- scripts/updateDepositManager.js | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 scripts/updateDepositManager.js diff --git a/scripts/updateDepositManager.js b/scripts/updateDepositManager.js new file mode 100644 index 000000000..2c37a92c0 --- /dev/null +++ b/scripts/updateDepositManager.js @@ -0,0 +1,34 @@ +const contractAddresses = require('../contractAddresses.json') + +const DepositManager = artifacts.require('DepositManager') +const DepositManagerProxy = artifacts.require('DepositManagerProxy') + +async function deployAndUpdateWM() { + const newDM = await DepositManager.new() + console.log('new DM deployed at: ', newDM.address) + const dmProxy = await DepositManagerProxy.at( + contractAddresses.root.DepositManagerProxy + ) + const result = await dmProxy.updateImplementation(newDM.address) + console.log('tx hash: ', result.tx) + + const impl = await dmProxy.implementation() + console.log('new impl: ', impl) +} + +module.exports = async function(callback) { + // args starts with index 6, example: first arg == process.args[6] + console.log(process.argv) + try { + const accounts = await web3.eth.getAccounts() + console.log( + 'Current configured address to make transactions:', + accounts[0] + ) + await deployAndUpdateWM() + } catch (e) { + // truffle exec