From 2c4a933a0e4e8637ae763f6b4ee82b2199b213af Mon Sep 17 00:00:00 2001 From: leovct Date: Fri, 1 Dec 2023 08:57:45 +0100 Subject: [PATCH 01/32] feat: POL token update --- .gitignore | 1 + .soliumrc.json | 2 +- contracts/common/tokens/POLTokenMock.sol | 56 +++++++ .../root/depositManager/DepositManager.sol | 80 +++++---- contracts/test/PolygonMigrationTest.sol | 34 ++++ package-lock.json | 24 +-- scripts/updateDepositManager.js | 34 ++++ test/helpers/artifacts.js | 2 + test/helpers/utils.js | 5 +- .../root/DepositManagerUpdate.test.js | 156 ++++++++++++++++++ 10 files changed, 350 insertions(+), 44 deletions(-) create mode 100644 contracts/common/tokens/POLTokenMock.sol create mode 100644 contracts/test/PolygonMigrationTest.sol create mode 100644 scripts/updateDepositManager.js create mode 100644 test/integration/root/DepositManagerUpdate.test.js diff --git a/.gitignore b/.gitignore index 13b1b7c9e..a18d6d1f6 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ test-blockchain/data *.log .DS_Store +.vscode *.env coverage/ 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/common/tokens/POLTokenMock.sol b/contracts/common/tokens/POLTokenMock.sol new file mode 100644 index 000000000..c666f98a5 --- /dev/null +++ b/contracts/common/tokens/POLTokenMock.sol @@ -0,0 +1,56 @@ +pragma solidity ^0.5.2; + +import "openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol"; +import "openzeppelin-solidity/contracts/utils/Address.sol"; + + +contract POLTokenMock is ERC20Mintable { + using Address for address; + + // detailed ERC20 + string public name; + string public symbol; + uint8 public decimals = 18; + + constructor(string memory _name, string memory _symbol) public { + name = _name; + symbol = _symbol; + + 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 b3be23ad2..4abb411f5 100644 --- a/contracts/root/depositManager/DepositManager.sol +++ b/contracts/root/depositManager/DepositManager.sol @@ -14,13 +14,20 @@ 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; 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" + ); _; } @@ -36,25 +43,48 @@ contract DepositManager is DepositManagerStorage, IDepositManager, ERC721Holder depositEther(); } + // new: governance function to migrate MATIC to POL + 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); } 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"); + } } } @@ -104,21 +134,14 @@ 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); + _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 +161,19 @@ 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 { + // 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 + else 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 b1283138d..6bae4d586 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11000,9 +11000,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.597", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.597.tgz", - "integrity": "sha512-0XOQNqHhg2YgRVRUrS4M4vWjFCFIP2ETXcXe/0KIQBjXE9Cpy+tgzzYfuq6HGai3hWq0YywtG+5XK8fyG08EjA==" + "version": "1.4.600", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.600.tgz", + "integrity": "sha512-KD6CWjf1BnQG+NsXuyiTDDT1eV13sKuYsOUioXkQweYTQIbgHkXPry9K7M+7cKtYHnSUPitVaLrXYB1jTkkYrw==" }, "node_modules/elliptic": { "version": "6.3.3", @@ -20190,9 +20190,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "node_modules/nofilter": { "version": "1.0.4", @@ -37073,9 +37073,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "electron-to-chromium": { - "version": "1.4.597", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.597.tgz", - "integrity": "sha512-0XOQNqHhg2YgRVRUrS4M4vWjFCFIP2ETXcXe/0KIQBjXE9Cpy+tgzzYfuq6HGai3hWq0YywtG+5XK8fyG08EjA==" + "version": "1.4.600", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.600.tgz", + "integrity": "sha512-KD6CWjf1BnQG+NsXuyiTDDT1eV13sKuYsOUioXkQweYTQIbgHkXPry9K7M+7cKtYHnSUPitVaLrXYB1jTkkYrw==" }, "elliptic": { "version": "6.3.3", @@ -44565,9 +44565,9 @@ } }, "node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "nofilter": { "version": "1.0.4", 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