├── .editorconfig ├── .env.example ├── .env.test.example ├── .gitattributes ├── .github └── workflows │ ├── ci.yaml │ └── wiki.yaml ├── .gitignore ├── .husky └── pre-commit ├── .openzeppelin ├── mainnet.json ├── polygon.json ├── sepolia.json └── unknown-80002.json ├── .prettierignore ├── .solcover.js ├── .solhint.json ├── .solhintignore ├── INTEGRATION.md ├── Makefile ├── README.md ├── contracts ├── ChildPool.sol ├── FxBaseChildTunnel.sol ├── FxBaseRootTunnel.sol ├── FxStateChildTunnel.sol ├── FxStateRootTunnel.sol ├── MaticX.sol ├── ProxyAdmin.sol ├── RateProvider.sol ├── TimelockController.sol ├── UChildERC20.sol ├── ValidatorRegistry.sol ├── interfaces │ ├── IChildPool.sol │ ├── IChildToken.sol │ ├── IFxStateChildTunnel.sol │ ├── IFxStateRootTunnel.sol │ ├── IMaticX.sol │ ├── IPolygonMigration.sol │ ├── IRateProvider.sol │ ├── IStakeManager.sol │ ├── IStakingInfo.sol │ ├── IValidatorRegistry.sol │ └── IValidatorShare.sol ├── libraries │ ├── ExitPayloadReader.sol │ ├── Merkle.sol │ ├── MerklePatriciaProof.sol │ └── RLPReader.sol └── mocks │ ├── ChildPoolMock.sol │ ├── ExtendedMaticXMock.sol │ ├── ExtendedValidatorRegistryMock.sol │ ├── FxRootMock.sol │ ├── MaticXFuzz.sol │ ├── MaticXMock.sol │ ├── PolygonMock.sol │ ├── StakeManagerMock.sol │ ├── ValidatorRegistryMock.sol │ └── ValidatorShareMock.sol ├── docs ├── Amoy-Testnet.md ├── Ethereum-Mainnet-(Preprod).md ├── Ethereum-Mainnet-(Prod).md ├── Home.md ├── Polygon-Mainnet-(Preprod).md ├── Polygon-Mainnet-(Prod).md └── Sepolia-Testnet.md ├── echidna.config.yaml ├── eslint.config.js ├── hardhat.config.ts ├── mainnet-deployment-info.json ├── package-lock.json ├── package.json ├── prettier.config.js ├── requirements-mythril.txt ├── requirements.txt ├── scripts ├── checkDeployIntegrity.ts ├── deploy.ts ├── deployers.ts ├── tasks.ts ├── types.ts ├── upgradeMaticX.ts ├── upgradeValidatorRegistry.ts └── utils.ts ├── slither.config.json ├── solc.json ├── tasks ├── deploy-child-pool.ts ├── deploy-fx-state-child-tunnel.ts ├── deploy-fx-state-root-tunnel.ts ├── deploy-implementation.ts ├── deploy-matic-x.ts ├── deploy-timelock-controller.ts ├── deploy-u-child-erc20.ts ├── deploy-validator-registry.ts ├── generate-initializev2-calldata-matic-x.ts ├── generate-initializev2-calldata-validator-registry.ts ├── import-contract.ts ├── index.ts ├── initialize-v2-matic-x.ts ├── initialize-v2-validator-registry.ts ├── upgrade-contract.ts ├── validate-child-deployment.ts ├── validate-parent-deployment.ts └── verify-contract.ts ├── test ├── ChildPool.ts ├── MaticX.ts └── ValidatorRegistry.ts ├── testnet-deployment-info.json ├── tsconfig.json └── utils ├── account.ts ├── environment.ts └── network.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | insert_final_newline = true 6 | end_of_line = lf 7 | indent_style = tab 8 | indent_size = 4 9 | max_line_length = 80 10 | 11 | [*.yaml] 12 | indent_size = 2 13 | 14 | [Makefile] 15 | indent_style = tab 16 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | RPC_PROVIDER=ankr 2 | SEPOLIA_API_KEY=1234567890ABCDEF1234567890abcdef 3 | AMOY_API_KEY=1234567890ABCDEF1234567890abcdef 4 | ETHEREUM_API_KEY=1234567890ABCDEF1234567890abcdef 5 | POLYGON_API_KEY=1234567890ABCDEF1234567890abcdef 6 | ETHERSCAN_API_KEY=1234567890ABCDEF1234567890abcdef12 7 | POLYGONSCAN_API_KEY=1234567890ABCDEF1234567890abcdef12 8 | FORKING_BLOCK_NUMBER=21578805 9 | COINMARKETCAP_API_KEY= 10 | GAS_REPORTER_NETWORK=ethereum 11 | GAS_PRICE_GWEI=0 12 | REPORT_GAS=false 13 | DEPLOYER_MNEMONIC="test test test test test test test test test test test junk" 14 | DEPLOYER_PASSPHRASE= 15 | DEPLOYER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 16 | -------------------------------------------------------------------------------- /.env.test.example: -------------------------------------------------------------------------------- 1 | DEPLOYER_PRIVATE_KEY=1234567890ABCDEF1234567890abcde1234567890ABCDEF1234567890abcdeff 2 | ETHERSCAN_API_KEY=1234567890ABCDEF1234567890abcdef12 3 | ROOT_CHAIN_RPC=https://rpc.ankr.com/eth 4 | ROOT_GAS_PRICE=0 5 | CHILD_CHAIN_RPC=https://rpc.ankr.com/polygon 6 | CHILD_GAS_PRICE=0 7 | STAKE_MANAGER= 8 | MATIC_TOKEN= 9 | MANAGER= 10 | INSTANT_POOL_OWNER= 11 | TREASURY= 12 | FX_ROOT= 13 | FX_CHILD= 14 | CHECKPOINT_MANAGER= 15 | DEFENDER_TEAM_API_KEY=1234567890ABCDEF1234567890abcdef 16 | DEFENDER_TEAM_API_SECRET_KEY=1234567890ABCDEF1234567890abcde1234567890ABCDEF1234567890abcdeff 17 | FORKING_ROOT_BLOCK_NUMBER=20700204 18 | REPORT_GAS=false 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - "**" 7 | jobs: 8 | ci: 9 | runs-on: ubuntu-latest 10 | env: 11 | ETHEREUM_API_KEY: ${{ secrets.ETHEREUM_API_KEY }} 12 | strategy: 13 | matrix: 14 | node-version: ["20.15.x"] 15 | python-version: ["3.12"] 16 | solidity-version: ["0.8.7"] 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v4 20 | 21 | - name: Setup node ${{ matrix.node-version }} 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | cache: npm 26 | 27 | - name: Setup python ${{ matrix.python-version }} 28 | uses: actions/setup-python@v5 29 | with: 30 | python-version: ${{ matrix.python-version }} 31 | check-latest: true 32 | cache: pip 33 | 34 | - name: Install node dependencies 35 | run: npm ci 36 | 37 | - name: Install python dependencies 38 | run: | 39 | pip install -U pip setuptools 40 | pip install -q -r requirements.txt 41 | 42 | - name: Configure solidity compiler 43 | run: | 44 | solc-select install ${{ matrix.solidity-version }} 45 | solc-select use ${{ matrix.solidity-version }} 46 | 47 | - name: Clean artifacts 48 | run: npm run clean 49 | 50 | - name: Compile contracts 51 | run: npm run compile 52 | 53 | - name: Lint code 54 | run: npm run lint 55 | 56 | - name: Check contracts with linter 57 | run: npm run check 58 | 59 | - name: Analyze contracts with static analyzer 60 | if: false 61 | run: npm run analyze:ci 62 | 63 | - name: Run unit tests 64 | run: npm run test:ci 65 | 66 | - name: Deploy contracts into hardhat network 67 | run: make hardhat 68 | 69 | - name: Save contract artifacts 70 | uses: actions/upload-artifact@v4 71 | with: 72 | name: contracts 73 | path: | 74 | artifacts/contracts/ 75 | !artifacts/contracts/**/*.dbg.json 76 | !artifacts/contracts/interfaces/ 77 | !artifacts/contracts/libraries/ 78 | !artifacts/contracts/mocks/ 79 | retention-days: 30 80 | -------------------------------------------------------------------------------- /.github/workflows/wiki.yaml: -------------------------------------------------------------------------------- 1 | name: Wiki 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - "**" 7 | jobs: 8 | wiki: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout repository 12 | uses: actions/checkout@v4 13 | with: 14 | path: maticX 15 | 16 | - name: Checkout wiki repository 17 | uses: actions/checkout@v4 18 | with: 19 | repository: stader-labs/maticX.wiki 20 | path: maticX.wiki 21 | 22 | - name: Mirror docs to wiki 23 | run: | 24 | cd $GITHUB_WORKSPACE/maticX.wiki 25 | git config user.name Actions 26 | git config user.email actions@users.noreply.github.com 27 | cp $GITHUB_WORKSPACE/maticX/docs/*.md $GITHUB_WORKSPACE/maticX.wiki 28 | git add . 29 | git diff-index --quiet HEAD -- || git commit -m "Mirror docs to wiki" 30 | git push 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | artifacts/ 4 | cache/ 5 | coverage/ 6 | crytic-corpus/ 7 | crytic-export/ 8 | node_modules/ 9 | typechain-types/ 10 | .DS_Store 11 | .env 12 | .env.test 13 | .solcx-lock-* 14 | coverage.json 15 | mochaOutput.json 16 | testMatrix.json 17 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npm run format && npm run lint && npm run check && npm test 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .openzeppelin/ 3 | .vscode/ 4 | artifacts/ 5 | cache/ 6 | contracts/ChildPool.sol 7 | contracts/interfaces/IChildPool.sol 8 | coverage/ 9 | crytic-corpus/ 10 | crytic-export/ 11 | node_modules/ 12 | typechain-types/ 13 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-undef 2 | module.exports = { 3 | configureYulOptimizer: true, 4 | network: "hardhat", 5 | skipFiles: ["interfaces/", "libraries/", "mocks/", "state-transfer/"], 6 | }; 7 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "avoid-call-value": "error", 6 | "avoid-low-level-calls": "error", 7 | "avoid-sha3": "error", 8 | "avoid-suicide": "error", 9 | "avoid-throw": "error", 10 | "avoid-tx-origin": "error", 11 | "check-send-result": "error", 12 | "code-complexity": ["error", 9], 13 | "compiler-version": ["error", "^0.8.7"], 14 | "comprehensive-interface": "off", 15 | "const-name-snakecase": "error", 16 | "constructor-syntax": "error", 17 | "contract-name-capwords": "error", 18 | "event-name-capwords": "error", 19 | "explicit-types": ["error", "explicit"], 20 | "foundry-test-functions": "off", 21 | "func-name-mixedcase": "off", 22 | "func-named-parameters": ["error", 12], 23 | "func-param-name-mixedcase": "error", 24 | "func-visibility": ["error", { "ignoreConstructors": true }], 25 | "function-max-lines": ["error", 75], 26 | "gas-calldata-parameters": "off", 27 | "gas-custom-errors": "off", 28 | "gas-increment-by-one": "error", 29 | "gas-indexed-events": "off", 30 | "gas-length-in-loops": "error", 31 | "gas-multitoken1155": "error", 32 | "gas-named-return-values": "off", 33 | "gas-small-strings": "off", 34 | "gas-strict-inequalities": "off", 35 | "gas-struct-packing": "error", 36 | "immutable-vars-naming": ["error", { "immutablesAsConstants": false }], 37 | "interface-starts-with-i": "error", 38 | "imports-on-top": "error", 39 | "imports-order": "off", 40 | "mark-callable-contracts": "off", 41 | "max-line-length": ["error", 160], 42 | "max-states-count": ["error", 15], 43 | "modifier-name-mixedcase": "error", 44 | "multiple-sends": "error", 45 | "named-parameters-mapping": "off", 46 | "no-complex-fallback": "error", 47 | "no-console": "error", 48 | "no-empty-blocks": "error", 49 | "no-global-import": "off", 50 | "no-inline-assembly": "off", 51 | "no-unused-import": "error", 52 | "no-unused-vars": "warn", 53 | "not-rely-on-block-hash": "error", 54 | "not-rely-on-time": "off", 55 | "one-contract-per-file": "error", 56 | "ordering": "off", 57 | "payable-fallback": "error", 58 | "prettier/prettier": "error", 59 | "private-vars-leading-underscore": "off", 60 | "quotes": ["error", "double"], 61 | "reason-string": ["error", { "maxLength": 80 }], 62 | "reentrancy": "error", 63 | "state-visibility": "error", 64 | "use-forbidden-name": "error", 65 | "var-name-mixedcase": "off", 66 | "visibility-modifier-order": "error" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .openzeppelin/ 3 | .vscode/ 4 | artifacts/ 5 | cache/ 6 | coverage/ 7 | contracts/ChildPool.sol 8 | contracts/FxBaseRootTunnel.sol 9 | contracts/interfaces/IChildPool.sol 10 | contracts/libraries/ 11 | contracts/mocks/ 12 | crytic-corpus/ 13 | crytic-export/ 14 | node_modules/ 15 | typechain-types/ 16 | -------------------------------------------------------------------------------- /INTEGRATION.md: -------------------------------------------------------------------------------- 1 | # Integration guide 2 | 3 | Deployment addresses can be found at: 4 | 5 | - Mainnet: [mainnet-deployment-info.json](mainnet-deployment-info.json) 6 | - Testnet: [testnet-deployment-info.json](testnet-deployment-info.json) 7 | 8 | ## Ethereum 9 | 10 | Liquid staking is achieved through `MaticX` contract and the yield-bearing ERC-20 token `MaticX` is given to the user. 11 | 12 | ### 1. Stake Matic on Ethereum 13 | 14 | Send Matic and receive liquid staking MaticX token. 15 | _MaticX approval should be given prior._ 16 | 17 | ```SOLIDITY 18 | IMatic matic = IMatic(MATIC_ADDRESS); 19 | IMaticX maticX = IMaticX(MATICX_ADDRESS); 20 | require(matic.approve(MATICX_ADDRESS, _amountInMatic), "Not approved"); 21 | uint256 amountInMaticX = maticX.submit(_amountInMatic); 22 | 23 | emit StakeEvent(msg.sender, msg.value, amountInMaticX); 24 | ``` 25 | 26 | ### 2. Unstake Matic on Ethereum 27 | 28 | Send MaticX and create a withdrawal request. 29 | _MaticX approval should be given prior._ 30 | 31 | ```SOLIDITY 32 | IMaticX maticX = IMaticX(MATICX_ADDRESS); 33 | require( 34 | maticX.approve(MATICX_ADDRESS, _amountInMaticX), 35 | "Not approved" 36 | ); 37 | maticX.requestWithdraw(_amountInMaticX); 38 | 39 | emit UnstakeEvent(msg.sender, _amountInMaticX); 40 | ``` 41 | 42 | ### 3. Claim Matic on Ethereum 43 | 44 | After 3-4 days (80 checkpoints), Matic can be withdrawn. 45 | 46 | ```SOLIDITY 47 | IMatic matic = IMatic(MATIC_ADDRESS); 48 | IMaticX maticX = IMaticX(MATICX_ADDRESS); 49 | 50 | // Claim all available withdrawal requests 51 | WithdrawalRequest[] memory requests = getUserWithdrawalRequests( 52 | msg.sender 53 | ); 54 | 55 | // StakeManager is necessary to check the availability of the withdrawal request 56 | IStakeManager stakeManager = IStakeManager(STAKEMANAGER_ADDRESS); 57 | // Important: Looping from the beginning doesn't work due to 58 | // non-shifting removal from the withdrawal request array. 59 | for (uint256 idx = requests.length - 1; idx >= 0; idx--) { 60 | WithdrawalRequest request = requests[idx].amount; 61 | if (stakeManager.epoch() < request.requestEpoch) continue; 62 | 63 | uint256 amountInMaticBefore = matic.balanceOf(msg.sender); 64 | // Swaps the given index with the latest item and reduces the size. 65 | // . V . . 66 | // 6 1 4 9 Original array 67 | // 6 9 4 9 Swapping with the latest item 68 | // 6 9 4 Final array 69 | maticX.claimWithdrawal(idx); 70 | uint256 amountInMaticAfter = matic.balanceOf(msg.sender); 71 | 72 | emit ClaimEvent( 73 | msg.sender, 74 | amountInMaticAfter - amountInMaticBefore 75 | ); 76 | } 77 | ``` 78 | 79 | ### Full example on Ethereum 80 | 81 | ```SOLIDITY 82 | pragma solidity ^0.8.0; 83 | 84 | import "IMatic.sol"; 85 | import "IMaticX.sol"; 86 | 87 | contract Example { 88 | event StakeEvent( 89 | address indexed _address, 90 | uint256 amountInMatic, 91 | uint256 amountInMaticX 92 | ); 93 | event UnstakeEvent(address indexed _address, uint256 amountInMaticX); 94 | event ClaimEvent(address indexed _address, uint256 amountInMatic); 95 | 96 | address private MATIC_ADDRESS = 97 | "0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0"; //mainnet address 98 | address private MATICX_ADDRESS = 99 | "0xf03A7Eb46d01d9EcAA104558C732Cf82f6B6B645"; //mainnet address 100 | address private STAKEMANAGER_ADDRESS = 101 | "0x5e3Ef299fDDf15eAa0432E6e66473ace8c13D908"; //mainnet address 102 | 103 | function stake(uint256 _amountInMatic) external { 104 | IMatic matic = IMatic(MATIC_ADDRESS); 105 | IMaticX maticX = IMaticX(MATICX_ADDRESS); 106 | require(matic.approve(MATICX_ADDRESS, _amountInMatic), "Not approved"); 107 | uint256 amountInMaticX = maticX.submit(_amountInMatic); 108 | 109 | emit StakeEvent(msg.sender, msg.value, amountInMaticX); 110 | } 111 | 112 | function unstake(uint256 _amountInMaticX) external { 113 | IMaticX maticX = IMaticX(MATICX_ADDRESS); 114 | require( 115 | maticX.approve(MATICX_ADDRESS, _amountInMaticX), 116 | "Not approved" 117 | ); 118 | maticX.requestWithdraw(_amountInMaticX); 119 | 120 | emit UnstakeEvent(msg.sender, _amountInMaticX); 121 | } 122 | 123 | function claim() external { 124 | IMatic matic = IMatic(MATIC_ADDRESS); 125 | IMaticX maticX = IMaticX(MATICX_ADDRESS); 126 | 127 | // Claim all available withdrawal requests 128 | WithdrawalRequest[] memory requests = getUserWithdrawalRequests( 129 | msg.sender 130 | ); 131 | 132 | // StakeManager is necessary to check the availability of the withdrawal request 133 | IStakeManager stakeManager = IStakeManager(STAKEMANAGER_ADDRESS); 134 | // Important: Looping from the beginning doesn't work due to 135 | // non-shifting removal from the withdrawal request array. 136 | for (uint256 idx = requests.length - 1; idx >= 0; idx--) { 137 | WithdrawalRequest request = requests[idx].amount; 138 | if (stakeManager.epoch() < request.requestEpoch) continue; 139 | 140 | uint256 amountInMaticBefore = matic.balanceOf(msg.sender); 141 | // Swaps the given index with the latest item and reduces the size. 142 | // . V . . 143 | // 6 1 4 9 Original array 144 | // 6 9 4 9 Swapping with the latest item 145 | // 6 9 4 Final array 146 | maticX.claimWithdrawal(idx); 147 | uint256 amountInMaticAfter = matic.balanceOf(msg.sender); 148 | 149 | emit ClaimEvent( 150 | msg.sender, 151 | amountInMaticAfter - amountInMaticBefore 152 | ); 153 | } 154 | } 155 | } 156 | ``` 157 | 158 | ## Polygon 159 | 160 | Liquid staking is achieved through `ChildPool` contract and the yield-bearing ERC-20 token `MaticX` is given to the user. 161 | 162 | ### 1. Stake Matic on Polygon 163 | 164 | Send Matic and receive liquid staking MaticX token. 165 | _There should be enough MaticX token in the pool_ 166 | 167 | ```SOLIDITY 168 | IMaticX maticX = IMaticX(MATICX_ADDRESS); 169 | IChildPool childPool = IChildPool(CHILDPOOL_ADDRESS); 170 | 171 | // Check the liquidity of the pool 172 | uint256 availableMaticXAmount = childPool.instantPoolMaticX(); 173 | uint256 expectedMaticXAmount = childPool.convertMaticToMaticX( 174 | msg.value 175 | ); 176 | require( 177 | availableMaticXAmount >= expectedMaticXAmount, 178 | "Not enough MaticX" 179 | ); 180 | 181 | childPool.swapMaticForMaticXViaInstantPool{value: msg.value}(); 182 | uint256 amountInMaticX = maticX.balanceOf(msg.sender); 183 | 184 | emit StakeEvent(msg.sender, msg.value, amountInMaticX); 185 | ``` 186 | 187 | ### 2. Unstake Matic on Polygon 188 | 189 | Send MaticX and create a withdrawal request. 190 | _MaticX approval should be given prior._ 191 | _There should be enough Matic token in the pool_ 192 | 193 | ```SOLIDITY 194 | IMaticX maticX = IMaticX(MATICX_ADDRESS); 195 | IChildPool childPool = IChildPool(CHILDPOOL_ADDRESS); 196 | require( 197 | maticX.approve(CHILDPOOL_ADDRESS, _amountInMaticX), 198 | "Not approved" 199 | ); 200 | 201 | // Check the liquidity of the pool 202 | uint256 availableMaticAmount = childPool.instantPoolMatic(); 203 | uint256 expectedMaticAmount = childPool.convertMaticXToMatic( 204 | _amountInMaticX 205 | ); 206 | require( 207 | availableMaticAmount >= expectedMaticAmount, 208 | "Not enough Matic" 209 | ); 210 | 211 | childPool.requestMaticXSwap(_amountInMaticX); 212 | 213 | emit UnstakeEvent(msg.sender, _amountInMaticX); 214 | ``` 215 | 216 | ### 3. Claim Matic on Polygon 217 | 218 | After 3-4 days (80 checkpoints), Matic can be withdrawn. 219 | 220 | ```SOLIDITY 221 | IChildPool childPool = IChildPool(CHILDPOOL_ADDRESS); 222 | 223 | // Claim all available withdrawal requests 224 | WithdrawalRequest[] memory requests = getUserMaticXSwapRequests( 225 | msg.sender 226 | ); 227 | // Important: Looping from the beginning doesn't work due to 228 | // non-shifting removal from the withdrawal request array. 229 | for (uint256 idx = requests.length - 1; idx >= 0; idx--) { 230 | WithdrawalRequest request = requests[idx].amount; 231 | if (block.timestamp < request.withdrawalTime) continue; 232 | 233 | uint256 amountInMatic = request.amount; 234 | // Swaps the given index with the latest item and reduces the size. 235 | // . V . . 236 | // 6 1 4 9 Original array 237 | // 6 9 4 9 Swapping with the latest item 238 | // 6 9 4 Final array 239 | childPool.claimMaticXSwap(idx); 240 | 241 | emit ClaimEvent(msg.sender, amountInMatic); 242 | } 243 | ``` 244 | 245 | ### Full example on Polygon 246 | 247 | ```SOLIDITY 248 | pragma solidity ^0.8.0; 249 | 250 | import "ChildPool.sol"; 251 | import "IMaticX.sol"; 252 | 253 | contract Example { 254 | event StakeEvent( 255 | address indexed _address, 256 | uint256 amountInMatic, 257 | uint256 amountInMaticX 258 | ); 259 | event UnstakeEvent(address indexed _address, uint256 amountInMaticX); 260 | event ClaimEvent(address indexed _address, uint256 amountInMatic); 261 | 262 | address private CHILDPOOL_ADDRESS = 263 | "0xfd225C9e6601C9d38d8F98d8731BF59eFcF8C0E3"; //mainnet address 264 | address private MATICX_ADDRESS = 265 | "0xfa68fb4628dff1028cfec22b4162fccd0d45efb6"; //mainnet address 266 | 267 | function stake(uint256 _amountInMatic) external { 268 | IMaticX maticX = IMaticX(MATICX_ADDRESS); 269 | IChildPool childPool = IChildPool(CHILDPOOL_ADDRESS); 270 | 271 | // Check the liquidity of the pool 272 | uint256 availableMaticXAmount = childPool.instantPoolMaticX(); 273 | uint256 expectedMaticXAmount = childPool.convertMaticToMaticX( 274 | msg.value 275 | ); 276 | require( 277 | availableMaticXAmount >= expectedMaticXAmount, 278 | "Not enough MaticX" 279 | ); 280 | 281 | childPool.swapMaticForMaticXViaInstantPool{value: msg.value}(); 282 | uint256 amountInMaticX = maticX.balanceOf(msg.sender); 283 | 284 | emit StakeEvent(msg.sender, msg.value, amountInMaticX); 285 | } 286 | 287 | function unstake(uint256 _amountInMaticX) external { 288 | IMaticX maticX = IMaticX(MATICX_ADDRESS); 289 | IChildPool childPool = IChildPool(CHILDPOOL_ADDRESS); 290 | require( 291 | maticX.approve(CHILDPOOL_ADDRESS, _amountInMaticX), 292 | "Not approved" 293 | ); 294 | 295 | // Check the liquidity of the pool 296 | uint256 availableMaticAmount = childPool.instantPoolMatic(); 297 | uint256 expectedMaticAmount = childPool.convertMaticXToMatic( 298 | _amountInMaticX 299 | ); 300 | require( 301 | availableMaticAmount >= expectedMaticAmount, 302 | "Not enough Matic" 303 | ); 304 | 305 | childPool.requestMaticXSwap(_amountInMaticX); 306 | 307 | emit UnstakeEvent(msg.sender, _amountInMaticX); 308 | } 309 | 310 | function claim() external { 311 | IChildPool childPool = IChildPool(CHILDPOOL_ADDRESS); 312 | 313 | // Claim all available withdrawal requests 314 | WithdrawalRequest[] memory requests = getUserMaticXSwapRequests( 315 | msg.sender 316 | ); 317 | // Important: Looping from the beginning doesn't work due to 318 | // non-shifting removal from the withdrawal request array. 319 | for (uint256 idx = requests.length - 1; idx >= 0; idx--) { 320 | WithdrawalRequest request = requests[idx].amount; 321 | if (block.timestamp < request.withdrawalTime) continue; 322 | 323 | uint256 amountInMatic = request.amount; 324 | // Swaps the given index with the latest item and reduces the size. 325 | // . V . . 326 | // 6 1 4 9 Original array 327 | // 6 9 4 9 Swapping with the latest item 328 | // 6 9 4 Final array 329 | childPool.claimMaticXSwap(idx); 330 | 331 | emit ClaimEvent(msg.sender, amountInMatic); 332 | } 333 | } 334 | } 335 | ``` 336 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MaticX 2 | 3 | ## Github Workflows 4 | 5 | - ![CI](https://github.com/stader-labs/maticX/actions/workflows/ci.yaml/badge.svg) 6 | - ![Wiki](https://github.com/stader-labs/maticX/actions/workflows/wiki.yaml/badge.svg) 7 | 8 | ## Configuration 9 | 10 | Before deploying check out the `.env.test.example` file. You should create your own `.env.test` file. 11 | 12 | ```bash 13 | DEPLOYER_PRIVATE_KEY= 14 | ETHERSCAN_API_KEY= 15 | ROOT_CHAIN_RPC= 16 | ROOT_GAS_PRICE= 17 | CHILD_CHAIN_RPC= 18 | CHILD_GAS_PRICE= 19 | STAKE_MANAGER= 20 | MATIC_TOKEN=
21 | MANAGER=
22 | TREASURY=
23 | FX_ROOT= 24 | FX_CHILD= 25 | CHECKPOINT_MANAGER= 26 | ``` 27 | 28 | ## Deployment 29 | 30 | To deploy on testnet run: 31 | 32 | ```bash 33 | npm run deploy:test 34 | ``` 35 | 36 | To deploy on mainnet run: 37 | 38 | ```bash 39 | npm run deploy:main 40 | ``` 41 | 42 | To deploy contract directly, run: 43 | 44 | ```bash 45 | npx hardhat deployFxStateChildTunnel --network matic 46 | npx hardhat deployRateProvider --network matic 47 | npx hardhat deployChildPoolImpl --network matic 48 | npx hardhat deployChildPoolProxy --network matic 49 | npx hardhat deployFxStateRootTunnel --network mainnet 50 | npx hardhat deployMaticXImpl --network mainnet 51 | npx hardhat deployValidatorRegistryImpl --network mainnet 52 | ``` 53 | 54 | ## Upgrade 55 | 56 | ```bash 57 | npx hardhat run ./scripts/upgradeMaticX.ts --network 58 | npx hardhat run ./scripts/upgradeValidatorRegistry.ts --network 59 | ``` 60 | 61 | ## Verification on Etherscan 62 | 63 | ```bash 64 | npx hardhat verifyMaticX --network 65 | npx hardhat verify
<...args> --network 66 | ``` 67 | 68 | ## Test 69 | 70 | ```bash 71 | npx hardhat test 72 | ``` 73 | 74 | ## Integration 75 | 76 | Smart contract integration guide is at [link](INTEGRATION.md) 77 | -------------------------------------------------------------------------------- /contracts/FxBaseChildTunnel.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | // IFxMessageProcessor represents interface to process message 5 | interface IFxMessageProcessor { 6 | function processMessageFromRoot( 7 | uint256 stateId, 8 | address rootMessageSender, 9 | bytes calldata data 10 | ) external; 11 | } 12 | 13 | /** 14 | * @notice Mock child tunnel contract to receive and send message from L2 15 | */ 16 | abstract contract FxBaseChildTunnel is IFxMessageProcessor { 17 | // MessageTunnel on L1 will get data from this event 18 | event MessageSent(bytes message); 19 | 20 | // fx child 21 | address public fxChild; 22 | 23 | // fx root tunnel 24 | address public fxRootTunnel; 25 | 26 | constructor(address _fxChild) { 27 | fxChild = _fxChild; 28 | } 29 | 30 | // Sender must be fxRootTunnel in case of ERC20 tunnel 31 | modifier validateSender(address sender) { 32 | require( 33 | sender == fxRootTunnel, 34 | "FxBaseChildTunnel: INVALID_SENDER_FROM_ROOT" 35 | ); 36 | _; 37 | } 38 | 39 | function setFxRootTunnel(address _fxRootTunnel) external virtual; 40 | 41 | function processMessageFromRoot( 42 | uint256 stateId, 43 | address rootMessageSender, 44 | bytes calldata data 45 | ) external override { 46 | require(msg.sender == fxChild, "FxBaseChildTunnel: INVALID_SENDER"); 47 | _processMessageFromRoot(stateId, rootMessageSender, data); 48 | } 49 | 50 | /** 51 | * @notice Emit message that can be received on Root Tunnel 52 | * @dev Call the internal function when need to emit message 53 | * @param message bytes message that will be sent to Root Tunnel 54 | * some message examples - 55 | * abi.encode(tokenId); 56 | * abi.encode(tokenId, tokenMetadata); 57 | * abi.encode(messageType, messageData); 58 | */ 59 | function _sendMessageToRoot(bytes memory message) internal { 60 | emit MessageSent(message); 61 | } 62 | 63 | /** 64 | * @notice Process message received from Root Tunnel 65 | * @dev function needs to be implemented to handle message as per requirement 66 | * This is called by onStateReceive function. 67 | * Since it is called via a system call, any event will not be emitted during its execution. 68 | * @param stateId unique state id 69 | * @param sender root message sender 70 | * @param message bytes message that was sent from Root Tunnel 71 | */ 72 | function _processMessageFromRoot( 73 | uint256 stateId, 74 | address sender, 75 | bytes memory message 76 | ) internal virtual; 77 | } 78 | -------------------------------------------------------------------------------- /contracts/FxBaseRootTunnel.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | import { RLPReader } from "./libraries/RLPReader.sol"; 5 | import { MerklePatriciaProof } from "./libraries/MerklePatriciaProof.sol"; 6 | import { Merkle } from "./libraries/Merkle.sol"; 7 | import { ExitPayloadReader } from "./libraries/ExitPayloadReader.sol"; 8 | 9 | interface IFxStateSender { 10 | function sendMessageToChild( 11 | address _receiver, 12 | bytes calldata _data 13 | ) external; 14 | } 15 | 16 | contract ICheckpointManager { 17 | struct HeaderBlock { 18 | bytes32 root; 19 | uint256 start; 20 | uint256 end; 21 | uint256 createdAt; 22 | address proposer; 23 | } 24 | 25 | /** 26 | * @notice mapping of checkpoint header numbers to block details 27 | * @dev These checkpoints are submited by plasma contracts 28 | */ 29 | mapping(uint256 => HeaderBlock) public headerBlocks; 30 | } 31 | 32 | abstract contract FxBaseRootTunnel { 33 | using RLPReader for RLPReader.RLPItem; 34 | using Merkle for bytes32; 35 | using ExitPayloadReader for bytes; 36 | using ExitPayloadReader for ExitPayloadReader.ExitPayload; 37 | using ExitPayloadReader for ExitPayloadReader.Log; 38 | using ExitPayloadReader for ExitPayloadReader.LogTopics; 39 | using ExitPayloadReader for ExitPayloadReader.Receipt; 40 | 41 | // keccak256(MessageSent(bytes)) 42 | bytes32 public constant SEND_MESSAGE_EVENT_SIG = 43 | 0x8c5261668696ce22758910d05bab8f186d6eb247ceac2af2e82c7dc17669b036; 44 | 45 | // state sender contract 46 | IFxStateSender public fxRoot; 47 | // root chain manager 48 | ICheckpointManager public checkpointManager; 49 | // child tunnel contract which receives and sends messages 50 | address public fxChildTunnel; 51 | 52 | // storage to avoid duplicate exits 53 | mapping(bytes32 => bool) public processedExits; 54 | 55 | constructor(address _checkpointManager, address _fxRoot) { 56 | checkpointManager = ICheckpointManager(_checkpointManager); 57 | fxRoot = IFxStateSender(_fxRoot); 58 | } 59 | 60 | function setFxChildTunnel(address _fxChildTunnel) external virtual; 61 | 62 | /** 63 | * @notice Send bytes message to Child Tunnel 64 | * @param message bytes message that will be sent to Child Tunnel 65 | * some message examples - 66 | * abi.encode(tokenId); 67 | * abi.encode(tokenId, tokenMetadata); 68 | * abi.encode(messageType, messageData); 69 | */ 70 | function _sendMessageToChild(bytes memory message) internal { 71 | fxRoot.sendMessageToChild(fxChildTunnel, message); 72 | } 73 | 74 | function _validateAndExtractMessage( 75 | bytes memory inputData 76 | ) internal returns (bytes memory) { 77 | ExitPayloadReader.ExitPayload memory payload = inputData 78 | .toExitPayload(); 79 | 80 | bytes memory branchMaskBytes = payload.getBranchMaskAsBytes(); 81 | uint256 blockNumber = payload.getBlockNumber(); 82 | // checking if exit has already been processed 83 | // unique exit is identified using hash of (blockNumber, branchMask, receiptLogIndex) 84 | bytes32 exitHash = keccak256( 85 | abi.encodePacked( 86 | blockNumber, 87 | // first 2 nibbles are dropped while generating nibble array 88 | // this allows branch masks that are valid but bypass exitHash check (changing first 2 nibbles only) 89 | // so converting to nibble array and then hashing it 90 | MerklePatriciaProof._getNibbleArray(branchMaskBytes), 91 | payload.getReceiptLogIndex() 92 | ) 93 | ); 94 | require( 95 | processedExits[exitHash] == false, 96 | "FxRootTunnel: EXIT_ALREADY_PROCESSED" 97 | ); 98 | processedExits[exitHash] = true; 99 | 100 | ExitPayloadReader.Receipt memory receipt = payload.getReceipt(); 101 | ExitPayloadReader.Log memory log = receipt.getLog(); 102 | 103 | // check child tunnel 104 | require( 105 | fxChildTunnel == log.getEmitter(), 106 | "FxRootTunnel: INVALID_FX_CHILD_TUNNEL" 107 | ); 108 | 109 | bytes32 receiptRoot = payload.getReceiptRoot(); 110 | // verify receipt inclusion 111 | require( 112 | MerklePatriciaProof.verify( 113 | receipt.toBytes(), 114 | branchMaskBytes, 115 | payload.getReceiptProof(), 116 | receiptRoot 117 | ), 118 | "FxRootTunnel: INVALID_RECEIPT_PROOF" 119 | ); 120 | 121 | // verify checkpoint inclusion 122 | _checkBlockMembershipInCheckpoint( 123 | blockNumber, 124 | payload.getBlockTime(), 125 | payload.getTxRoot(), 126 | receiptRoot, 127 | payload.getHeaderNumber(), 128 | payload.getBlockProof() 129 | ); 130 | 131 | ExitPayloadReader.LogTopics memory topics = log.getTopics(); 132 | 133 | require( 134 | bytes32(topics.getField(0).toUint()) == SEND_MESSAGE_EVENT_SIG, // topic0 is event sig 135 | "FxRootTunnel: INVALID_SIGNATURE" 136 | ); 137 | 138 | // received message data 139 | bytes memory message = abi.decode(log.getData(), (bytes)); // event decodes params again, so decoding bytes to get message 140 | return message; 141 | } 142 | 143 | function _checkBlockMembershipInCheckpoint( 144 | uint256 blockNumber, 145 | uint256 blockTime, 146 | bytes32 txRoot, 147 | bytes32 receiptRoot, 148 | uint256 headerNumber, 149 | bytes memory blockProof 150 | ) private view returns (uint256) { 151 | ( 152 | bytes32 headerRoot, 153 | uint256 startBlock, 154 | , 155 | uint256 createdAt, 156 | 157 | ) = checkpointManager.headerBlocks(headerNumber); 158 | 159 | require( 160 | keccak256( 161 | abi.encodePacked(blockNumber, blockTime, txRoot, receiptRoot) 162 | ).checkMembership(blockNumber - startBlock, headerRoot, blockProof), 163 | "FxRootTunnel: INVALID_HEADER" 164 | ); 165 | return createdAt; 166 | } 167 | 168 | /** 169 | * @notice receive message from L2 to L1, validated by proof 170 | * @dev This function verifies if the transaction actually happened on child chain 171 | * 172 | * @param inputData RLP encoded data of the reference tx containing following list of fields 173 | * 0 - headerNumber - Checkpoint header block number containing the reference tx 174 | * 1 - blockProof - Proof that the block header (in the child chain) is a leaf in the submitted merkle root 175 | * 2 - blockNumber - Block number containing the reference tx on child chain 176 | * 3 - blockTime - Reference tx block time 177 | * 4 - txRoot - Transactions root of block 178 | * 5 - receiptRoot - Receipts root of block 179 | * 6 - receipt - Receipt of the reference transaction 180 | * 7 - receiptProof - Merkle proof of the reference receipt 181 | * 8 - branchMask - 32 bits denoting the path of receipt in merkle tree 182 | * 9 - receiptLogIndex - Log Index to read from the receipt 183 | */ 184 | function receiveMessage(bytes memory inputData) public virtual { 185 | bytes memory message = _validateAndExtractMessage(inputData); 186 | _processMessageFromChild(message); 187 | } 188 | 189 | /** 190 | * @notice Process message received from Child Tunnel 191 | * @dev function needs to be implemented to handle message as per requirement 192 | * This is called by onStateReceive function. 193 | * Since it is called via a system call, any event will not be emitted during its execution. 194 | * @param message bytes message that was sent from Child Tunnel 195 | */ 196 | function _processMessageFromChild(bytes memory message) internal virtual; 197 | } 198 | -------------------------------------------------------------------------------- /contracts/FxStateChildTunnel.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol"; 5 | import { FxBaseChildTunnel } from "./FxBaseChildTunnel.sol"; 6 | 7 | /** 8 | * @title FxStateChildTunnel 9 | */ 10 | contract FxStateChildTunnel is FxBaseChildTunnel, AccessControl { 11 | uint256 public latestStateId; 12 | address public latestRootMessageSender; 13 | bytes public latestData; 14 | 15 | constructor(address _fxChild) FxBaseChildTunnel(_fxChild) { 16 | _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); 17 | } 18 | 19 | function setFxRootTunnel( 20 | address _fxRootTunnel 21 | ) external override onlyRole(DEFAULT_ADMIN_ROLE) { 22 | fxRootTunnel = _fxRootTunnel; 23 | } 24 | 25 | function _processMessageFromRoot( 26 | uint256 stateId, 27 | address sender, 28 | bytes memory data 29 | ) internal override validateSender(sender) { 30 | latestStateId = stateId; 31 | latestRootMessageSender = sender; 32 | latestData = data; 33 | } 34 | 35 | /** 36 | * @dev Function that returns the amount of MaticX and MATIC 37 | * @return First return value is the number of MaticX present, second value is MATIC 38 | */ 39 | function getReserves() public view returns (uint256, uint256) { 40 | (uint256 maticX, uint256 MATIC) = abi.decode( 41 | latestData, 42 | (uint256, uint256) 43 | ); 44 | 45 | return (maticX, MATIC); 46 | } 47 | 48 | function getRate() external view returns (uint256) { 49 | (uint256 balanceInMATIC, , ) = convertMaticXToMatic(1 ether); 50 | return balanceInMATIC; 51 | } 52 | 53 | function convertMaticXToMatic( 54 | uint256 _balance 55 | ) public view returns (uint256, uint256, uint256) { 56 | (uint256 maticX, uint256 matic) = getReserves(); 57 | maticX = maticX == 0 ? 1 : maticX; 58 | matic = matic == 0 ? 1 : matic; 59 | 60 | uint256 balanceInMATIC = (_balance * matic) / maticX; 61 | 62 | return (balanceInMATIC, maticX, matic); 63 | } 64 | 65 | function convertMaticToMaticX( 66 | uint256 _balance 67 | ) public view returns (uint256, uint256, uint256) { 68 | (uint256 maticX, uint256 matic) = getReserves(); 69 | maticX = maticX == 0 ? 1 : maticX; 70 | matic = matic == 0 ? 1 : matic; 71 | 72 | uint256 balanceInMaticX = (_balance * maticX) / matic; 73 | 74 | return (balanceInMaticX, maticX, matic); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /contracts/FxStateRootTunnel.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol"; 5 | import { FxBaseRootTunnel } from "./FxBaseRootTunnel.sol"; 6 | 7 | /** 8 | * @title FxStateRootTunnel 9 | */ 10 | contract FxStateRootTunnel is FxBaseRootTunnel, AccessControl { 11 | bytes public latestData; 12 | address public maticX; 13 | 14 | constructor( 15 | address _checkpointManager, 16 | address _fxRoot, 17 | address _maticX 18 | ) FxBaseRootTunnel(_checkpointManager, _fxRoot) { 19 | _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); 20 | 21 | maticX = _maticX; 22 | } 23 | 24 | function _processMessageFromChild(bytes memory data) internal override { 25 | latestData = data; 26 | } 27 | 28 | function sendMessageToChild(bytes memory message) public { 29 | require(msg.sender == maticX, "Not maticX"); 30 | _sendMessageToChild(message); 31 | } 32 | 33 | function setMaticX(address _maticX) external onlyRole(DEFAULT_ADMIN_ROLE) { 34 | maticX = _maticX; 35 | } 36 | 37 | function setFxChildTunnel( 38 | address _fxChildTunnel 39 | ) external override onlyRole(DEFAULT_ADMIN_ROLE) { 40 | fxChildTunnel = _fxChildTunnel; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /contracts/ProxyAdmin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.7; 3 | 4 | import { ProxyAdmin as OpenzeppelinProxyAdmin } from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; 5 | 6 | /** 7 | * @title ProxyAdmin 8 | */ 9 | // solhint-disable-next-line no-empty-blocks 10 | contract ProxyAdmin is OpenzeppelinProxyAdmin {} 11 | -------------------------------------------------------------------------------- /contracts/RateProvider.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol"; 5 | import { IRateProvider } from "./interfaces/IRateProvider.sol"; 6 | import { IFxStateChildTunnel } from "./interfaces/IFxStateChildTunnel.sol"; 7 | 8 | /** 9 | * @title RateProvider 10 | */ 11 | contract RateProvider is IRateProvider, AccessControl { 12 | address public override fxChild; 13 | 14 | constructor(address _fxChild) { 15 | _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); 16 | 17 | fxChild = _fxChild; 18 | } 19 | 20 | function getRate() external view override returns (uint256) { 21 | return IFxStateChildTunnel(fxChild).getRate(); 22 | } 23 | 24 | function setFxChild( 25 | address _fxChild 26 | ) external override onlyRole(DEFAULT_ADMIN_ROLE) { 27 | fxChild = _fxChild; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /contracts/TimelockController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | import { TimelockController as OpenzeppelinTimelockController } from "@openzeppelin/contracts/governance/TimelockController.sol"; 5 | 6 | contract TimelockController is OpenzeppelinTimelockController { 7 | constructor( 8 | uint256 minDelay, 9 | address[] memory proposers, 10 | address[] memory executors, 11 | address admin 12 | ) OpenzeppelinTimelockController(minDelay, proposers, executors, admin) {} 13 | } 14 | -------------------------------------------------------------------------------- /contracts/UChildERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.8; 3 | 4 | import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 5 | import { ContextUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; 6 | import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; 7 | import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; 8 | import { ERC20PermitUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; 9 | import { IChildToken } from "./interfaces/IChildToken.sol"; 10 | 11 | contract UChildERC20 is 12 | Initializable, 13 | ContextUpgradeable, 14 | AccessControlUpgradeable, 15 | ERC20Upgradeable, 16 | ERC20PermitUpgradeable, 17 | IChildToken 18 | { 19 | bytes32 public constant DEPOSITOR_ROLE = keccak256("DEPOSITOR_ROLE"); 20 | 21 | /// @dev The constructor is disabled for a proxy upgrade. 22 | /// @custom:oz-upgrades-unsafe-allow constructor 23 | constructor() { 24 | _disableInitializers(); 25 | } 26 | 27 | /// @notice Initialize the contract after it has been proxified 28 | /// @dev meant to be called once immediately after deployment 29 | function initialize( 30 | string calldata name_, 31 | string calldata symbol_, 32 | address childChainManager 33 | ) external initializer { 34 | ContextUpgradeable.__Context_init(); 35 | AccessControlUpgradeable.__AccessControl_init(); 36 | ERC20Upgradeable.__ERC20_init(name_, symbol_); 37 | ERC20PermitUpgradeable.__ERC20Permit_init(name_); 38 | 39 | _grantRole(DEFAULT_ADMIN_ROLE, _msgSender()); 40 | _grantRole(DEPOSITOR_ROLE, childChainManager); 41 | } 42 | 43 | /// @notice called when token is deposited on root chain 44 | /// @dev Should be callable only by ChildChainManager 45 | /// Should handle deposit by minting the required amount for user 46 | /// Make sure minting is done only by this function 47 | /// @param user user address for whom deposit is being done 48 | /// @param depositData abi encoded amount 49 | function deposit( 50 | address user, 51 | bytes calldata depositData 52 | ) external override onlyRole(DEPOSITOR_ROLE) { 53 | uint256 amount = abi.decode(depositData, (uint256)); 54 | _mint(user, amount); 55 | } 56 | 57 | /// @notice called when user wants to withdraw tokens back to root chain 58 | /// @dev Should burn user's tokens. This transaction will be verified when exiting on root chain 59 | /// @param amount amount of tokens to withdraw 60 | function withdraw(uint256 amount) external override { 61 | _burn(_msgSender(), amount); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /contracts/ValidatorRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; 5 | import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; 6 | import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; 7 | import { StringsUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol"; 8 | import { IStakeManager } from "./interfaces/IStakeManager.sol"; 9 | import { IValidatorShare } from "./interfaces/IValidatorShare.sol"; 10 | import { IValidatorRegistry } from "./interfaces/IValidatorRegistry.sol"; 11 | 12 | /// @title ValidatorRegistry contract 13 | /// @notice ValidatorRegistry is the main contract that manages validators. 14 | contract ValidatorRegistry is 15 | IValidatorRegistry, 16 | PausableUpgradeable, 17 | AccessControlUpgradeable, 18 | ReentrancyGuardUpgradeable 19 | { 20 | using StringsUpgradeable for string; 21 | 22 | bytes32 public constant BOT = keccak256("BOT"); 23 | 24 | IStakeManager private stakeManager; 25 | address private maticToken; 26 | address private maticX; 27 | string public override version; 28 | uint256 public override preferredDepositValidatorId; 29 | uint256 public override preferredWithdrawalValidatorId; 30 | mapping(uint256 => bool) public override validatorIdExists; 31 | uint256[] private validators; 32 | address private polToken; 33 | 34 | /// ------------------------------ Modifiers ------------------------------- 35 | 36 | /// @notice Checks if the given validator id is not zero. 37 | modifier validatoIdIsZero(uint256 _validatorId) { 38 | require(_validatorId != 0, "Zero validator id"); 39 | _; 40 | } 41 | 42 | /// @notice Checks if the given validator id exists in the registry. 43 | /// @param _validatorId - Validator id 44 | modifier whenValidatorIdExists(uint256 _validatorId) { 45 | require( 46 | validatorIdExists[_validatorId], 47 | "Validator id doesn't exist in our registry" 48 | ); 49 | _; 50 | } 51 | 52 | /// @notice Checks if the given validator id doesn't exist in the registry. 53 | /// @param _validatorId - Validator id 54 | modifier whenValidatorIdDoesNotExist(uint256 _validatorId) { 55 | require( 56 | !validatorIdExists[_validatorId], 57 | "Validator id already exists in our registry" 58 | ); 59 | _; 60 | } 61 | 62 | /// -------------------------- Initializers -------------------------------- 63 | 64 | /// @dev The constructor is disabled for a proxy upgrade. 65 | /// @custom:oz-upgrades-unsafe-allow constructor 66 | constructor() { 67 | _disableInitializers(); 68 | } 69 | 70 | /// @notice Initialize the ValidatorRegistry contract. 71 | /// @param _stakeManager address of the polygon stake manager 72 | /// @param _maticToken address of the polygon ERC20 contract 73 | /// @param _maticX address of the MaticX contract 74 | /// @param _manager address of the manager 75 | function initialize( 76 | address _stakeManager, 77 | address _maticToken, 78 | address _maticX, 79 | address _manager 80 | ) external initializer { 81 | AccessControlUpgradeable.__AccessControl_init(); 82 | PausableUpgradeable.__Pausable_init(); 83 | 84 | require(_stakeManager != address(0), "Zero stake manager address"); 85 | stakeManager = IStakeManager(_stakeManager); 86 | 87 | require(_maticToken != address(0), "Zero Matic token address"); 88 | maticToken = _maticToken; 89 | 90 | // slither-disable-next-line missing-zero-check 91 | maticX = _maticX; 92 | 93 | require(_manager != address(0), "Zero manager address"); 94 | _setupRole(DEFAULT_ADMIN_ROLE, _manager); 95 | } 96 | 97 | /** 98 | * @dev Initializes version 2 of the current contract. 99 | * @param _polToken - Address of the POL token 100 | */ 101 | function initializeV2( 102 | address _polToken 103 | ) external reinitializer(2) onlyRole(DEFAULT_ADMIN_ROLE) { 104 | ReentrancyGuardUpgradeable.__ReentrancyGuard_init(); 105 | 106 | require(_polToken != address(0), "Zero POL token address"); 107 | polToken = _polToken; 108 | 109 | version = "2"; 110 | } 111 | 112 | /// ----------------------------- API -------------------------------------- 113 | 114 | /// @notice Allows a validator that has been already staked on the stake 115 | /// manager contract to join the MaticX protocol. 116 | /// @param _validatorId - Validator id 117 | function addValidator( 118 | uint256 _validatorId 119 | ) 120 | external 121 | override 122 | whenNotPaused 123 | onlyRole(DEFAULT_ADMIN_ROLE) 124 | validatoIdIsZero(_validatorId) 125 | whenValidatorIdDoesNotExist(_validatorId) 126 | { 127 | IStakeManager.Validator memory validator = stakeManager.validators( 128 | _validatorId 129 | ); 130 | require( 131 | validator.contractAddress != address(0), 132 | "Validator has no validator share" 133 | ); 134 | require( 135 | (validator.status == IStakeManager.Status.Active) && 136 | validator.deactivationEpoch == 0, 137 | "Validator isn't active" 138 | ); 139 | 140 | validators.push(_validatorId); 141 | validatorIdExists[_validatorId] = true; 142 | 143 | emit AddValidator(_validatorId); 144 | } 145 | 146 | /// @notice Removes a validator from the registry. 147 | /// @param _validatorId - Validator id 148 | /// @param _ignoreBalance - If bypass the validator balance check or not 149 | // slither-disable-next-line pess-multiple-storage-read 150 | function removeValidator( 151 | uint256 _validatorId, 152 | bool _ignoreBalance 153 | ) 154 | external 155 | override 156 | whenNotPaused 157 | onlyRole(DEFAULT_ADMIN_ROLE) 158 | validatoIdIsZero(_validatorId) 159 | whenValidatorIdExists(_validatorId) 160 | { 161 | require( 162 | preferredDepositValidatorId != _validatorId, 163 | "Can't remove a preferred validator for deposits" 164 | ); 165 | require( 166 | preferredWithdrawalValidatorId != _validatorId, 167 | "Can't remove a preferred validator for withdrawals" 168 | ); 169 | 170 | if (!_ignoreBalance) { 171 | address validatorShare = stakeManager.getValidatorContract( 172 | _validatorId 173 | ); 174 | (uint256 validatorBalance, ) = IValidatorShare(validatorShare) 175 | .getTotalStake(maticX); 176 | require(validatorBalance == 0, "Validator has some shares left"); 177 | } 178 | 179 | uint256 iterationCount = validators.length - 1; 180 | for (uint256 i = 0; i < iterationCount; ) { 181 | if (_validatorId == validators[i]) { 182 | validators[i] = validators[iterationCount]; 183 | break; 184 | } 185 | 186 | unchecked { 187 | ++i; 188 | } 189 | } 190 | 191 | validators.pop(); 192 | delete validatorIdExists[_validatorId]; 193 | 194 | emit RemoveValidator(_validatorId); 195 | } 196 | 197 | /// ------------------------------ Setters --------------------------------- 198 | 199 | /// @notice Sets the prefered validator id for deposits. 200 | /// @param _validatorId - Validator id for deposits 201 | function setPreferredDepositValidatorId( 202 | uint256 _validatorId 203 | ) 204 | external 205 | override 206 | whenNotPaused 207 | onlyRole(BOT) 208 | validatoIdIsZero(_validatorId) 209 | whenValidatorIdExists(_validatorId) 210 | { 211 | preferredDepositValidatorId = _validatorId; 212 | 213 | emit SetPreferredDepositValidatorId(_validatorId); 214 | } 215 | 216 | /// @notice Set the prefered validator id for withdrawals. 217 | /// @param _validatorId - Validator id for withdrawals 218 | function setPreferredWithdrawalValidatorId( 219 | uint256 _validatorId 220 | ) 221 | external 222 | override 223 | whenNotPaused 224 | onlyRole(BOT) 225 | validatoIdIsZero(_validatorId) 226 | whenValidatorIdExists(_validatorId) 227 | { 228 | preferredWithdrawalValidatorId = _validatorId; 229 | 230 | emit SetPreferredWithdrawalValidatorId(_validatorId); 231 | } 232 | 233 | /// @notice Sets the address of MaticX. 234 | /// @param _maticX - Address of MaticX 235 | function setMaticX( 236 | address _maticX 237 | ) external override onlyRole(DEFAULT_ADMIN_ROLE) { 238 | require(_maticX != address(0), "Zero MaticX address"); 239 | maticX = _maticX; 240 | 241 | emit SetMaticX(_maticX); 242 | } 243 | 244 | /// @notice Sets a new version of this contract 245 | /// @param _version - New version of this contract 246 | function setVersion( 247 | string memory _version 248 | ) external override onlyRole(DEFAULT_ADMIN_ROLE) { 249 | require(!_version.equal(""), "Empty version"); 250 | version = _version; 251 | 252 | emit SetVersion(_version); 253 | } 254 | 255 | /// @notice Toggles the paused status of this contract. 256 | function togglePause() external override onlyRole(DEFAULT_ADMIN_ROLE) { 257 | paused() ? _unpause() : _pause(); 258 | } 259 | 260 | /// ------------------------------ Getters --------------------------------- 261 | 262 | /// @notice Returns the contract addresses used on the current contract. 263 | /// @return _stakeManager - Address of the stake manager 264 | /// @return _maticToken - Address of the Matic token 265 | /// @return _maticX - Address of MaticX 266 | /// @return _polToken - Address of the POL token 267 | function getContracts() 268 | external 269 | view 270 | override 271 | returns ( 272 | IStakeManager _stakeManager, 273 | address _maticToken, 274 | address _maticX, 275 | address _polToken 276 | ) 277 | { 278 | _stakeManager = stakeManager; 279 | _maticToken = maticToken; 280 | _maticX = maticX; 281 | _polToken = polToken; 282 | } 283 | 284 | /// @notice Returns validator id by index. 285 | /// @param _idx - Validator index 286 | /// @return Validator id 287 | function getValidatorId( 288 | uint256 _idx 289 | ) external view override returns (uint256) { 290 | require(_idx < validators.length, "Validator id does not exist"); 291 | return validators[_idx]; 292 | } 293 | 294 | /// @notice Returns an array of registered validator ids. 295 | /// @return Array of registered validator ids 296 | function getValidators() external view override returns (uint256[] memory) { 297 | return validators; 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /contracts/interfaces/IChildPool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | interface IChildPool { 5 | struct MaticXSwapRequest { 6 | uint256 amount; 7 | uint256 requestTime; 8 | uint256 withdrawalTime; 9 | } 10 | 11 | function version() external view returns (string memory); 12 | 13 | function claimedMatic() external view returns (uint256); 14 | 15 | function maticXSwapLockPeriod() external view returns (uint256); 16 | 17 | function treasury() external view returns (address payable); 18 | 19 | function instantPoolOwner() external view returns (address payable); 20 | 21 | function instantPoolMatic() external view returns (uint256); 22 | 23 | function instantPoolMaticX() external view returns (uint256); 24 | 25 | function instantWithdrawalFees() external view returns (uint256); 26 | 27 | function instantWithdrawalFeeBps() external view returns (uint256); 28 | 29 | function provideInstantPoolMatic() external payable; 30 | 31 | function provideInstantPoolMaticX(uint256 _amount) external; 32 | 33 | function withdrawInstantPoolMaticX(uint256 _amount) external; 34 | 35 | function withdrawInstantPoolMatic(uint256 _amount) external; 36 | 37 | function withdrawInstantWithdrawalFees(uint256 _amount) external; 38 | 39 | function swapMaticForMaticXViaInstantPool() external payable; 40 | 41 | function swapMaticXForMaticViaInstantPool(uint256 _amount) external; 42 | 43 | function getMaticXSwapLockPeriod() external view returns (uint256); 44 | 45 | function setMaticXSwapLockPeriod(uint256 _hours) external; 46 | 47 | function getUserMaticXSwapRequests(address _address) 48 | external 49 | view 50 | returns (MaticXSwapRequest[] memory); 51 | 52 | function requestMaticXSwap(uint256 _amount) external returns (uint256); 53 | 54 | function claimMaticXSwap(uint256 _idx) external; 55 | 56 | function setTreasury(address payable _address) external; 57 | 58 | function setInstantPoolOwner(address payable _address) external; 59 | 60 | function setFxStateChildTunnel(address _address) external; 61 | 62 | function setInstantWithdrawalFeeBps(uint256 _feeBps) external; 63 | 64 | function setTrustedForwarder(address _address) external; 65 | 66 | function setVersion(string calldata _version) external; 67 | 68 | function togglePause() external; 69 | 70 | function convertMaticXToMatic(uint256 _balance) 71 | external 72 | view 73 | returns ( 74 | uint256, 75 | uint256, 76 | uint256 77 | ); 78 | 79 | function convertMaticToMaticX(uint256 _balance) 80 | external 81 | view 82 | returns ( 83 | uint256, 84 | uint256, 85 | uint256 86 | ); 87 | 88 | function getAmountAfterInstantWithdrawalFees(uint256 _amount) 89 | external 90 | view 91 | returns (uint256, uint256); 92 | 93 | function getContracts() 94 | external 95 | view 96 | returns ( 97 | address _fxStateChildTunnel, 98 | address _maticX, 99 | address _trustedForwarder 100 | ); 101 | 102 | event SetTreasury(address _address); 103 | event SetInstantPoolOwner(address _address); 104 | event SetFxStateChildTunnel(address _address); 105 | event SetTrustedForwarder(address _address); 106 | event SetVersion(string _version); 107 | event CollectedInstantWithdrawalFees(uint256 _fees); 108 | event SetInstantWithdrawalFeeBps(uint256 _feeBps); 109 | event SetMaticXSwapLockPeriodEvent(uint256 _hours); 110 | event ClaimMaticXSwap( 111 | address indexed _from, 112 | uint256 indexed _idx, 113 | uint256 _amountClaimed 114 | ); 115 | 116 | event RequestMaticXSwap( 117 | address indexed _from, 118 | uint256 _amountMaticX, 119 | uint256 _amountMatic, 120 | uint256 userSwapRequestsIndex 121 | ); 122 | } 123 | -------------------------------------------------------------------------------- /contracts/interfaces/IChildToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.8; 3 | 4 | interface IChildToken { 5 | function deposit(address user, bytes calldata depositData) external; 6 | 7 | function withdraw(uint256 amount) external; 8 | } 9 | -------------------------------------------------------------------------------- /contracts/interfaces/IFxStateChildTunnel.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | interface IFxStateChildTunnel { 5 | function latestStateId() external view returns (uint256); 6 | 7 | function latestRootMessageSender() external view returns (address); 8 | 9 | function latestData() external view returns (bytes memory); 10 | 11 | function sendMessageToRoot(bytes memory message) external; 12 | 13 | function setFxRootTunnel(address _fxRootTunnel) external; 14 | 15 | function getReserves() external view returns (uint256, uint256); 16 | 17 | function getRate() external view returns (uint256); 18 | 19 | function convertMaticXToMatic( 20 | uint256 _balance 21 | ) external view returns (uint256, uint256, uint256); 22 | 23 | function convertMaticToMaticX( 24 | uint256 _balance 25 | ) external view returns (uint256, uint256, uint256); 26 | } 27 | -------------------------------------------------------------------------------- /contracts/interfaces/IFxStateRootTunnel.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | interface IFxStateRootTunnel { 5 | function latestData() external view returns (bytes memory); 6 | 7 | function setFxChildTunnel(address _fxChildTunnel) external; 8 | 9 | function sendMessageToChild(bytes memory message) external; 10 | 11 | function setMaticX(address _maticX) external; 12 | } 13 | -------------------------------------------------------------------------------- /contracts/interfaces/IMaticX.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; 5 | import { IFxStateRootTunnel } from "./IFxStateRootTunnel.sol"; 6 | import { IStakeManager } from "./IStakeManager.sol"; 7 | import { IValidatorRegistry } from "./IValidatorRegistry.sol"; 8 | import { IValidatorShare } from "./IValidatorShare.sol"; 9 | 10 | /// @title MaticX interface 11 | /// @notice Defines a public interface for the MaticX contract. 12 | interface IMaticX is IERC20Upgradeable { 13 | struct WithdrawalRequest { 14 | // Validator's incremental nonce 15 | uint256 validatorNonce; 16 | // Request epoch 17 | uint256 requestEpoch; 18 | // Address of the validator share 19 | address validatorAddress; 20 | } 21 | 22 | /// @notice Emitted when the user submits her POL or Matic tokens. 23 | /// @param _from - User who stakes 24 | /// @param _amount - Stake amount 25 | event Submit(address indexed _from, uint256 _amount); 26 | 27 | /// @notice Emitted when the user submits her POL or Matic tokens. 28 | /// @param _validatorId - Validator which accepted the user's stake 29 | /// @param _amountDelegated - Stake amount 30 | event Delegate(uint256 indexed _validatorId, uint256 _amountDelegated); 31 | 32 | /// @notice Emitted when the user requests a withdrawal for her previously 33 | // staked tokens. 34 | /// @param _from - User who requests a withdrawal 35 | /// @param _amountInMaticX - Requested amount in MaticX shares 36 | /// @param _amountInStakeTokens - Requested amount in POL tokens 37 | event RequestWithdraw( 38 | address indexed _from, 39 | uint256 _amountInMaticX, 40 | uint256 _amountInStakeTokens 41 | ); 42 | 43 | /// @notice Emitted when the user claims a previously requested withdrawal. 44 | /// @param _from - User who claims a withdrawal 45 | /// @param _idx - Withdrawal index 46 | /// @param _claimedAmount - Claimed amount in POL tokens 47 | event ClaimWithdrawal( 48 | address indexed _from, 49 | uint256 indexed _idx, 50 | uint256 _claimedAmount 51 | ); 52 | 53 | /// @notice Emitted when rewards are withdrawn from a given validator. 54 | /// @param _validatorId - Validator id 55 | /// @param _rewards - Rewards amount 56 | event WithdrawRewards(uint256 indexed _validatorId, uint256 _rewards); 57 | 58 | /// @notice Emitted when rewards are staked at a given validator. 59 | /// @param _validatorId - Validator id 60 | /// @param _stakedAmount - Staked amount 61 | event StakeRewards(uint256 indexed _validatorId, uint256 _stakedAmount); 62 | 63 | /// @notice Emitted when fees are distributed to the treasury. 64 | /// @param _treasury - Address of the treasury 65 | /// @param _feeAmount - Fee amount 66 | event DistributeFees(address indexed _treasury, uint256 _feeAmount); 67 | 68 | /// @notice Emitted when POL tokens are delegated to another validator. 69 | /// @param _fromValidatorId - Validator id to migrate POL tokens from 70 | /// @param _toValidatorId - Validator id to migrate POL tokens to 71 | /// @param _amount - Amount of POL tokens 72 | event MigrateDelegation( 73 | uint256 indexed _fromValidatorId, 74 | uint256 indexed _toValidatorId, 75 | uint256 _amount 76 | ); 77 | 78 | /// @notice Emitted when the fee percent is set. 79 | /// @param _feePercent - Fee percent 80 | event SetFeePercent(uint16 _feePercent); 81 | 82 | /// @notice Emitted when the address of the treasury is set. 83 | /// @param _treasury - Address of the treasury 84 | event SetTreasury(address _treasury); 85 | 86 | /// @notice Emitted when the address of the validator registry is set. 87 | /// @param _validatorRegistry - Address of the validator registry 88 | event SetValidatorRegistry(address _validatorRegistry); 89 | 90 | /// @notice Emitted when the address of the fx state root tunnel is set. 91 | /// @param _fxStateRootTunnel - Address of the fx state root tunnel 92 | event SetFxStateRootTunnel(address _fxStateRootTunnel); 93 | 94 | /// @notice Emitted when the new version of the current contract is set. 95 | /// @param _version - Version of the current contract 96 | event SetVersion(string _version); 97 | 98 | /// @notice Emitted when the address of the POL token is set. 99 | /// @param _polToken - Address of the POL token 100 | event SetPOLToken(address _polToken); 101 | 102 | /// @notice Sends Matic tokens to the current contract and mints MaticX 103 | /// shares in return. It requires that the sender has a preliminary approved 104 | /// amount of Matic to this contract. 105 | /// @custom:deprecated 106 | /// @param _amount - Amount of Matic tokens 107 | /// @return Amount of minted MaticX shares 108 | function submit(uint256 _amount) external returns (uint256); 109 | 110 | /// @notice Sends POL tokens to the current contract and mints MaticX shares 111 | /// in return. It requires that the sender has a preliminary approved amount 112 | /// of POL to this contract. 113 | /// @param _amount - Amount of POL tokens 114 | /// @return Amount of minted MaticX shares 115 | function submitPOL(uint256 _amount) external returns (uint256); 116 | 117 | /// @notice Registers a user's request to withdraw an amount of POL tokens. 118 | /// @param _amount - Amount of POL tokens 119 | function requestWithdraw(uint256 _amount) external; 120 | 121 | /// @notice Claims POL tokens from a validator share and sends them to the 122 | /// user. 123 | /// @param _idx - Array index of the user's withdrawal request 124 | function claimWithdrawal(uint256 _idx) external; 125 | 126 | /// @notice Withdraws POL rewards from the given validator. 127 | /// @custom:deprecated 128 | /// @param _validatorId - Validator id to withdraw Matic rewards 129 | function withdrawRewards(uint256 _validatorId) external returns (uint256); 130 | 131 | /// @notice Withdraws POL rewards from the given validators. 132 | /// @param _validatorIds - Array of validator ids to withdraw Matic rewards 133 | function withdrawValidatorsReward( 134 | uint256[] calldata _validatorIds 135 | ) external returns (uint256[] memory); 136 | 137 | /// @notice Stakes POL rewards and distribute fees to the treasury if any. 138 | /// @param _validatorId - Validator id to stake POL rewards 139 | function stakeRewardsAndDistributeFees(uint256 _validatorId) external; 140 | 141 | /// @notice Stakes Matic rewards and distribute fees to the treasury if any. 142 | /// @custom:deprecated 143 | /// @param _validatorId - Validator id to stake Matic rewards 144 | function stakeRewardsAndDistributeFeesMatic(uint256 _validatorId) external; 145 | 146 | /// @notice Delegates a given amount of POL tokens to another validator. 147 | /// @param _fromValidatorId - Validator id to migrate POL tokens from 148 | /// @param _toValidatorId - Validator id to migrate POL tokens to 149 | /// @param _amount - Amount of POL tokens 150 | function migrateDelegation( 151 | uint256 _fromValidatorId, 152 | uint256 _toValidatorId, 153 | uint256 _amount 154 | ) external; 155 | 156 | /// @notice Sets a fee percent where 1 = 0.01%. 157 | /// @param _feePercent - Fee percent 158 | function setFeePercent(uint16 _feePercent) external; 159 | 160 | /// @notice Sets the address of the treasury. 161 | /// @param _treasury Address of the treasury 162 | function setTreasury(address _treasury) external; 163 | 164 | /// @notice Sets the address of the validator registry. 165 | /// @param _validatorRegistry Address of the validator registry 166 | function setValidatorRegistry(address _validatorRegistry) external; 167 | 168 | /// @notice Sets the address of the fx state root tunnel. 169 | /// @param _fxStateRootTunnel Address of the fx state root tunnel 170 | function setFxStateRootTunnel(address _fxStateRootTunnel) external; 171 | 172 | /// @notice Sets a new version of this contract 173 | /// @param _version - New version of this contract 174 | function setVersion(string calldata _version) external; 175 | 176 | /// @notice Toggles the paused status of this contract. 177 | function togglePause() external; 178 | 179 | /// @notice Converts an amount of MaticX shares to POL tokens. 180 | /// @param _balance - Balance in MaticX shares 181 | /// @return Balance in POL tokens 182 | /// @return Total MaticX shares 183 | /// @return Total pooled POL tokens 184 | function convertMaticXToPOL( 185 | uint256 _balance 186 | ) external view returns (uint256, uint256, uint256); 187 | 188 | /// @notice Converts an amount of MaticX shares to POL tokens. 189 | /// @custom:deprecated 190 | /// @param _balance - Balance in MaticX shares 191 | /// @return Balance in POL tokens 192 | /// @return Total MaticX shares 193 | /// @return Total pooled POL tokens 194 | function convertMaticXToMatic( 195 | uint256 _balance 196 | ) external view returns (uint256, uint256, uint256); 197 | 198 | /// @notice Converts an amount of POL tokens to MaticX shares. 199 | /// @param _balance - Balance in POL tokens 200 | /// @return Balance in MaticX shares 201 | /// @return Total MaticX shares 202 | /// @return Total pooled POL tokens 203 | function convertPOLToMaticX( 204 | uint256 _balance 205 | ) external view returns (uint256, uint256, uint256); 206 | 207 | /// @notice Converts an amount of POL tokens to MaticX shares. 208 | /// @custom:deprecated 209 | /// @param _balance - Balance in POL tokens 210 | /// @return Balance in MaticX shares 211 | /// @return Total MaticX shares 212 | /// @return Total pooled POL tokens 213 | function convertMaticToMaticX( 214 | uint256 _balance 215 | ) external view returns (uint256, uint256, uint256); 216 | 217 | /// @notice Returns total pooled POL tokens from all registered validators. 218 | /// @return Total pooled POL tokens 219 | function getTotalStakeAcrossAllValidators() external view returns (uint256); 220 | 221 | /// @notice Returns total pooled POL tokens from all registered validators. 222 | /// @custom:deprecated 223 | /// @return Total pooled POL tokens 224 | function getTotalPooledMatic() external view returns (uint256); 225 | 226 | /// @notice Returns the total amount of staked POL tokens and their exchange 227 | /// rate for the current contract on the given validator share. 228 | /// @param _validatorShare - Address of the validator share 229 | /// @return Total amount of staked POL tokens 230 | /// @return Exchange rate 231 | function getTotalStake( 232 | IValidatorShare _validatorShare 233 | ) external view returns (uint256, uint256); 234 | 235 | /// @notice Returns all withdrawal requests initiated by the user. 236 | /// @param _user - Address of the user 237 | /// @return Array of user's withdrawal requests 238 | function getUserWithdrawalRequests( 239 | address _user 240 | ) external view returns (WithdrawalRequest[] memory); 241 | 242 | /// @dev Returns a shares amount of the withdrawal request. 243 | /// @param _user - Address of the user 244 | /// @param _idx Index of the withdrawal request 245 | /// @return Share amount fo the withdrawal request 246 | function getSharesAmountOfUserWithdrawalRequest( 247 | address _user, 248 | uint256 _idx 249 | ) external view returns (uint256); 250 | 251 | /// @notice Returns the contract addresses used on the current contract. 252 | /// @return _stakeManager - Address of the stake manager 253 | /// @return _maticToken - Address of the Matic token 254 | /// @return _validatorRegistry - Address of the validator registry 255 | /// @return _polToken - Address of the POL token 256 | function getContracts() 257 | external 258 | view 259 | returns ( 260 | IStakeManager _stakeManager, 261 | IERC20Upgradeable _maticToken, 262 | IValidatorRegistry _validatorRegistry, 263 | IERC20Upgradeable _polToken 264 | ); 265 | 266 | /// @notice Returns the address of the treasury. 267 | function treasury() external view returns (address); 268 | 269 | /// @notice Returns the version of the current contract. 270 | function version() external view returns (string memory); 271 | 272 | /// @notice Returns the fee percent. 273 | function feePercent() external view returns (uint16); 274 | 275 | /// @notice Returns the address of the fx state root tunnel. 276 | function fxStateRootTunnel() external view returns (IFxStateRootTunnel); 277 | } 278 | -------------------------------------------------------------------------------- /contracts/interfaces/IPolygonMigration.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | 6 | interface IPolygonMigration { 7 | event Migrated(address indexed account, uint256 amount); 8 | 9 | event Unmigrated( 10 | address indexed account, 11 | address indexed recipient, 12 | uint256 amount 13 | ); 14 | 15 | event UnmigrationLockUpdated(bool lock); 16 | 17 | error UnmigrationLocked(); 18 | 19 | error InvalidAddressOrAlreadySet(); 20 | 21 | error InvalidAddress(); 22 | 23 | function migrate(uint256 amount) external; 24 | 25 | function unmigrate(uint256 amount) external; 26 | 27 | function unmigrateTo(address recipient, uint256 amount) external; 28 | 29 | function unmigrateWithPermit( 30 | uint256 amount, 31 | uint256 deadline, 32 | uint8 v, 33 | bytes32 r, 34 | bytes32 s 35 | ) external; 36 | 37 | function updateUnmigrationLock(bool unmigrationLocked) external; 38 | 39 | function burn(uint256 amount) external; 40 | 41 | function matic() external view returns (IERC20 maticToken); 42 | 43 | function polygon() external view returns (IERC20 polygonEcosystemToken); 44 | 45 | function unmigrationLocked() 46 | external 47 | view 48 | returns (bool isUnmigrationLocked); 49 | 50 | function version() external pure returns (string memory version); 51 | } 52 | -------------------------------------------------------------------------------- /contracts/interfaces/IRateProvider.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | interface IRateProvider { 5 | function getRate() external view returns (uint256); 6 | 7 | function setFxChild(address _fxChild) external; 8 | 9 | function fxChild() external view returns (address); 10 | } 11 | -------------------------------------------------------------------------------- /contracts/interfaces/IStakeManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | interface IStakeManager { 5 | enum Status { 6 | Inactive, 7 | Active, 8 | Locked, 9 | Unstaked 10 | } 11 | 12 | struct Validator { 13 | uint256 amount; 14 | uint256 reward; 15 | uint256 activationEpoch; 16 | uint256 deactivationEpoch; 17 | uint256 jailTime; 18 | address signer; 19 | address contractAddress; 20 | Status status; 21 | uint256 commissionRate; 22 | uint256 lastCommissionUpdate; 23 | uint256 delegatorsReward; 24 | uint256 delegatedAmount; 25 | uint256 initialRewardPerStake; 26 | } 27 | 28 | function migrateDelegation( 29 | uint256 _fromValidatorId, 30 | uint256 _toValidatorId, 31 | uint256 _amount 32 | ) external; 33 | 34 | function setCurrentEpoch(uint256 _currentEpoch) external; 35 | 36 | function getValidatorContract( 37 | uint256 _validatorId 38 | ) external view returns (address); 39 | 40 | function validators( 41 | uint256 _index 42 | ) external view returns (Validator memory); 43 | 44 | function epoch() external view returns (uint256); 45 | 46 | function withdrawalDelay() external view returns (uint256); 47 | } 48 | -------------------------------------------------------------------------------- /contracts/interfaces/IStakingInfo.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | interface IStakingInfo { 5 | event ShareMinted( 6 | uint256 indexed validatorId, 7 | address indexed user, 8 | uint256 indexed amount, 9 | uint256 tokens 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /contracts/interfaces/IValidatorRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | import { IStakeManager } from "./IStakeManager.sol"; 5 | 6 | /// @title IValidatorRegistry 7 | /// @notice Defines a public interface for the ValidatorRegistry contract. 8 | interface IValidatorRegistry { 9 | /// @notice Emitted when a validator is joined the MaticX protocol. 10 | /// @param _validatorId - Validator id 11 | event AddValidator(uint256 indexed _validatorId); 12 | 13 | /// @notice Emitted when a validator is removed from the registry. 14 | /// @param _validatorId - Validator id 15 | event RemoveValidator(uint256 indexed _validatorId); 16 | 17 | /// @notice Emitted when the preferred validator is set for deposits. 18 | /// @param _validatorId - Validator id 19 | event SetPreferredDepositValidatorId(uint256 indexed _validatorId); 20 | 21 | /// @notice Emitted when the preferred validator is set for withdrawals. 22 | /// @param _validatorId - Validator id 23 | event SetPreferredWithdrawalValidatorId(uint256 indexed _validatorId); 24 | 25 | /// @notice Emitted when MaticX is set. 26 | /// @param _maticX - Address of MaticX 27 | event SetMaticX(address _maticX); 28 | 29 | /// @notice Emitted when the new version of the current contract is set. 30 | /// @param _version - Version of the current contract 31 | event SetVersion(string _version); 32 | 33 | /// @notice Allows a validator that has been already staked on the stake 34 | /// manager contract to join the MaticX protocol. 35 | /// @param _validatorId - Validator id 36 | function addValidator(uint256 _validatorId) external; 37 | 38 | /// @notice Removes a validator from the registry. 39 | /// @param _validatorId - Validator id 40 | /// @param _ignoreBalance - If bypass the validator balance check or not 41 | function removeValidator( 42 | uint256 _validatorId, 43 | bool _ignoreBalance 44 | ) external; 45 | 46 | /// @notice Sets the prefered validator id for deposits. 47 | /// @param _validatorId - Validator id for deposits 48 | function setPreferredDepositValidatorId(uint256 _validatorId) external; 49 | 50 | /// @notice Set the prefered validator id for withdrawals. 51 | /// @param _validatorId - Validator id for withdrawals 52 | function setPreferredWithdrawalValidatorId(uint256 _validatorId) external; 53 | 54 | /// @notice Sets the address of MaticX. 55 | /// @param _maticX - Address of MaticX 56 | function setMaticX(address _maticX) external; 57 | 58 | /// @notice Sets a new version of this contract 59 | /// @param _version - New version of this contract 60 | function setVersion(string memory _version) external; 61 | 62 | /// @notice Toggles the paused status of this contract. 63 | function togglePause() external; 64 | 65 | /// @notice Returns the version of the current contract. 66 | function version() external view returns (string memory); 67 | 68 | /// @notice Returns the id of the preferred validator for deposits. 69 | function preferredDepositValidatorId() external view returns (uint256); 70 | 71 | /// @notice Returns the id of the preferred validator for withdrawals. 72 | function preferredWithdrawalValidatorId() external view returns (uint256); 73 | 74 | /// @notice Checks if the given validator is joined the MaticX protocol. 75 | /// @param _validatorId - Validator id 76 | function validatorIdExists( 77 | uint256 _validatorId 78 | ) external view returns (bool); 79 | 80 | /// @notice Returns the contract addresses used on the current contract. 81 | /// @return _stakeManager - Address of the stake manager 82 | /// @return _maticToken - Address of the Matic token 83 | /// @return _maticX - Address of MaticX 84 | /// @return _polToken - Address of the POL token 85 | function getContracts() 86 | external 87 | view 88 | returns ( 89 | IStakeManager _stakeManager, 90 | address _maticToken, 91 | address _maticX, 92 | address _polToken 93 | ); 94 | 95 | /// @notice Returns the validator id by index. 96 | /// @param _idx - Validator index 97 | /// @return Validator id 98 | function getValidatorId(uint256 _idx) external view returns (uint256); 99 | 100 | /// @notice Returns an array of registered validator ids. 101 | /// @return Array of registered validator ids 102 | function getValidators() external view returns (uint256[] memory); 103 | } 104 | -------------------------------------------------------------------------------- /contracts/interfaces/IValidatorShare.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; 5 | 6 | interface IValidatorShare is IERC20Upgradeable { 7 | struct DelegatorUnbond { 8 | uint256 shares; 9 | uint256 withdrawEpoch; 10 | } 11 | 12 | function buyVoucher( 13 | uint256 _amount, 14 | uint256 _minSharesToMint 15 | ) external returns (uint256 amountToDeposit); 16 | 17 | function buyVoucherPOL( 18 | uint256 _amount, 19 | uint256 _minSharesToMint 20 | ) external returns (uint256 amountToDeposit); 21 | 22 | function sellVoucher_newPOL( 23 | uint256 _claimAmount, 24 | uint256 _maximumSharesToBurn 25 | ) external; 26 | 27 | function unstakeClaimTokens_newPOL(uint256 _unbondNonce) external; 28 | 29 | function withdrawRewardsPOL() external; 30 | 31 | function getTotalStake( 32 | address _user 33 | ) external view returns (uint256, uint256); 34 | 35 | function unbondNonces(address _user) external view returns (uint256); 36 | 37 | function unbonds_new( 38 | address _user, 39 | uint256 _unbondNonce 40 | ) external view returns (DelegatorUnbond memory); 41 | 42 | function stakingLogger() external view returns (address); 43 | } 44 | -------------------------------------------------------------------------------- /contracts/libraries/ExitPayloadReader.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | import { RLPReader } from "./RLPReader.sol"; 5 | 6 | library ExitPayloadReader { 7 | using RLPReader for bytes; 8 | using RLPReader for RLPReader.RLPItem; 9 | 10 | uint8 constant WORD_SIZE = 32; 11 | 12 | struct ExitPayload { 13 | RLPReader.RLPItem[] data; 14 | } 15 | 16 | struct Receipt { 17 | RLPReader.RLPItem[] data; 18 | bytes raw; 19 | uint256 logIndex; 20 | } 21 | 22 | struct Log { 23 | RLPReader.RLPItem data; 24 | RLPReader.RLPItem[] list; 25 | } 26 | 27 | struct LogTopics { 28 | RLPReader.RLPItem[] data; 29 | } 30 | 31 | // copy paste of private copy() from RLPReader to avoid changing of existing contracts 32 | function copy(uint256 src, uint256 dest, uint256 len) private pure { 33 | if (len == 0) return; 34 | 35 | // copy as many word sizes as possible 36 | for (; len >= WORD_SIZE; len -= WORD_SIZE) { 37 | assembly { 38 | mstore(dest, mload(src)) 39 | } 40 | 41 | src += WORD_SIZE; 42 | dest += WORD_SIZE; 43 | } 44 | 45 | // left over bytes. Mask is used to remove unwanted bytes from the word 46 | uint256 mask = 256 ** (WORD_SIZE - len) - 1; 47 | assembly { 48 | let srcpart := and(mload(src), not(mask)) // zero out src 49 | let destpart := and(mload(dest), mask) // retrieve the bytes 50 | mstore(dest, or(destpart, srcpart)) 51 | } 52 | } 53 | 54 | function toExitPayload( 55 | bytes memory data 56 | ) internal pure returns (ExitPayload memory) { 57 | RLPReader.RLPItem[] memory payloadData = data.toRlpItem().toList(); 58 | 59 | return ExitPayload(payloadData); 60 | } 61 | 62 | function getHeaderNumber( 63 | ExitPayload memory payload 64 | ) internal pure returns (uint256) { 65 | return payload.data[0].toUint(); 66 | } 67 | 68 | function getBlockProof( 69 | ExitPayload memory payload 70 | ) internal pure returns (bytes memory) { 71 | return payload.data[1].toBytes(); 72 | } 73 | 74 | function getBlockNumber( 75 | ExitPayload memory payload 76 | ) internal pure returns (uint256) { 77 | return payload.data[2].toUint(); 78 | } 79 | 80 | function getBlockTime( 81 | ExitPayload memory payload 82 | ) internal pure returns (uint256) { 83 | return payload.data[3].toUint(); 84 | } 85 | 86 | function getTxRoot( 87 | ExitPayload memory payload 88 | ) internal pure returns (bytes32) { 89 | return bytes32(payload.data[4].toUint()); 90 | } 91 | 92 | function getReceiptRoot( 93 | ExitPayload memory payload 94 | ) internal pure returns (bytes32) { 95 | return bytes32(payload.data[5].toUint()); 96 | } 97 | 98 | function getReceipt( 99 | ExitPayload memory payload 100 | ) internal pure returns (Receipt memory receipt) { 101 | receipt.raw = payload.data[6].toBytes(); 102 | RLPReader.RLPItem memory receiptItem = receipt.raw.toRlpItem(); 103 | 104 | if (receiptItem.isList()) { 105 | // legacy tx 106 | receipt.data = receiptItem.toList(); 107 | } else { 108 | // pop first byte before parsting receipt 109 | bytes memory typedBytes = receipt.raw; 110 | bytes memory result = new bytes(typedBytes.length - 1); 111 | uint256 srcPtr; 112 | uint256 destPtr; 113 | assembly { 114 | srcPtr := add(33, typedBytes) 115 | destPtr := add(0x20, result) 116 | } 117 | 118 | copy(srcPtr, destPtr, result.length); 119 | receipt.data = result.toRlpItem().toList(); 120 | } 121 | 122 | receipt.logIndex = getReceiptLogIndex(payload); 123 | return receipt; 124 | } 125 | 126 | function getReceiptProof( 127 | ExitPayload memory payload 128 | ) internal pure returns (bytes memory) { 129 | return payload.data[7].toBytes(); 130 | } 131 | 132 | function getBranchMaskAsBytes( 133 | ExitPayload memory payload 134 | ) internal pure returns (bytes memory) { 135 | return payload.data[8].toBytes(); 136 | } 137 | 138 | function getBranchMaskAsUint( 139 | ExitPayload memory payload 140 | ) internal pure returns (uint256) { 141 | return payload.data[8].toUint(); 142 | } 143 | 144 | function getReceiptLogIndex( 145 | ExitPayload memory payload 146 | ) internal pure returns (uint256) { 147 | return payload.data[9].toUint(); 148 | } 149 | 150 | // Receipt methods 151 | function toBytes( 152 | Receipt memory receipt 153 | ) internal pure returns (bytes memory) { 154 | return receipt.raw; 155 | } 156 | 157 | function getLog(Receipt memory receipt) internal pure returns (Log memory) { 158 | RLPReader.RLPItem memory logData = receipt.data[3].toList()[ 159 | receipt.logIndex 160 | ]; 161 | return Log(logData, logData.toList()); 162 | } 163 | 164 | // Log methods 165 | function getEmitter(Log memory log) internal pure returns (address) { 166 | return RLPReader.toAddress(log.list[0]); 167 | } 168 | 169 | function getTopics( 170 | Log memory log 171 | ) internal pure returns (LogTopics memory) { 172 | return LogTopics(log.list[1].toList()); 173 | } 174 | 175 | function getData(Log memory log) internal pure returns (bytes memory) { 176 | return log.list[2].toBytes(); 177 | } 178 | 179 | function toRlpBytes(Log memory log) internal pure returns (bytes memory) { 180 | return log.data.toRlpBytes(); 181 | } 182 | 183 | // LogTopics methods 184 | function getField( 185 | LogTopics memory topics, 186 | uint256 index 187 | ) internal pure returns (RLPReader.RLPItem memory) { 188 | return topics.data[index]; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /contracts/libraries/Merkle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | library Merkle { 5 | function checkMembership( 6 | bytes32 leaf, 7 | uint256 index, 8 | bytes32 rootHash, 9 | bytes memory proof 10 | ) internal pure returns (bool) { 11 | require(proof.length % 32 == 0, "Invalid proof length"); 12 | uint256 proofHeight = proof.length / 32; 13 | // Proof of size n means, height of the tree is n+1. 14 | // In a tree of height n+1, max #leafs possible is 2 ^ n 15 | require(index < 2 ** proofHeight, "Leaf index is too big"); 16 | 17 | bytes32 proofElement; 18 | bytes32 computedHash = leaf; 19 | for (uint256 i = 32; i <= proof.length; i += 32) { 20 | assembly { 21 | proofElement := mload(add(proof, i)) 22 | } 23 | 24 | if (index % 2 == 0) { 25 | computedHash = keccak256( 26 | abi.encodePacked(computedHash, proofElement) 27 | ); 28 | } else { 29 | computedHash = keccak256( 30 | abi.encodePacked(proofElement, computedHash) 31 | ); 32 | } 33 | 34 | index = index / 2; 35 | } 36 | return computedHash == rootHash; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /contracts/libraries/MerklePatriciaProof.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | import { RLPReader } from "./RLPReader.sol"; 5 | 6 | library MerklePatriciaProof { 7 | /* 8 | * @dev Verifies a merkle patricia proof. 9 | * @param value The terminating value in the trie. 10 | * @param encodedPath The path in the trie leading to value. 11 | * @param rlpParentNodes The rlp encoded stack of nodes. 12 | * @param root The root hash of the trie. 13 | * @return The boolean validity of the proof. 14 | */ 15 | function verify( 16 | bytes memory value, 17 | bytes memory encodedPath, 18 | bytes memory rlpParentNodes, 19 | bytes32 root 20 | ) internal pure returns (bool) { 21 | RLPReader.RLPItem memory item = RLPReader.toRlpItem(rlpParentNodes); 22 | RLPReader.RLPItem[] memory parentNodes = RLPReader.toList(item); 23 | 24 | bytes memory currentNode; 25 | RLPReader.RLPItem[] memory currentNodeList; 26 | 27 | bytes32 nodeKey = root; 28 | uint256 pathPtr = 0; 29 | 30 | bytes memory path = _getNibbleArray(encodedPath); 31 | if (path.length == 0) { 32 | return false; 33 | } 34 | 35 | for (uint256 i = 0; i < parentNodes.length; i++) { 36 | if (pathPtr > path.length) { 37 | return false; 38 | } 39 | 40 | currentNode = RLPReader.toRlpBytes(parentNodes[i]); 41 | if (nodeKey != keccak256(currentNode)) { 42 | return false; 43 | } 44 | currentNodeList = RLPReader.toList(parentNodes[i]); 45 | 46 | if (currentNodeList.length == 17) { 47 | if (pathPtr == path.length) { 48 | if ( 49 | keccak256(RLPReader.toBytes(currentNodeList[16])) == 50 | keccak256(value) 51 | ) { 52 | return true; 53 | } else { 54 | return false; 55 | } 56 | } 57 | 58 | uint8 nextPathNibble = uint8(path[pathPtr]); 59 | if (nextPathNibble > 16) { 60 | return false; 61 | } 62 | nodeKey = bytes32( 63 | RLPReader.toUintStrict(currentNodeList[nextPathNibble]) 64 | ); 65 | pathPtr += 1; 66 | } else if (currentNodeList.length == 2) { 67 | uint256 traversed = _nibblesToTraverse( 68 | RLPReader.toBytes(currentNodeList[0]), 69 | path, 70 | pathPtr 71 | ); 72 | if (pathPtr + traversed == path.length) { 73 | //leaf node 74 | if ( 75 | keccak256(RLPReader.toBytes(currentNodeList[1])) == 76 | keccak256(value) 77 | ) { 78 | return true; 79 | } else { 80 | return false; 81 | } 82 | } 83 | 84 | //extension node 85 | if (traversed == 0) { 86 | return false; 87 | } 88 | 89 | pathPtr += traversed; 90 | nodeKey = bytes32(RLPReader.toUintStrict(currentNodeList[1])); 91 | } else { 92 | return false; 93 | } 94 | } 95 | } 96 | 97 | function _nibblesToTraverse( 98 | bytes memory encodedPartialPath, 99 | bytes memory path, 100 | uint256 pathPtr 101 | ) private pure returns (uint256) { 102 | uint256 len = 0; 103 | // encodedPartialPath has elements that are each two hex characters (1 byte), but partialPath 104 | // and slicedPath have elements that are each one hex character (1 nibble) 105 | bytes memory partialPath = _getNibbleArray(encodedPartialPath); 106 | bytes memory slicedPath = new bytes(partialPath.length); 107 | 108 | // pathPtr counts nibbles in path 109 | // partialPath.length is a number of nibbles 110 | for (uint256 i = pathPtr; i < pathPtr + partialPath.length; i++) { 111 | bytes1 pathNibble = path[i]; 112 | slicedPath[i - pathPtr] = pathNibble; 113 | } 114 | 115 | if (keccak256(partialPath) == keccak256(slicedPath)) { 116 | len = partialPath.length; 117 | } else { 118 | len = 0; 119 | } 120 | return len; 121 | } 122 | 123 | // bytes b must be hp encoded 124 | function _getNibbleArray( 125 | bytes memory b 126 | ) internal pure returns (bytes memory) { 127 | bytes memory nibbles = ""; 128 | if (b.length > 0) { 129 | uint8 offset; 130 | uint8 hpNibble = uint8(_getNthNibbleOfBytes(0, b)); 131 | if (hpNibble == 1 || hpNibble == 3) { 132 | nibbles = new bytes(b.length * 2 - 1); 133 | bytes1 oddNibble = _getNthNibbleOfBytes(1, b); 134 | nibbles[0] = oddNibble; 135 | offset = 1; 136 | } else { 137 | nibbles = new bytes(b.length * 2 - 2); 138 | offset = 0; 139 | } 140 | 141 | for (uint256 i = offset; i < nibbles.length; i++) { 142 | nibbles[i] = _getNthNibbleOfBytes(i - offset + 2, b); 143 | } 144 | } 145 | return nibbles; 146 | } 147 | 148 | function _getNthNibbleOfBytes( 149 | uint256 n, 150 | bytes memory str 151 | ) private pure returns (bytes1) { 152 | return 153 | bytes1( 154 | n % 2 == 0 ? uint8(str[n / 2]) / 0x10 : uint8(str[n / 2]) % 0x10 155 | ); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /contracts/libraries/RLPReader.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | library RLPReader { 5 | uint8 constant STRING_SHORT_START = 0x80; 6 | uint8 constant STRING_LONG_START = 0xb8; 7 | uint8 constant LIST_SHORT_START = 0xc0; 8 | uint8 constant LIST_LONG_START = 0xf8; 9 | uint8 constant WORD_SIZE = 32; 10 | 11 | struct RLPItem { 12 | uint256 len; 13 | uint256 memPtr; 14 | } 15 | 16 | struct Iterator { 17 | RLPItem item; // Item that's being iterated over. 18 | uint256 nextPtr; // Position of the next item in the list. 19 | } 20 | 21 | /* 22 | * @dev Returns the next element in the iteration. Reverts if it has not next element. 23 | * @param self The iterator. 24 | * @return The next element in the iteration. 25 | */ 26 | function next(Iterator memory self) internal pure returns (RLPItem memory) { 27 | require(hasNext(self)); 28 | 29 | uint256 ptr = self.nextPtr; 30 | uint256 itemLength = _itemLength(ptr); 31 | self.nextPtr = ptr + itemLength; 32 | 33 | return RLPItem(itemLength, ptr); 34 | } 35 | 36 | /* 37 | * @dev Returns true if the iteration has more elements. 38 | * @param self The iterator. 39 | * @return true if the iteration has more elements. 40 | */ 41 | function hasNext(Iterator memory self) internal pure returns (bool) { 42 | RLPItem memory item = self.item; 43 | return self.nextPtr < item.memPtr + item.len; 44 | } 45 | 46 | /* 47 | * @param item RLP encoded bytes 48 | */ 49 | function toRlpItem( 50 | bytes memory item 51 | ) internal pure returns (RLPItem memory) { 52 | uint256 memPtr; 53 | assembly { 54 | memPtr := add(item, 0x20) 55 | } 56 | 57 | return RLPItem(item.length, memPtr); 58 | } 59 | 60 | /* 61 | * @dev Create an iterator. Reverts if item is not a list. 62 | * @param self The RLP item. 63 | * @return An 'Iterator' over the item. 64 | */ 65 | function iterator( 66 | RLPItem memory self 67 | ) internal pure returns (Iterator memory) { 68 | require(isList(self)); 69 | 70 | uint256 ptr = self.memPtr + _payloadOffset(self.memPtr); 71 | return Iterator(self, ptr); 72 | } 73 | 74 | /* 75 | * @param item RLP encoded bytes 76 | */ 77 | function rlpLen(RLPItem memory item) internal pure returns (uint256) { 78 | return item.len; 79 | } 80 | 81 | /* 82 | * @param item RLP encoded bytes 83 | */ 84 | function payloadLen(RLPItem memory item) internal pure returns (uint256) { 85 | return item.len - _payloadOffset(item.memPtr); 86 | } 87 | 88 | /* 89 | * @param item RLP encoded list in bytes 90 | */ 91 | function toList( 92 | RLPItem memory item 93 | ) internal pure returns (RLPItem[] memory) { 94 | require(isList(item)); 95 | 96 | uint256 items = numItems(item); 97 | RLPItem[] memory result = new RLPItem[](items); 98 | 99 | uint256 memPtr = item.memPtr + _payloadOffset(item.memPtr); 100 | uint256 dataLen; 101 | for (uint256 i = 0; i < items; i++) { 102 | dataLen = _itemLength(memPtr); 103 | result[i] = RLPItem(dataLen, memPtr); 104 | memPtr = memPtr + dataLen; 105 | } 106 | 107 | return result; 108 | } 109 | 110 | // @return indicator whether encoded payload is a list. negate this function call for isData. 111 | function isList(RLPItem memory item) internal pure returns (bool) { 112 | if (item.len == 0) return false; 113 | 114 | uint8 byte0; 115 | uint256 memPtr = item.memPtr; 116 | assembly { 117 | byte0 := byte(0, mload(memPtr)) 118 | } 119 | 120 | if (byte0 < LIST_SHORT_START) return false; 121 | return true; 122 | } 123 | 124 | /* 125 | * @dev A cheaper version of keccak256(toRlpBytes(item)) that avoids copying memory. 126 | * @return keccak256 hash of RLP encoded bytes. 127 | */ 128 | function rlpBytesKeccak256( 129 | RLPItem memory item 130 | ) internal pure returns (bytes32) { 131 | uint256 ptr = item.memPtr; 132 | uint256 len = item.len; 133 | bytes32 result; 134 | assembly { 135 | result := keccak256(ptr, len) 136 | } 137 | return result; 138 | } 139 | 140 | function payloadLocation( 141 | RLPItem memory item 142 | ) internal pure returns (uint256, uint256) { 143 | uint256 offset = _payloadOffset(item.memPtr); 144 | uint256 memPtr = item.memPtr + offset; 145 | uint256 len = item.len - offset; // data length 146 | return (memPtr, len); 147 | } 148 | 149 | /* 150 | * @dev A cheaper version of keccak256(toBytes(item)) that avoids copying memory. 151 | * @return keccak256 hash of the item payload. 152 | */ 153 | function payloadKeccak256( 154 | RLPItem memory item 155 | ) internal pure returns (bytes32) { 156 | (uint256 memPtr, uint256 len) = payloadLocation(item); 157 | bytes32 result; 158 | assembly { 159 | result := keccak256(memPtr, len) 160 | } 161 | return result; 162 | } 163 | 164 | /** RLPItem conversions into data types **/ 165 | 166 | // @returns raw rlp encoding in bytes 167 | function toRlpBytes( 168 | RLPItem memory item 169 | ) internal pure returns (bytes memory) { 170 | bytes memory result = new bytes(item.len); 171 | if (result.length == 0) return result; 172 | 173 | uint256 ptr; 174 | assembly { 175 | ptr := add(0x20, result) 176 | } 177 | 178 | copy(item.memPtr, ptr, item.len); 179 | return result; 180 | } 181 | 182 | // any non-zero byte is considered true 183 | function toBoolean(RLPItem memory item) internal pure returns (bool) { 184 | require(item.len == 1); 185 | uint256 result; 186 | uint256 memPtr = item.memPtr; 187 | assembly { 188 | result := byte(0, mload(memPtr)) 189 | } 190 | 191 | return result == 0 ? false : true; 192 | } 193 | 194 | function toAddress(RLPItem memory item) internal pure returns (address) { 195 | // 1 byte for the length prefix 196 | require(item.len == 21); 197 | 198 | return address(uint160(toUint(item))); 199 | } 200 | 201 | function toUint(RLPItem memory item) internal pure returns (uint256) { 202 | require(item.len > 0 && item.len <= 33); 203 | 204 | uint256 offset = _payloadOffset(item.memPtr); 205 | uint256 len = item.len - offset; 206 | 207 | uint256 result; 208 | uint256 memPtr = item.memPtr + offset; 209 | assembly { 210 | result := mload(memPtr) 211 | 212 | // shfit to the correct location if neccesary 213 | if lt(len, 32) { 214 | result := div(result, exp(256, sub(32, len))) 215 | } 216 | } 217 | 218 | return result; 219 | } 220 | 221 | // enforces 32 byte length 222 | function toUintStrict(RLPItem memory item) internal pure returns (uint256) { 223 | // one byte prefix 224 | require(item.len == 33); 225 | 226 | uint256 result; 227 | uint256 memPtr = item.memPtr + 1; 228 | assembly { 229 | result := mload(memPtr) 230 | } 231 | 232 | return result; 233 | } 234 | 235 | function toBytes(RLPItem memory item) internal pure returns (bytes memory) { 236 | require(item.len > 0); 237 | 238 | uint256 offset = _payloadOffset(item.memPtr); 239 | uint256 len = item.len - offset; // data length 240 | bytes memory result = new bytes(len); 241 | 242 | uint256 destPtr; 243 | assembly { 244 | destPtr := add(0x20, result) 245 | } 246 | 247 | copy(item.memPtr + offset, destPtr, len); 248 | return result; 249 | } 250 | 251 | /* 252 | * Private Helpers 253 | */ 254 | 255 | // @return number of payload items inside an encoded list. 256 | function numItems(RLPItem memory item) private pure returns (uint256) { 257 | if (item.len == 0) return 0; 258 | 259 | uint256 count = 0; 260 | uint256 currPtr = item.memPtr + _payloadOffset(item.memPtr); 261 | uint256 endPtr = item.memPtr + item.len; 262 | while (currPtr < endPtr) { 263 | currPtr = currPtr + _itemLength(currPtr); // skip over an item 264 | count++; 265 | } 266 | 267 | return count; 268 | } 269 | 270 | // @return entire rlp item byte length 271 | function _itemLength(uint256 memPtr) private pure returns (uint256) { 272 | uint256 itemLen; 273 | uint256 byte0; 274 | assembly { 275 | byte0 := byte(0, mload(memPtr)) 276 | } 277 | 278 | if (byte0 < STRING_SHORT_START) itemLen = 1; 279 | else if (byte0 < STRING_LONG_START) 280 | itemLen = byte0 - STRING_SHORT_START + 1; 281 | else if (byte0 < LIST_SHORT_START) { 282 | assembly { 283 | let byteLen := sub(byte0, 0xb7) // # of bytes the actual length is 284 | memPtr := add(memPtr, 1) // skip over the first byte 285 | /* 32 byte word size */ 286 | let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to get the len 287 | itemLen := add(dataLen, add(byteLen, 1)) 288 | } 289 | } else if (byte0 < LIST_LONG_START) { 290 | itemLen = byte0 - LIST_SHORT_START + 1; 291 | } else { 292 | assembly { 293 | let byteLen := sub(byte0, 0xf7) 294 | memPtr := add(memPtr, 1) 295 | 296 | let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to the correct length 297 | itemLen := add(dataLen, add(byteLen, 1)) 298 | } 299 | } 300 | 301 | return itemLen; 302 | } 303 | 304 | // @return number of bytes until the data 305 | function _payloadOffset(uint256 memPtr) private pure returns (uint256) { 306 | uint256 byte0; 307 | assembly { 308 | byte0 := byte(0, mload(memPtr)) 309 | } 310 | 311 | if (byte0 < STRING_SHORT_START) return 0; 312 | else if ( 313 | byte0 < STRING_LONG_START || 314 | (byte0 >= LIST_SHORT_START && byte0 < LIST_LONG_START) 315 | ) return 1; 316 | else if (byte0 < LIST_SHORT_START) 317 | // being explicit 318 | return byte0 - (STRING_LONG_START - 1) + 1; 319 | else return byte0 - (LIST_LONG_START - 1) + 1; 320 | } 321 | 322 | /* 323 | * @param src Pointer to source 324 | * @param dest Pointer to destination 325 | * @param len Amount of memory to copy from the source 326 | */ 327 | function copy(uint256 src, uint256 dest, uint256 len) private pure { 328 | if (len == 0) return; 329 | 330 | // copy as many word sizes as possible 331 | for (; len >= WORD_SIZE; len -= WORD_SIZE) { 332 | assembly { 333 | mstore(dest, mload(src)) 334 | } 335 | 336 | src += WORD_SIZE; 337 | dest += WORD_SIZE; 338 | } 339 | 340 | if (len == 0) return; 341 | 342 | // left over bytes. Mask is used to remove unwanted bytes from the word 343 | uint256 mask = 256 ** (WORD_SIZE - len) - 1; 344 | 345 | assembly { 346 | let srcpart := and(mload(src), not(mask)) // zero out src 347 | let destpart := and(mload(dest), mask) // retrieve the bytes 348 | mstore(dest, or(destpart, srcpart)) 349 | } 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /contracts/mocks/ChildPoolMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | import { ChildPool } from "../ChildPool.sol"; 5 | 6 | contract ChildPoolMock is ChildPool { 7 | uint256 public value; 8 | 9 | event ValueSet(uint256 value); 10 | 11 | function setValue(uint256 value_) external { 12 | value = value_; 13 | emit ValueSet(value_); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /contracts/mocks/ExtendedMaticXMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | import { MaticX } from "../MaticX.sol"; 5 | 6 | contract ExtendedMaticXMock is MaticX { 7 | uint256 public value; 8 | 9 | event ValueSet(uint256 value); 10 | 11 | function setValue(uint256 value_) external { 12 | value = value_; 13 | emit ValueSet(value_); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /contracts/mocks/ExtendedValidatorRegistryMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | import { ValidatorRegistry } from "../ValidatorRegistry.sol"; 5 | 6 | contract ExtendedValidatorRegistryMock is ValidatorRegistry { 7 | uint256 public value; 8 | 9 | event ValueSet(uint256 value); 10 | 11 | function setValue(uint256 value_) external { 12 | value = value_; 13 | emit ValueSet(value_); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /contracts/mocks/FxRootMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | import "../FxBaseChildTunnel.sol"; 5 | 6 | contract FxRootMock { 7 | uint256 stateId; 8 | 9 | function sendMessageToChild( 10 | address fxChildTunnel, 11 | bytes memory _message 12 | ) external { 13 | FxBaseChildTunnel(fxChildTunnel).processMessageFromRoot( 14 | stateId, 15 | msg.sender, 16 | _message 17 | ); 18 | stateId++; 19 | } 20 | 21 | function sendMessageToChildWithAddress( 22 | address fxChildTunnel, 23 | address fxRootTunnelAddress, 24 | bytes memory _message 25 | ) external { 26 | FxBaseChildTunnel(fxChildTunnel).processMessageFromRoot( 27 | stateId, 28 | fxRootTunnelAddress, 29 | _message 30 | ); 31 | stateId++; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/mocks/MaticXFuzz.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | import { MaticX } from "../MaticX.sol"; 5 | 6 | contract MaticXFuzz is MaticX { 7 | function testSubmit(uint256 _amount) external { 8 | uint256 initialSupply = totalSupply(); 9 | this.submit(_amount); 10 | assert(totalSupply() == initialSupply + _amount); 11 | } 12 | 13 | function testRequestWithdraw(uint256 _amount) external { 14 | uint256 initialSupply = totalSupply(); 15 | this.submit(_amount); 16 | this.requestWithdraw(_amount); 17 | assert(totalSupply() == initialSupply - _amount); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contracts/mocks/MaticXMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | import { MaticX } from "../MaticX.sol"; 5 | 6 | contract MaticXMock is MaticX { 7 | uint256 public value; 8 | 9 | event ValueSet(uint256 value); 10 | 11 | function setValue(uint256 value_) external { 12 | value = value_; 13 | emit ValueSet(value_); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /contracts/mocks/PolygonMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract PolygonMock is ERC20 { 7 | constructor() ERC20("polygon", "POL") {} 8 | 9 | function mint(uint256 _amount) external { 10 | _mint(msg.sender, _amount); 11 | } 12 | 13 | function mintTo(address _to, uint256 _amount) external { 14 | _mint(_to, _amount); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /contracts/mocks/StakeManagerMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import { ValidatorShareMock } from "./ValidatorShareMock.sol"; 6 | 7 | contract StakeManagerMock { 8 | enum Status { 9 | Inactive, 10 | Active, 11 | Locked, 12 | Unstaked 13 | } 14 | 15 | struct Validator { 16 | uint256 amount; 17 | uint256 reward; 18 | uint256 activationEpoch; 19 | uint256 deactivationEpoch; 20 | uint256 jailTime; 21 | address signer; 22 | address contractAddress; 23 | Status status; 24 | uint256 commissionRate; 25 | uint256 lastCommissionUpdate; 26 | uint256 delegatorsReward; 27 | uint256 delegatedAmount; 28 | uint256 initialRewardPerStake; 29 | } 30 | 31 | event UpdateSigner(uint256 validatorId, bytes signerPubkey); 32 | event UpdateCommissionRate(uint256 validatorId, uint256 newCommissionRate); 33 | 34 | mapping(uint256 => Validator) smValidators; 35 | struct State { 36 | address token; 37 | address stakeNFT; 38 | uint256 id; 39 | mapping(address => uint256) validators; 40 | mapping(uint256 => address) Owners; 41 | mapping(uint256 => uint256) stakedAmount; 42 | mapping(uint256 => address) signer; 43 | mapping(uint256 => address) validatorShares; 44 | mapping(address => uint256) delegator2Amount; 45 | uint256 epoch; 46 | } 47 | 48 | State private state; 49 | 50 | constructor(address _token, address _stakeNFT) { 51 | state.token = _token; 52 | state.stakeNFT = _stakeNFT; 53 | } 54 | 55 | function unstake(uint256 _validatorId) external { 56 | smValidators[_validatorId].deactivationEpoch = block.timestamp; 57 | } 58 | 59 | function createValidator(uint256 _validatorId) external { 60 | smValidators[_validatorId] = Validator({ 61 | amount: 0, 62 | reward: 0, 63 | activationEpoch: block.timestamp, 64 | deactivationEpoch: 0, 65 | jailTime: 0, 66 | signer: address(this), 67 | contractAddress: address( 68 | new ValidatorShareMock(state.token, address(this), _validatorId) 69 | ), 70 | status: Status.Active, 71 | commissionRate: 0, 72 | lastCommissionUpdate: 0, 73 | delegatorsReward: 0, 74 | delegatedAmount: 0, 75 | initialRewardPerStake: 0 76 | }); 77 | state.validatorShares[_validatorId] = address( 78 | new ValidatorShareMock(state.token, address(this), _validatorId) 79 | ); 80 | } 81 | 82 | function getValidatorId(address _user) external view returns (uint256) { 83 | return state.validators[_user]; 84 | } 85 | 86 | function getValidatorContract( 87 | uint256 _validatorId 88 | ) external view returns (address) { 89 | return state.validatorShares[_validatorId]; 90 | } 91 | 92 | function withdrawRewards(uint256) external { 93 | IERC20(state.token).transfer(msg.sender, 1000); 94 | } 95 | 96 | function unstakeClaim(uint256 _validatorId) external { 97 | IERC20(state.token).transfer( 98 | msg.sender, 99 | IERC20(state.token).balanceOf(address(this)) 100 | ); 101 | state.delegator2Amount[msg.sender] = 0; 102 | smValidators[_validatorId].status = Status.Unstaked; 103 | } 104 | 105 | function validatorStake( 106 | uint256 _validatorId 107 | ) external view returns (uint256) { 108 | return state.stakedAmount[_validatorId]; 109 | } 110 | 111 | function withdrawalDelay() external pure returns (uint256) { 112 | return (2 ** 13); 113 | } 114 | 115 | function delegationDeposit( 116 | uint256 _validatorId, 117 | uint256 _amount, 118 | address _delegator 119 | ) external returns (bool) { 120 | state.delegator2Amount[msg.sender] += _amount; 121 | state.stakedAmount[_validatorId] += _amount; 122 | IERC20(state.token).transferFrom(_delegator, address(this), _amount); 123 | return IERC20(state.token).transfer(msg.sender, _amount); 124 | } 125 | 126 | function setCurrentEpoch(uint256 _currentEpoch) external { 127 | state.epoch = _currentEpoch; 128 | } 129 | 130 | function epoch() external view returns (uint256) { 131 | return state.epoch; 132 | } 133 | 134 | function slash(uint256 _validatorId) external { 135 | smValidators[_validatorId].status = Status.Locked; 136 | state.stakedAmount[_validatorId] -= 100; 137 | } 138 | 139 | function validators( 140 | uint256 _validatorId 141 | ) external view returns (Validator memory) { 142 | return smValidators[_validatorId]; 143 | } 144 | 145 | function setEpoch(uint256 _epoch) external { 146 | state.epoch = _epoch; 147 | } 148 | 149 | function migrateDelegation( 150 | uint256 _fromValidatorId, 151 | uint256 _toValidatorId, 152 | uint256 _amount 153 | ) public { 154 | state.stakedAmount[_fromValidatorId] -= _amount; 155 | state.stakedAmount[_toValidatorId] += _amount; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /contracts/mocks/ValidatorRegistryMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | import { ValidatorRegistry } from "../ValidatorRegistry.sol"; 5 | 6 | contract ValidatorRegistryMock is ValidatorRegistry { 7 | uint256 public value; 8 | 9 | event ValueSet(uint256 value); 10 | 11 | function setValue(uint256 value_) external { 12 | value = value_; 13 | emit ValueSet(value_); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /contracts/mocks/ValidatorShareMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity 0.8.7; 3 | 4 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | 6 | contract ValidatorShareMock { 7 | struct DelegatorUnbond { 8 | uint256 shares; 9 | uint256 withdrawEpoch; 10 | } 11 | 12 | uint256 constant REWARD_PRECISION = 10 ** 25; 13 | 14 | address public token; 15 | 16 | bool public delegation; 17 | uint256 mAmount; 18 | uint256 public rewardPerShare; 19 | 20 | uint256 public totalShares; 21 | uint256 public withdrawPool; 22 | uint256 public totalStaked; 23 | uint256 public totalWithdrawPoolShares; 24 | uint256 public validatorId; 25 | 26 | mapping(address => mapping(uint256 => uint256)) 27 | public user2WithdrawPoolShare; 28 | mapping(address => uint256) public unbondNonces; 29 | mapping(address => uint256) public initalRewardPerShare; 30 | 31 | address stakeManager; 32 | address public stakingLogger; 33 | 34 | constructor(address _token, address _stakeManager, uint256 _id) { 35 | token = _token; 36 | stakeManager = _stakeManager; 37 | validatorId = _id; 38 | delegation = true; 39 | } 40 | 41 | function buyVoucher(uint256 _amount, uint256) external returns (uint256) { 42 | return _buyVoucher(_amount); 43 | } 44 | 45 | function buyVoucherPOL( 46 | uint256 _amount, 47 | uint256 48 | ) external returns (uint256) { 49 | return _buyVoucher(_amount); 50 | } 51 | 52 | function sellVoucher_new(uint256 _claimAmount, uint256) external { 53 | _sellVoucher_new(_claimAmount); 54 | } 55 | 56 | function sellVoucher_newPOL(uint256 _claimAmount, uint256) external { 57 | _sellVoucher_new(_claimAmount); 58 | } 59 | 60 | function unstakeClaimTokens_new(uint256 _unbondNonce) external { 61 | _unstakeClaimTokens_new(_unbondNonce); 62 | } 63 | 64 | function unstakeClaimTokens_newPOL(uint256 _unbondNonce) external { 65 | _unstakeClaimTokens_new(_unbondNonce); 66 | } 67 | 68 | function restake() external returns (uint256, uint256) { 69 | uint256 liquidRewards = _withdrawReward(msg.sender); 70 | // uint256 amountRestaked = buyVoucher(liquidRewards, 0); 71 | uint256 amountRestaked = 0; 72 | 73 | return (amountRestaked, liquidRewards - amountRestaked); 74 | } 75 | 76 | function calculateRewards() private view returns (uint256) { 77 | uint256 thisBalance = IERC20(token).balanceOf(address(this)); 78 | return thisBalance - (totalStaked + withdrawPool); 79 | } 80 | 81 | function withdrawRewards() external { 82 | _withdrawReward(msg.sender); 83 | } 84 | 85 | function withdrawRewardsPOL() external { 86 | _withdrawReward(msg.sender); 87 | } 88 | 89 | function getTotalStake(address) external view returns (uint256, uint256) { 90 | //getTotalStake returns totalStake of msg.sender but we need withdrawPool 91 | return (totalStaked, 1); 92 | } 93 | 94 | function setMinAmount(uint256 _minAmount) public { 95 | mAmount = _minAmount; 96 | } 97 | 98 | function minAmount() public view returns (uint256) { 99 | return mAmount; 100 | } 101 | 102 | function _withdrawReward(address _user) private returns (uint256) { 103 | uint256 reward = calculateRewards(); 104 | require(reward >= minAmount(), "Reward < minAmount"); 105 | IERC20(token).transfer(_user, reward); 106 | 107 | return reward; 108 | } 109 | 110 | function unbonds_new( 111 | address _address, 112 | uint256 _unbondNonce 113 | ) external view returns (DelegatorUnbond memory) { 114 | DelegatorUnbond memory unbond = DelegatorUnbond( 115 | user2WithdrawPoolShare[_address][_unbondNonce], 116 | 2 117 | ); 118 | return unbond; 119 | } 120 | 121 | function _buyVoucher(uint256 _amount) private returns (uint256) { 122 | uint256 totalAmount = IERC20(token).balanceOf(address(this)); 123 | 124 | uint256 shares = totalAmount != 0 125 | ? (_amount * totalShares) / totalAmount 126 | : _amount; 127 | 128 | totalShares += shares; 129 | totalStaked += _amount; 130 | 131 | return 1; 132 | } 133 | 134 | function _sellVoucher_new(uint256 _claimAmount) private { 135 | uint256 unbondNonce = unbondNonces[msg.sender] + 1; 136 | 137 | withdrawPool += _claimAmount; 138 | totalWithdrawPoolShares += _claimAmount; 139 | totalStaked -= _claimAmount; 140 | 141 | unbondNonces[msg.sender] = unbondNonce; 142 | user2WithdrawPoolShare[msg.sender][unbondNonce] = _claimAmount; 143 | } 144 | 145 | function _unstakeClaimTokens_new(uint256 _unbondNonce) private { 146 | uint256 withdrawPoolShare = user2WithdrawPoolShare[msg.sender][ 147 | _unbondNonce 148 | ]; 149 | uint256 amount2Transfer = (withdrawPoolShare * withdrawPool) / 150 | totalWithdrawPoolShares; 151 | 152 | withdrawPool -= amount2Transfer; 153 | totalShares -= withdrawPoolShare; 154 | totalWithdrawPoolShares -= withdrawPoolShare; 155 | IERC20(token).transfer(msg.sender, amount2Transfer); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /docs/Amoy-Testnet.md: -------------------------------------------------------------------------------- 1 | ## Deployment Information 2 | 3 | ### MaticX 4 | 5 | - Proxy address: [0xFD837d45dd6Af18D039122301C3331D4A4307351](https://amoy.polygonscan.com/address/0xFD837d45dd6Af18D039122301C3331D4A4307351) 6 | - Implementation address: [0xFAFbbA44f12066B4BF8EB0d05dD26a5567D3bb2F](https://amoy.polygonscan.com/address/0xFAFbbA44f12066B4BF8EB0d05dD26a5567D3bb2F) 7 | - Proxy admin address: [0x2c589ae5c64Be500585A712892c71ffd105A184E](https://amoy.polygonscan.com/address/0x2c589ae5c64Be500585A712892c71ffd105A184E) 8 | 9 | ### Child Pool 10 | 11 | - Proxy address: [0x54a994B66FA7eA7160D6281002B56a041db0b5a5](https://amoy.polygonscan.com/address/0x54a994B66FA7eA7160D6281002B56a041db0b5a5) 12 | - Implementation address: [0xE98fc808E8aE8025a1D17d6F4Fbc3Df226788438](https://amoy.polygonscan.com/address/0xE98fc808E8aE8025a1D17d6F4Fbc3Df226788438) 13 | - Proxy admin address: [0x9eA2a15715629A67f08815CDf6608E35A2371691](https://amoy.polygonscan.com/address/0x9eA2a15715629A67f08815CDf6608E35A2371691) 14 | 15 | ### Fx State Child Tunnel 16 | 17 | - Address: [0xDC5e26af83c1694A655beBe22689f12A42573d94](https://amoy.polygonscan.com/address/0xDC5e26af83c1694A655beBe22689f12A42573d94) 18 | 19 | ### Fx Child 20 | 21 | - Address: [0xE5930336866d0388f0f745A2d9207C7781047C0f](https://amoy.polygonscan.com/address/0xE5930336866d0388f0f745A2d9207C7781047C0f) 22 | 23 | ### Manager 24 | 25 | - Address: [0x8C6B3eE457b193A49794df466957441b4AccD102](https://amoy.polygonscan.com/address/0x8C6B3eE457b193A49794df466957441b4AccD102) 26 | 27 | ### Treasury 28 | 29 | - Address: [0x8C6B3eE457b193A49794df466957441b4AccD102](https://amoy.polygonscan.com/address/0x8C6B3eE457b193A49794df466957441b4AccD102) 30 | -------------------------------------------------------------------------------- /docs/Ethereum-Mainnet-(Preprod).md: -------------------------------------------------------------------------------- 1 | ## Deployment Information 2 | 3 | ### TimelockController 4 | 5 | - Address: [0x420d319efe3Dd573Ccf3219dDdf60C83902B9f10](https://etherscan.io/address/0x420d319efe3Dd573Ccf3219dDdf60C83902B9f10) 6 | 7 | ### Validator Registry 8 | 9 | - Proxy address: [0xcEbaFD96Df8a1Cc63ee77770296F14B68089Ab04](https://etherscan.io/address/0xcEbaFD96Df8a1Cc63ee77770296F14B68089Ab04) 10 | - Implementation address: [0xaC72d5A7078F5E6B1661c0c4fe036573c771e568](https://etherscan.io/address/0xaC72d5A7078F5E6B1661c0c4fe036573c771e568) 11 | - Proxy admin address: [0x5cEB726baBE01776D47b25247f16Cc8Ca439A8F0](https://etherscan.io/address/0x5cEB726baBE01776D47b25247f16Cc8Ca439A8F0) 12 | 13 | ### MaticX 14 | 15 | - Proxy address: [0xD22dd194B8ec2abEeeFAC36a69cd8898FB6C43b3](https://etherscan.io/address/0xD22dd194B8ec2abEeeFAC36a69cd8898FB6C43b3) 16 | - Implementation address: [0xA5Fdb8D80621AA2DE1Ac5600EF9997cb88D1fEA0](https://etherscan.io/address/0xA5Fdb8D80621AA2DE1Ac5600EF9997cb88D1fEA0) 17 | - Proxy admin address: [0xC7c319e04Bf69f8F5387d3A7a79d842f750bAde5](https://etherscan.io/address/0xC7c319e04Bf69f8F5387d3A7a79d842f750bAde5) 18 | 19 | ### Stake Manager 20 | 21 | - Proxy address: [0x5e3Ef299fDDf15eAa0432E6e66473ace8c13D908](https://etherscan.io/address/0x5e3Ef299fDDf15eAa0432E6e66473ace8c13D908) 22 | - Implementation address: [0xbA9Ac3C9983a3e967f0f387c75cCbD38Ad484963](https://etherscan.io/address/0xbA9Ac3C9983a3e967f0f387c75cCbD38Ad484963) 23 | 24 | ### Fx State Root Tunnel 25 | 26 | - Address: [0xcD7AdBBF3030AFFa7766606c5EA9E016141422Ca](https://etherscan.io/address/0xcD7AdBBF3030AFFa7766606c5EA9E016141422Ca) 27 | 28 | ### Checkpoint Manager 29 | 30 | - Address: [0x86e4dc95c7fbdbf52e33d563bbdb00823894c287](https://etherscan.io/address/0x86e4dc95c7fbdbf52e33d563bbdb00823894c287) 31 | 32 | ### Fx Root 33 | 34 | - Address: [0xfe5e5D361b2ad62c541bAb87C45a0B9B018389a2](https://etherscan.io/address/0xfe5e5D361b2ad62c541bAb87C45a0B9B018389a2) 35 | 36 | ### Matic Token 37 | 38 | - Address: [0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0](https://etherscan.io/address/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0) 39 | 40 | ### Polygon Ecosystem Token 41 | 42 | - Address: [0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6](https://etherscan.io/address/0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6) 43 | 44 | ### Polygon Migration 45 | 46 | - Proxy address: [0x29e7DF7b6A1B2b07b731457f499E1696c60E2C4e](https://etherscan.io/address/0x29e7DF7b6A1B2b07b731457f499E1696c60E2C4e) 47 | - Implementation address: [0x550B7CDaC6F5a0d9e840505c3Df74aC045530446](https://etherscan.io/address/0x550B7CDaC6F5a0d9e840505c3Df74aC045530446) 48 | 49 | ### Manager 50 | 51 | - Address: [0xf9CBb2BF3f14c9F6De3F16fc0da4bAaDa25ebDC7](https://etherscan.io/address/0xf9CBb2BF3f14c9F6De3F16fc0da4bAaDa25ebDC7) 52 | 53 | ### Treasury 54 | 55 | - Address: [0xf9CBb2BF3f14c9F6De3F16fc0da4bAaDa25ebDC7](https://etherscan.io/address/0xf9CBb2BF3f14c9F6De3F16fc0da4bAaDa25ebDC7) 56 | 57 | ### Deployer 58 | 59 | - Address: [0x6FCBE6C2d6f6c890fD0cC5b0288E2D474D90BB1F](https://etherscan.io/address/0x6FCBE6C2d6f6c890fD0cC5b0288E2D474D90BB1F) 60 | -------------------------------------------------------------------------------- /docs/Ethereum-Mainnet-(Prod).md: -------------------------------------------------------------------------------- 1 | ## Deployment Information 2 | 3 | ### ProxyAdmin 4 | 5 | - Address: [0x6CBd89A4919E39Ad4c7718B04443CC1722B2cB2A](https://etherscan.io/address/0x6CBd89A4919E39Ad4c7718B04443CC1722B2cB2A) 6 | 7 | ### TimelockController 8 | 9 | - Address: [0x20Ea6f63de406040E1e4B67aD98E84A0Eb3778Be](https://etherscan.io/address/0x20Ea6f63de406040E1e4B67aD98E84A0Eb3778Be) 10 | 11 | ### Validator Registry 12 | 13 | - Proxy address: [0xf556442D5B77A4B0252630E15d8BbE2160870d77](https://etherscan.io/address/0xf556442D5B77A4B0252630E15d8BbE2160870d77) 14 | - Old implementation address: [0x0a7f554AbFBD710D9CDFB7Cf88217a91acA7457D](https://etherscan.io/address/0x0a7f554AbFBD710D9CDFB7Cf88217a91acA7457D) 15 | - New implementation address: [0xaC72d5A7078F5E6B1661c0c4fe036573c771e568](https://etherscan.io/address/0xaC72d5A7078F5E6B1661c0c4fe036573c771e568) 16 | 17 | ### MaticX 18 | 19 | - Proxy address: [0xf03A7Eb46d01d9EcAA104558C732Cf82f6B6B645](https://etherscan.io/address/0xf03A7Eb46d01d9EcAA104558C732Cf82f6B6B645) 20 | - Old implementation address: [0x5a78f4BD60C92FCbbf1C941Bc1136491D2896b35](https://etherscan.io/address/0x5a78f4BD60C92FCbbf1C941Bc1136491D2896b35) 21 | - New implementation address: [0xA5Fdb8D80621AA2DE1Ac5600EF9997cb88D1fEA0](https://etherscan.io/address/0xA5Fdb8D80621AA2DE1Ac5600EF9997cb88D1fEA0) 22 | 23 | ### Stake Manager 24 | 25 | - Proxy address: [0x5e3Ef299fDDf15eAa0432E6e66473ace8c13D908](https://etherscan.io/address/0x5e3Ef299fDDf15eAa0432E6e66473ace8c13D908) 26 | - Implementation address: [0xbA9Ac3C9983a3e967f0f387c75cCbD38Ad484963](https://etherscan.io/address/0xbA9Ac3C9983a3e967f0f387c75cCbD38Ad484963) 27 | 28 | ### Fx State Root Tunnel 29 | 30 | - Address: [0x40FB804Cc07302b89EC16a9f8d040506f64dFe29](https://etherscan.io/address/0x40FB804Cc07302b89EC16a9f8d040506f64dFe29) 31 | 32 | ### Checkpoint Manager 33 | 34 | - Address: [0x86e4dc95c7fbdbf52e33d563bbdb00823894c287](https://etherscan.io/address/0x86e4dc95c7fbdbf52e33d563bbdb00823894c287) 35 | 36 | ### Fx Root 37 | 38 | - Address: [0xfe5e5D361b2ad62c541bAb87C45a0B9B018389a2](https://etherscan.io/address/0xfe5e5D361b2ad62c541bAb87C45a0B9B018389a2) 39 | 40 | ### Matic Token 41 | 42 | - Address: [0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0](https://etherscan.io/address/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0) 43 | 44 | ### Polygon Ecosystem Token 45 | 46 | - Address: [0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6](https://etherscan.io/address/0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6) 47 | 48 | ### Polygon Migration 49 | 50 | - Proxy address: [0x29e7DF7b6A1B2b07b731457f499E1696c60E2C4e](https://etherscan.io/address/0x29e7DF7b6A1B2b07b731457f499E1696c60E2C4e) 51 | - Implementation address: [0x550B7CDaC6F5a0d9e840505c3Df74aC045530446](https://etherscan.io/address/0x550B7CDaC6F5a0d9e840505c3Df74aC045530446) 52 | 53 | ### Manager 54 | 55 | - Address: [0x80A43dd35382C4919991C5Bca7f46Dd24Fde4C67](https://etherscan.io/address/0x80A43dd35382C4919991C5Bca7f46Dd24Fde4C67) 56 | 57 | ### Treasury 58 | 59 | - Address: [0x01422247a1d15BB4FcF91F5A077Cf25BA6460130](https://etherscan.io/address/0x01422247a1d15BB4FcF91F5A077Cf25BA6460130) 60 | 61 | ### Deployer 62 | 63 | - Address: [0x75db63125A4f04E59A1A2Ab4aCC4FC1Cd5Daddd5](https://etherscan.io/address/0x75db63125A4f04E59A1A2Ab4aCC4FC1Cd5Daddd5) 64 | -------------------------------------------------------------------------------- /docs/Home.md: -------------------------------------------------------------------------------- 1 | # MaticX 2 | 3 | ## Deployment Information 4 | 5 | - [Ethereum Mainnet (Prod)]() 6 | - [Ethereum Mainnet (Preprod)]() 7 | - [Polygon Mainnet (Prod)]() 8 | - [Polygon Mainnet (Preprod)]() 9 | - [Sepolia Testnet](https://github.com/stader-labs/maticX/wiki/Sepolia-Testnet) 10 | - [Amoy Testnet](https://github.com/stader-labs/maticX/wiki/Amoy-Testnet) 11 | -------------------------------------------------------------------------------- /docs/Polygon-Mainnet-(Preprod).md: -------------------------------------------------------------------------------- 1 | ## Deployment Information 2 | 3 | ### MaticX 4 | 5 | - Proxy address: [0x797Ea002716E9a91F764721bBf3C4F34548B87F8](https://polygonscan.com/address/0x797Ea002716E9a91F764721bBf3C4F34548B87F8) 6 | - Implementation address: [0x4968047b03c1eaDed22e2db80c28bC15169D8D8e](https://polygonscan.com/address/0x4968047b03c1eaDed22e2db80c28bC15169D8D8e) 7 | - Proxy admin address: [0x492ab6231D1ceDC4A964045A0C733e55d35F8dC0](https://polygonscan.com/address/0x492ab6231D1ceDC4A964045A0C733e55d35F8dC0) 8 | 9 | ### Child Pool 10 | 11 | - Proxy address: [0x86182711ee76085444FD66E8a4d58A15f9b6EfAC](https://polygonscan.com/address/0x86182711ee76085444FD66E8a4d58A15f9b6EfAC) 12 | - Implementation address: [0x16D7D971Fe6D98E235E20Ef304f7f811429c6329](https://polygonscan.com/address/0x16D7D971Fe6D98E235E20Ef304f7f811429c6329) 13 | - Proxy admin address: [0x62FAfFA589a8a1a797E41079A86235Cf3Bb82A4D](https://polygonscan.com/address/0x62FAfFA589a8a1a797E41079A86235Cf3Bb82A4D) 14 | 15 | ### Fx State Child Tunnel 16 | 17 | - Address: [0x976BdbC06B84349C003A4C7Afeb2bAbdfd689bAa](https://polygonscan.com/address/0x976BdbC06B84349C003A4C7Afeb2bAbdfd689bAa) 18 | 19 | ### Fx Child 20 | 21 | - Address: [0x6FCBE6C2d6f6c890fD0cC5b0288E2D474D90BB1F](https://amoy.polygonscan.com/address/0x6FCBE6C2d6f6c890fD0cC5b0288E2D474D90BB1F) 22 | 23 | ### Manager 24 | 25 | - Address: [0x6FCBE6C2d6f6c890fD0cC5b0288E2D474D90BB1F](https://polygonscan.com/address/0x6FCBE6C2d6f6c890fD0cC5b0288E2D474D90BB1F) 26 | 27 | ### Treasury 28 | 29 | - Address: [0x6FCBE6C2d6f6c890fD0cC5b0288E2D474D90BB1F](https://polygonscan.com/address/0x6FCBE6C2d6f6c890fD0cC5b0288E2D474D90BB1F) 30 | -------------------------------------------------------------------------------- /docs/Polygon-Mainnet-(Prod).md: -------------------------------------------------------------------------------- 1 | ## Deployment Information 2 | 3 | ### Proxy Admin 4 | 5 | - Address: [0x79f2Ec80e2B9fB995D9deBda4d9026eF7A1507b9](https://polygonscan.com/address/0x79f2Ec80e2B9fB995D9deBda4d9026eF7A1507b9) 6 | 7 | ### MaticX 8 | 9 | - Proxy address: [0xfa68FB4628DFF1028CFEc22b4162FCcd0d45efb6](https://polygonscan.com/address/0xfa68FB4628DFF1028CFEc22b4162FCcd0d45efb6) 10 | - Implementation address: [0x44cc2b69710E68593a01B3d42d9a1390Ee564BBf](https://polygonscan.com/address/0x44cc2b69710E68593a01B3d42d9a1390Ee564BBf) 11 | 12 | ### Child Pool 13 | 14 | - Proxy address: [0xfd225C9e6601C9d38d8F98d8731BF59eFcF8C0E3](https://polygonscan.com/address/0xfd225C9e6601C9d38d8F98d8731BF59eFcF8C0E3) 15 | - Implementation address: [0xd6f5781b49f774aebdea7ab52671b0d72d36b6dd](https://polygonscan.com/address/0xd6f5781b49f774aebdea7ab52671b0d72d36b6dd) 16 | 17 | ### Fx State Child Tunnel 18 | 19 | - Address: [0x97E58a6950D86d751082D5e1d350e74c19047570](https://polygonscan.com/address/0x97E58a6950D86d751082D5e1d350e74c19047570) 20 | 21 | ### Fx Child 22 | 23 | - Address: [0x8397259c983751DAf40400790063935a11afa28a](https://amoy.polygonscan.com/address/0x8397259c983751DAf40400790063935a11afa28a) 24 | 25 | ### Rate Provider 26 | 27 | - Address: [0xeE652bbF72689AA59F0B8F981c9c90e2A8Af8d8f](https://polygonscan.com/address/0xeE652bbF72689AA59F0B8F981c9c90e2A8Af8d8f) 28 | 29 | ### Multisig 30 | 31 | - Address: [0x51358004cFe135E64453d7F6a0dC433CAba09A2a](https://polygonscan.com/address/0x51358004cFe135E64453d7F6a0dC433CAba09A2a) 32 | 33 | ### Manager 34 | 35 | - Address: [0x80A43dd35382C4919991C5Bca7f46Dd24Fde4C67](https://polygonscan.com/address/0x80A43dd35382C4919991C5Bca7f46Dd24Fde4C67) 36 | 37 | ### Treasury 38 | 39 | - Address: [0x80A43dd35382C4919991C5Bca7f46Dd24Fde4C67](https://polygonscan.com/address/0x80A43dd35382C4919991C5Bca7f46Dd24Fde4C67) 40 | -------------------------------------------------------------------------------- /docs/Sepolia-Testnet.md: -------------------------------------------------------------------------------- 1 | ## Deployment Information 2 | 3 | ### ProxyAdmin 4 | 5 | - Address: [0xFD837d45dd6Af18D039122301C3331D4A4307351](https://sepolia.etherscan.io/address/0xFD837d45dd6Af18D039122301C3331D4A4307351) 6 | 7 | ### TimelockController 8 | 9 | - Address: [0x5039f9ccD7D8166fbc6F06C10975869ab9E8D108](https://sepolia.etherscan.io/address/0x5039f9ccD7D8166fbc6F06C10975869ab9E8D108) 10 | 11 | ### Validator Registry 12 | 13 | - Proxy address: [0xE98fc808E8aE8025a1D17d6F4Fbc3Df226788438](https://sepolia.etherscan.io/address/0xE98fc808E8aE8025a1D17d6F4Fbc3Df226788438) 14 | - Implementation address: [0xDc70b17ADaa392B13B3120D7a3E7587aEe9d53B2](https://sepolia.etherscan.io/address/0xDc70b17ADaa392B13B3120D7a3E7587aEe9d53B2) 15 | 16 | ### MaticX 17 | 18 | - Proxy address: [0xB51AAb302085F436204c4B1964fBE74517B2D4b6](https://sepolia.etherscan.io/address/0xB51AAb302085F436204c4B1964fBE74517B2D4b6) 19 | - Implementation address: [0x2970394b40154352Ed2aF00a354F2d0DEcf7b4FB](https://sepolia.etherscan.io/address/0x2970394b40154352Ed2aF00a354F2d0DEcf7b4FB) 20 | 21 | ### Stake Manager 22 | 23 | - Proxy address: [0x4AE8f648B1Ec892B6cc68C89cc088583964d08bE](https://sepolia.etherscan.io/address/0x4AE8f648B1Ec892B6cc68C89cc088583964d08bE) 24 | - Implementation address: [0xE3104cC25C94b21a162d316064fe50fDDA0635aC](https://sepolia.etherscan.io/address/0xE3104cC25C94b21a162d316064fe50fDDA0635aC) 25 | 26 | ### Fx State Root Tunnel 27 | 28 | - Address: [0x1316e16f56a5A05538277906E12b77F2B39B4d79](https://sepolia.etherscan.io/address/0x1316e16f56a5A05538277906E12b77F2B39B4d79) 29 | 30 | ### Checkpoint Manager 31 | 32 | - Address: [0xbd07D7E1E93c8d4b2a261327F3C28a8EA7167209](https://sepolia.etherscan.io/address/0xbd07D7E1E93c8d4b2a261327F3C28a8EA7167209) 33 | 34 | ### Fx Root 35 | 36 | - Address: [0x0E13EBEdDb8cf9f5987512d5E081FdC2F5b0991e](https://sepolia.etherscan.io/address/0x0E13EBEdDb8cf9f5987512d5E081FdC2F5b0991e) 37 | 38 | ### Matic Token 39 | 40 | - Address: [0x3fd0A53F4Bf853985a95F4Eb3F9C9FDE1F8e2b53](https://sepolia.etherscan.io/address/0x3fd0A53F4Bf853985a95F4Eb3F9C9FDE1F8e2b53) 41 | 42 | ### Polygon Ecosystem Token 43 | 44 | - Address: [0x44499312f493F62f2DFd3C6435Ca3603EbFCeeBa](https://sepolia.etherscan.io/address/0x44499312f493F62f2DFd3C6435Ca3603EbFCeeBa) 45 | 46 | ### Polygon Migration 47 | 48 | - Proxy address: [0x3A3B750E7d4d389Bc1d0be20E5D09530F82B9911](https://sepolia.etherscan.io/address/0x3A3B750E7d4d389Bc1d0be20E5D09530F82B9911) 49 | - Implementation Address: [0xC70198ad91082c4d6eEb70d991cc4B2b61Cb3d1E](https://sepolia.etherscan.io/address/0xC70198ad91082c4d6eEb70d991cc4B2b61Cb3d1E) 50 | 51 | ### Manager 52 | 53 | - Address: [0x369B31971250859d3AD37E7cEDCF42AA5CF2C4F4](https://sepolia.etherscan.io/address/0x369B31971250859d3AD37E7cEDCF42AA5CF2C4F4) 54 | 55 | ### Treasury 56 | 57 | - Address: [0xdeb90df43BBa8FC0e2C08C54dC0F48cfc694F896](https://sepolia.etherscan.io/address/0xdeb90df43BBa8FC0e2C08C54dC0F48cfc694F896) 58 | 59 | ### Deployer 60 | 61 | - Address: [0x8C6B3eE457b193A49794df466957441b4AccD102](https://sepolia.etherscan.io/address/0x8C6B3eE457b193A49794df466957441b4AccD102) 62 | -------------------------------------------------------------------------------- /echidna.config.yaml: -------------------------------------------------------------------------------- 1 | testMode: assertion 2 | testLimit: 10000 3 | cryticArgs: 4 | - --compile-force-framework 5 | - hardhat 6 | - --solc-remaps 7 | - "@openzeppelin/=/share/node_modules/@openzeppelin/" 8 | deployer: "0x10000" 9 | sender: 10 | - "0x10000" 11 | - "0x20000" 12 | - "0x30000" 13 | corpusDir: crytic-corpus 14 | stopOnFail: true 15 | filterBlacklist: false 16 | filterFunctions: 17 | - MaticXFuzz.testSubmit(uint256) 18 | - MaticXFuzz.testRequestWithdraw(uint256) 19 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-undef,@typescript-eslint/no-require-imports 2 | const eslint = require("@eslint/js"); 3 | // eslint-disable-next-line no-undef,@typescript-eslint/no-require-imports 4 | const tseslint = require("typescript-eslint"); 5 | // eslint-disable-next-line no-undef,@typescript-eslint/no-require-imports 6 | const eslintPluginPrettierRecommended = require("eslint-plugin-prettier/recommended"); 7 | 8 | // eslint-disable-next-line no-undef 9 | module.exports = tseslint.config( 10 | { 11 | ignores: [ 12 | ".openzeppelin/*", 13 | "artifacts/*", 14 | "cache/*", 15 | "contracts/*", 16 | "coverage/*", 17 | "crytic-corpus/*", 18 | "crytic-export/*", 19 | "node_modules/*", 20 | "typechain-types/*", 21 | ], 22 | }, 23 | eslint.configs.recommended, 24 | ...tseslint.configs.strict, 25 | ...tseslint.configs.stylistic, 26 | eslintPluginPrettierRecommended, 27 | { 28 | rules: { 29 | "@typescript-eslint/no-unused-expressions": "off", 30 | }, 31 | languageOptions: { 32 | parserOptions: { 33 | warnOnUnsupportedTypeScriptVersion: false, 34 | }, 35 | }, 36 | } 37 | ); 38 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from "dotenv"; 2 | import { 3 | HardhatNetworkHDAccountsConfig, 4 | HardhatNetworkMiningConfig, 5 | } from "hardhat/types"; 6 | import { HardhatUserConfig } from "hardhat/config"; 7 | import "@nomicfoundation/hardhat-toolbox"; 8 | import "@nomiclabs/hardhat-solhint"; 9 | import "@openzeppelin/hardhat-upgrades"; 10 | import "hardhat-contract-sizer"; 11 | import "./tasks"; 12 | import { extractEnvironmentVariables } from "./utils/environment"; 13 | import { getProviderUrl, Network } from "./utils/network"; 14 | 15 | const isCI = process.env.CI; 16 | dotenv.config({ 17 | path: isCI ? ".env.example" : ".env", 18 | }); 19 | 20 | const envVars = extractEnvironmentVariables(); 21 | 22 | const accounts: Omit = { 23 | mnemonic: envVars.DEPLOYER_MNEMONIC, 24 | passphrase: envVars.DEPLOYER_PASSPHRASE, 25 | path: "m/44'/60'/0'/0", 26 | initialIndex: 0, 27 | count: 10, 28 | }; 29 | 30 | const mining: HardhatNetworkMiningConfig = { 31 | auto: true, 32 | interval: 1_000, 33 | mempool: { 34 | order: "fifo", 35 | }, 36 | }; 37 | 38 | const gasPrice = envVars.GAS_PRICE_GWEI || "auto"; 39 | 40 | const config: HardhatUserConfig = { 41 | networks: { 42 | [Network.Hardhat]: { 43 | initialBaseFeePerGas: 0, // See https://github.com/sc-forks/solidity-coverage/issues/652#issuecomment-896330136 44 | blockGasLimit: 30_000_000, 45 | mining, 46 | forking: { 47 | url: getProviderUrl( 48 | Network.Ethereum, 49 | envVars.RPC_PROVIDER, 50 | envVars.ETHEREUM_API_KEY 51 | ), 52 | blockNumber: envVars.FORKING_BLOCK_NUMBER, 53 | enabled: false, 54 | }, 55 | }, 56 | [Network.Localhost]: { 57 | url: "http://127.0.0.1:8545", 58 | blockGasLimit: 30_000_000, 59 | mining, 60 | }, 61 | [Network.Sepolia]: { 62 | url: getProviderUrl( 63 | Network.Sepolia, 64 | envVars.RPC_PROVIDER, 65 | envVars.SEPOLIA_API_KEY 66 | ), 67 | chainId: 11_155_111, 68 | from: envVars.DEPLOYER_ADDRESS, 69 | accounts, 70 | gasPrice, 71 | }, 72 | [Network.Amoy]: { 73 | url: getProviderUrl( 74 | Network.Amoy, 75 | envVars.RPC_PROVIDER, 76 | envVars.AMOY_API_KEY 77 | ), 78 | chainId: 80_002, 79 | from: envVars.DEPLOYER_ADDRESS, 80 | accounts, 81 | gasPrice, 82 | }, 83 | [Network.Ethereum]: { 84 | url: getProviderUrl( 85 | Network.Ethereum, 86 | envVars.RPC_PROVIDER, 87 | envVars.ETHEREUM_API_KEY 88 | ), 89 | chainId: 1, 90 | from: envVars.DEPLOYER_ADDRESS, 91 | accounts, 92 | gasPrice, 93 | }, 94 | [Network.Polygon]: { 95 | url: getProviderUrl( 96 | Network.Polygon, 97 | envVars.RPC_PROVIDER, 98 | envVars.POLYGON_API_KEY 99 | ), 100 | chainId: 137, 101 | from: envVars.DEPLOYER_ADDRESS, 102 | accounts, 103 | gasPrice, 104 | }, 105 | }, 106 | defaultNetwork: Network.Hardhat, 107 | solidity: { 108 | compilers: [ 109 | { 110 | version: "0.8.7", 111 | settings: { 112 | optimizer: { 113 | enabled: true, 114 | runs: 200, 115 | }, 116 | }, 117 | }, 118 | { 119 | version: "0.8.8", 120 | settings: { 121 | optimizer: { 122 | enabled: true, 123 | runs: 200, 124 | }, 125 | }, 126 | }, 127 | ], 128 | }, 129 | mocha: { 130 | reporter: process.env.CI ? "dot" : "nyan", 131 | timeout: "1h", 132 | }, 133 | etherscan: { 134 | apiKey: { 135 | [Network.Sepolia]: envVars.ETHERSCAN_API_KEY, 136 | [Network.AmoyAlt]: envVars.POLYGONSCAN_API_KEY, 137 | [Network.EthereumAlt]: envVars.ETHERSCAN_API_KEY, 138 | [Network.Polygon]: envVars.POLYGONSCAN_API_KEY, 139 | }, 140 | }, 141 | gasReporter: { 142 | coinmarketcap: envVars.COINMARKETCAP_API_KEY, 143 | excludeContracts: [ 144 | "@openzeppelin/", 145 | "interfaces/", 146 | "libraries/", 147 | "mocks/", 148 | "state-transfer/", 149 | ], 150 | enabled: envVars.REPORT_GAS, 151 | ...(envVars.GAS_REPORTER_NETWORK === "polygon" 152 | ? { 153 | currency: "POL", 154 | token: "POL", 155 | gasPriceApi: 156 | "https://api.polygonscan.com/api?module=proxy&action=eth_gasPrice", 157 | } 158 | : { 159 | currency: "ETH", 160 | token: "ETH", 161 | gasPriceApi: 162 | "https://api.etherscan.io/api?module=proxy&action=eth_gasPrice", 163 | }), 164 | }, 165 | contractSizer: { 166 | alphaSort: false, 167 | disambiguatePaths: false, 168 | runOnCompile: false, 169 | strict: true, 170 | except: [ 171 | "@openzeppelin/", 172 | "interfaces/", 173 | "libraries/", 174 | "mocks/", 175 | "state-transfer/", 176 | ], 177 | only: ["ChildPool", "MaticX", "ValidatorRegistry"], 178 | }, 179 | }; 180 | 181 | export default config; 182 | -------------------------------------------------------------------------------- /mainnet-deployment-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "root_deployer": "0x75db63125A4f04E59A1A2Ab4aCC4FC1Cd5Daddd5", 3 | "manager": "0x80A43dd35382C4919991C5Bca7f46Dd24Fde4C67", 4 | "treasury": "0x80A43dd35382C4919991C5Bca7f46Dd24Fde4C67", 5 | "matic_erc20_address": "0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0", 6 | "matic_stake_manager_proxy": "0x5e3Ef299fDDf15eAa0432E6e66473ace8c13D908", 7 | "eth_multisig": "0x80A43dd35382C4919991C5Bca7f46Dd24Fde4C67", 8 | "eth_proxy_admin": "0x6CBd89A4919E39Ad4c7718B04443CC1722B2cB2A", 9 | "eth_maticX_proxy": "0xf03A7Eb46d01d9EcAA104558C732Cf82f6B6B645", 10 | "eth_maticX_impl": "0x5a78f4BD60C92FCbbf1C941Bc1136491D2896b35", 11 | "eth_validator_registry_proxy": "0xf556442D5B77A4B0252630E15d8BbE2160870d77", 12 | "eth_validator_registry_impl": "0x0a7f554AbFBD710D9CDFB7Cf88217a91acA7457D", 13 | "rate_provider": "0xeE652bbF72689AA59F0B8F981c9c90e2A8Af8d8f", 14 | "polygon_fx_state_child_tunnel": "0x97E58a6950D86d751082D5e1d350e74c19047570", 15 | "eth_fx_state_root_tunnel": "0x40FB804Cc07302b89EC16a9f8d040506f64dFe29", 16 | "polygon_multisig": "0x51358004cFe135E64453d7F6a0dC433CAba09A2a", 17 | "polygon_proxy_admin": "0x79f2Ec80e2B9fB995D9deBda4d9026eF7A1507b9", 18 | "polygon_child_pool_proxy": "0xfd225C9e6601C9d38d8F98d8731BF59eFcF8C0E3", 19 | "polygon_child_pool_impl": "0xd6f5781b49f774aebdea7ab52671b0d72d36b6dd" 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MaticX", 3 | "version": "0.1.0", 4 | "description": "MaticX contracts", 5 | "scripts": { 6 | "prepare": "husky", 7 | "format": "prettier --check .", 8 | "format:fix": "prettier --write .", 9 | "lint": "eslint .", 10 | "lint:fix": "eslint --fix .", 11 | "compile": "hardhat compile", 12 | "check": "hardhat check", 13 | "test": "hardhat test --typecheck --bail", 14 | "test:ci": "hardhat test --typecheck --parallel", 15 | "cover": "rimraf coverage && hardhat coverage", 16 | "gas": "REPORT_GAS=true npm run test", 17 | "size": "hardhat size-contracts", 18 | "clean": "hardhat clean && rm -rf coverage cache crytic-{corpus,export} mochaOutput.json testMatrix.json", 19 | "node": "hardhat node", 20 | "analyze": "slitherin .", 21 | "analyze:ci": "slitherin . --skip-clean --ignore-compile" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/stader-labs/maticX" 26 | }, 27 | "keywords": [], 28 | "author": "", 29 | "license": "BUSL-1.1", 30 | "bugs": { 31 | "url": "https://github.com/stader-labs/maticX/issues" 32 | }, 33 | "homepage": "https://github.com/stader-labs/maticX#readme", 34 | "engines": { 35 | "node": "^20.9.0", 36 | "npm": ">=10.2.0" 37 | }, 38 | "type": "commonjs", 39 | "dependencies": { 40 | "@openzeppelin/contracts": "^4.9.6", 41 | "@openzeppelin/contracts-upgradeable": "^4.9.6" 42 | }, 43 | "devDependencies": { 44 | "@nomicfoundation/hardhat-toolbox": "^5.0.0", 45 | "@nomiclabs/hardhat-solhint": "^4.0.0", 46 | "@openzeppelin/hardhat-upgrades": "^3.5.0", 47 | "@tsconfig/node20": "^20.1.4", 48 | "@types/eslint__js": "^8.42.3", 49 | "dotenv": "^16.4.5", 50 | "eslint": "^9.7.0", 51 | "eslint-config-prettier": "^10.0.1", 52 | "eslint-plugin-prettier": "^5.1.3", 53 | "hardhat": "^2.6.4", 54 | "hardhat-contract-sizer": "^2.5.1", 55 | "husky": "^9.1.5", 56 | "joi": "^17.13.3", 57 | "prettier": "^3.3.3", 58 | "prettier-plugin-solidity": "^1.4.1", 59 | "solhint": "^5.0.3", 60 | "solhint-community": "^4.0.0", 61 | "solhint-plugin-prettier": "^0.1.0", 62 | "typescript": "^5.5.4", 63 | "typescript-eslint": "^8.14.0" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-undef 2 | module.exports = { 3 | printWidth: 80, 4 | tabWidth: 4, 5 | useTabs: true, 6 | semi: true, 7 | singleQuote: false, 8 | quoteProps: "as-needed", 9 | trailingComma: "es5", 10 | bracketSpacing: true, 11 | arrowParens: "always", 12 | overrides: [ 13 | { 14 | files: "*.sol", 15 | options: { 16 | parser: "solidity-parse", 17 | printWidth: 80, 18 | }, 19 | }, 20 | { 21 | files: "*.yaml", 22 | options: { 23 | tabWidth: 2, 24 | }, 25 | }, 26 | ], 27 | plugins: ["prettier-plugin-solidity"], 28 | }; 29 | -------------------------------------------------------------------------------- /requirements-mythril.txt: -------------------------------------------------------------------------------- 1 | asn1crypto==1.5.1 2 | bitarray==3.0.0 3 | blake2b-py==0.3.0 4 | cached-property==1.5.2 5 | certifi==2024.12.14 6 | cffi==1.17.1 7 | cfgv==3.4.0 8 | charset-normalizer==3.4.1 9 | ckzg==1.0.2 10 | coincurve==20.0.0 11 | coloredlogs==15.0.1 12 | configparser==7.1.0 13 | contourpy==1.3.1 14 | coverage==6.5.0 15 | cycler==0.12.1 16 | cytoolz==1.0.1 17 | dictionaries==0.0.2 18 | distlib==0.3.9 19 | eth-abi==4.2.1 20 | eth-account==0.11.3 21 | eth-bloom==1.0.4 22 | eth-hash==0.3.3 23 | eth-keyfile==0.8.1 24 | eth-keys==0.4.0 25 | eth-rlp==0.3.0 26 | eth-typing==3.5.2 27 | eth-utils==2.3.2 28 | ethereum_input_decoder==0.2.2 29 | filelock==3.16.1 30 | fonttools==4.55.3 31 | hexbytes==0.2.3 32 | humanfriendly==10.0 33 | identify==2.6.5 34 | idna==3.10 35 | importlib_resources==6.5.2 36 | iniconfig==2.0.0 37 | Jinja2==3.1.5 38 | joblib==1.4.2 39 | kiwisolver==1.4.8 40 | lru-dict==1.3.0 41 | MarkupSafe==2.0.1 42 | matplotlib==3.10.0 43 | mock==5.1.0 44 | mypy_extensions==0.4.4 45 | mythril==0.24.8 46 | nodeenv==1.9.1 47 | numpy==2.2.1 48 | packaging==24.2 49 | parsimonious==0.9.0 50 | persistent==6.1 51 | pillow==11.1.0 52 | platformdirs==4.3.6 53 | pluggy==1.5.0 54 | pre-commit==2.20.0 55 | py-ecc==1.4.7 56 | py-evm==0.7.0a1 57 | py-flags==1.1.4 58 | py-solc==3.2.0 59 | py-solc-x==1.1.1 60 | pycparser==2.22 61 | pycryptodome==3.21.0 62 | pyethash==0.1.27 63 | pyparsing==2.4.7 64 | pytest==8.3.4 65 | pytest-cov==5.0.0 66 | pytest-mock==3.14.0 67 | python-dateutil==2.9.0.post0 68 | PyYAML==6.0.2 69 | regex==2024.11.6 70 | requests==2.32.3 71 | rlp==3.0.0 72 | scikit-learn==1.6.0 73 | scipy==1.15.0 74 | semantic-version==2.10.0 75 | setuptools==75.8.0 76 | six==1.17.0 77 | sortedcontainers==2.4.0 78 | threadpoolctl==3.5.0 79 | toml==0.10.2 80 | toolz==1.0.0 81 | transaction==5.0 82 | trie==2.2.0 83 | typing_extensions==4.12.2 84 | urllib3==2.3.0 85 | virtualenv==20.28.1 86 | z3-solver==4.12.5.0 87 | zope.deferredimport==5.0 88 | zope.interface==7.2 89 | zope.proxy==6.1 90 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohappyeyeballs==2.4.4 2 | aiohttp==3.11.11 3 | aiosignal==1.3.2 4 | attrs==24.3.0 5 | bitarray==3.0.0 6 | cbor2==5.6.5 7 | certifi==2024.12.14 8 | charset-normalizer==3.4.1 9 | ckzg==1.0.2 10 | crytic-compile==0.3.7 11 | cytoolz==1.0.1 12 | eth-account==0.11.3 13 | eth-hash==0.7.0 14 | eth-keyfile==0.8.1 15 | eth-keys==0.6.0 16 | eth-rlp==1.0.1 17 | eth-typing==4.4.0 18 | eth-utils==4.1.1 19 | eth_abi==5.1.0 20 | frozenlist==1.5.0 21 | hexbytes==0.3.1 22 | idna==3.10 23 | jsonschema==4.23.0 24 | jsonschema-specifications==2024.10.1 25 | lru-dict==1.2.0 26 | multidict==6.1.0 27 | packaging==24.2 28 | parsimonious==0.10.0 29 | prettytable==3.12.0 30 | propcache==0.2.1 31 | protobuf==5.29.3 32 | pycryptodome==3.21.0 33 | pyunormalize==16.0.0 34 | referencing==0.35.1 35 | regex==2024.11.6 36 | requests==2.32.3 37 | rlp==4.0.1 38 | rpds-py==0.22.3 39 | setuptools==75.8.0 40 | slither-analyzer==0.10.4 41 | slitherin==0.7.2 42 | solc-select==1.0.4 43 | toolz==1.0.0 44 | typing_extensions==4.12.2 45 | urllib3==2.3.0 46 | wcwidth==0.2.13 47 | web3==6.20.3 48 | websockets==14.1 49 | yarl==1.18.3 50 | -------------------------------------------------------------------------------- /scripts/checkDeployIntegrity.ts: -------------------------------------------------------------------------------- 1 | import hardhat, { ethers } from "hardhat"; 2 | import { ValidatorRegistry, MaticX } from "../typechain-types"; 3 | import { getUpgradeContext } from "./utils"; 4 | 5 | const checkDeployIntegrity = async () => { 6 | const { deployDetails } = getUpgradeContext(hardhat); 7 | const validatorRegistry: ValidatorRegistry = (await ethers.getContractAt( 8 | "ValidatorRegistry", 9 | deployDetails.validator_registry_proxy 10 | )) as ValidatorRegistry; 11 | 12 | const maticX: MaticX = (await ethers.getContractAt( 13 | "MaticX", 14 | deployDetails.maticX_proxy 15 | )) as MaticX; 16 | 17 | console.log("Checking contracts integrity..."); 18 | 19 | const res = await validatorRegistry.getContracts(); 20 | isValid( 21 | res._maticToken, 22 | deployDetails.matic_erc20_address, 23 | "ValidatorRegistry", 24 | "ERC20" 25 | ); 26 | isValid( 27 | res._maticX, 28 | deployDetails.maticX_proxy, 29 | "ValidatorRegistry", 30 | "MaticX" 31 | ); 32 | isValid( 33 | res._stakeManager, 34 | deployDetails.matic_stake_manager_proxy, 35 | "ValidatorRegistry", 36 | "StakeManager" 37 | ); 38 | 39 | const contracts = await maticX.getContracts(); 40 | isValid( 41 | contracts._validatorRegistry, 42 | deployDetails.validator_registry_proxy, 43 | "maticX", 44 | "validatorRegistry" 45 | ); 46 | isValid( 47 | contracts._maticToken, 48 | deployDetails.matic_erc20_address, 49 | "maticX", 50 | "matic_erc20_address" 51 | ); 52 | isValid( 53 | contracts._stakeManager, 54 | deployDetails.matic_stake_manager_proxy, 55 | "maticX", 56 | "StakeManager" 57 | ); 58 | 59 | console.log("All is Good :)"); 60 | }; 61 | 62 | const isValid = ( 63 | actual: string, 64 | target: string, 65 | contract: string, 66 | message: string 67 | ) => { 68 | if (actual.toLowerCase() !== target.toLowerCase()) { 69 | console.log("actual:", actual); 70 | console.log("target:", target); 71 | throw new Error(`Error: ${contract}--Invalid address--${message}`); 72 | } 73 | }; 74 | 75 | checkDeployIntegrity(); 76 | -------------------------------------------------------------------------------- /scripts/deploy.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | import { MaticXDeployer } from "./deployers"; 3 | 4 | const main = async () => { 5 | const [rootSigner] = await ethers.getSigners(); 6 | 7 | const maticXDeployer = 8 | await MaticXDeployer.CreateMaticXDeployer(rootSigner); 9 | await maticXDeployer.deploy(); 10 | await maticXDeployer.export(); 11 | }; 12 | 13 | main() 14 | .then(() => process.exit(0)) 15 | .catch((error) => { 16 | console.error(error); 17 | process.exit(1); 18 | }); 19 | -------------------------------------------------------------------------------- /scripts/deployers.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "node:fs"; 2 | import path from "node:path"; 3 | import { Contract, Wallet } from "ethers"; 4 | import { ethers, network, upgrades } from "hardhat"; 5 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 6 | import { predictContractAddress } from "./utils"; 7 | import { ValidatorRegistry, MaticX } from "../typechain-types"; 8 | import { extractEnvironmentVariables } from "../utils/environment"; 9 | 10 | interface DeploymentData { 11 | Network: string; 12 | Signer: string; 13 | MaticX: string; 14 | ValidatorRegistry: string; 15 | } 16 | 17 | type ContractNames = 18 | | "ProxyAdmin" 19 | | "ValidatorRegistryImplementation" 20 | | "ValidatorRegistry" 21 | | "MaticXImplementation" 22 | | "MaticX"; 23 | 24 | type DeploymentOrder = Record; 25 | 26 | const envVars = extractEnvironmentVariables(); 27 | 28 | const deploymentOrder: DeploymentOrder = { 29 | ProxyAdmin: 0, 30 | ValidatorRegistryImplementation: 1, 31 | ValidatorRegistry: 2, 32 | MaticXImplementation: 3, 33 | MaticX: 4, 34 | }; 35 | 36 | interface Exportable { 37 | data: Record; 38 | export(): void; 39 | } 40 | 41 | interface Deployable { 42 | deploy(): void; 43 | } 44 | 45 | class BlockchainDeployer { 46 | signer: Wallet | SignerWithAddress; 47 | nonce: number; 48 | 49 | constructor(signer: Wallet | SignerWithAddress, nonce: number) { 50 | this.signer = signer; 51 | this.nonce = nonce; 52 | } 53 | 54 | deployContract = async ( 55 | contractName: keyof DeploymentData, 56 | ...args: unknown[] 57 | ) => { 58 | console.log(`Deploying ${contractName}: ${args}, ${args.length}`); 59 | const Contract = await ethers.getContractFactory( 60 | contractName, 61 | this.signer 62 | ); 63 | const contract = args.length 64 | ? ((await Contract.deploy(...args)) as T) 65 | : ((await Contract.deploy()) as T); 66 | await contract.waitForDeployment(); 67 | console.log(`Deployed at ${contract.address}`); 68 | 69 | return contract; 70 | }; 71 | 72 | deployProxy = async ( 73 | contractName: keyof DeploymentData, 74 | ...args: unknown[] 75 | ) => { 76 | console.log(`Deploying ${contractName}: ${args}, ${args.length}`); 77 | const Contract = await ethers.getContractFactory( 78 | contractName, 79 | this.signer 80 | ); 81 | const contract = args.length 82 | ? ((await upgrades.deployProxy(Contract, args)) as T) 83 | : ((await upgrades.deployProxy(Contract)) as T); 84 | await contract.waitForDeployment(); 85 | console.log(`Deployed at ${contract.address}`); 86 | 87 | return contract; 88 | }; 89 | } 90 | 91 | abstract class MultichainDeployer { 92 | rootDeployer: BlockchainDeployer; 93 | 94 | constructor(rootDeployer: BlockchainDeployer) { 95 | this.rootDeployer = rootDeployer; 96 | } 97 | } 98 | 99 | export class MaticXDeployer 100 | extends MultichainDeployer 101 | implements Exportable, Deployable 102 | { 103 | data: Partial> = {}; 104 | 105 | public static CreateMaticXDeployer = async ( 106 | rootSigner: Wallet | SignerWithAddress 107 | ) => { 108 | const rootNonce = await rootSigner.getTransactionCount(); 109 | const rootDeployer = new BlockchainDeployer(rootSigner, rootNonce); 110 | const maticXDeployer = new MaticXDeployer(rootDeployer); 111 | 112 | maticXDeployer.predictAddresses(); 113 | 114 | return maticXDeployer; 115 | }; 116 | 117 | deploy = async () => { 118 | await this.deployValidatorRegistry(); 119 | await this.deployMaticX(); 120 | }; 121 | 122 | private deployValidatorRegistry = async () => { 123 | return this.rootDeployer.deployProxy( 124 | "ValidatorRegistry", 125 | envVars.STAKE_MANAGER, 126 | envVars.MATIC_TOKEN, 127 | this.data.MaticX, 128 | envVars.MANAGER 129 | ); 130 | }; 131 | 132 | private deployMaticX = async () => { 133 | return this.rootDeployer.deployProxy( 134 | "MaticX", 135 | this.data.ValidatorRegistry, 136 | envVars.STAKE_MANAGER, 137 | envVars.MATIC_TOKEN, 138 | envVars.MANAGER, 139 | envVars.TREASURY 140 | ); 141 | }; 142 | 143 | export = async () => { 144 | const fileName = path.join( 145 | __dirname, 146 | "../", 147 | `${network.name}-deployment-info.json` 148 | ); 149 | const chainId = await this.rootDeployer.signer.getChainId(); 150 | const out = { 151 | network: chainId, 152 | multisig_upgrader: { address: "0x", owners: [] }, 153 | root_deployer: this.rootDeployer.signer.address, 154 | manager: envVars.MANAGER, 155 | treasury: envVars.TREASURY, 156 | matic_erc20_address: envVars.MATIC_TOKEN, 157 | matic_stake_manager_proxy: envVars.STAKE_MANAGER, 158 | proxy_admin: this.data.ProxyAdmin, 159 | maticX_proxy: this.data.MaticX, 160 | maticX_impl: this.data.MaticXImplementation, 161 | validator_registry_proxy: this.data.ValidatorRegistry, 162 | validator_registry_impl: this.data.ValidatorRegistryImplementation, 163 | }; 164 | fs.writeFileSync(fileName, JSON.stringify(out)); 165 | }; 166 | 167 | private predictAddresses = () => { 168 | this.calculateRootContractAddresses(); 169 | }; 170 | 171 | private calculateRootContractAddresses = () => { 172 | (Object.keys(deploymentOrder) as ContractNames[]).forEach((k) => { 173 | this.data[k] = predictContractAddress( 174 | this.rootDeployer.signer.address, 175 | this.rootDeployer.nonce + deploymentOrder[k] 176 | ); 177 | }); 178 | }; 179 | } 180 | -------------------------------------------------------------------------------- /scripts/tasks.ts: -------------------------------------------------------------------------------- 1 | import { task } from "hardhat/config"; 2 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 3 | import * as GOERLI_DEPLOYMENT_DETAILS from "../testnet-deployment-info.json"; 4 | import { extractEnvironmentVariables } from "../utils/environment"; 5 | 6 | const envVars = extractEnvironmentVariables(); 7 | 8 | const verifyContract = async ( 9 | hre: HardhatRuntimeEnvironment, 10 | contractAddress: string 11 | ) => { 12 | await hre.run("verify:verify", { 13 | address: contractAddress, 14 | }); 15 | }; 16 | 17 | export const verify = async (hre: HardhatRuntimeEnvironment) => { 18 | const contracts = [ 19 | GOERLI_DEPLOYMENT_DETAILS.maticX_impl, 20 | GOERLI_DEPLOYMENT_DETAILS.validator_registry_impl, 21 | ]; 22 | 23 | for (const contract of contracts) { 24 | try { 25 | await verifyContract(hre, contract); 26 | } catch (error) { 27 | console.log(error); 28 | } 29 | } 30 | }; 31 | 32 | export async function deployDirect( 33 | hre: HardhatRuntimeEnvironment, 34 | contractName: string, 35 | ...args: unknown[] 36 | ) { 37 | const Contract = await hre.ethers.getContractFactory(contractName); 38 | 39 | console.log(`Deploying ${contractName}: ${args}, ${args.length}`); 40 | const contract = args.length 41 | ? await Contract.deploy(...args) 42 | : await Contract.deploy(); 43 | 44 | await contract.waitForDeployment(); 45 | 46 | console.log(`${contractName} deployed to:`, contract.address); 47 | } 48 | 49 | export async function deployProxy( 50 | hre: HardhatRuntimeEnvironment, 51 | contractName: string, 52 | ...args: unknown[] 53 | ) { 54 | const Contract = await hre.ethers.getContractFactory(contractName); 55 | 56 | console.log(`Deploying proxy ${contractName}: ${args}, ${args.length}`); 57 | const contract = args.length 58 | ? await hre.upgrades.deployProxy(Contract, args) 59 | : await hre.upgrades.deployProxy(Contract); 60 | 61 | await contract.waitForDeployment(); 62 | 63 | console.log(`Proxy ${contractName} deployed to:`, contract.address); 64 | } 65 | 66 | task("verifyMaticX", "MaticX contracts verification").setAction( 67 | async (args, hre: HardhatRuntimeEnvironment) => { 68 | await verify(hre); 69 | } 70 | ); 71 | 72 | task("deployFxStateChildTunnel", "Deploy FxStateChildTunnel").setAction( 73 | async (args, hre: HardhatRuntimeEnvironment) => { 74 | if (!isChildNetwork(hre.network.name)) { 75 | return; 76 | } 77 | await deployDirect(hre, "FxStateChildTunnel", envVars.FX_CHILD); 78 | } 79 | ); 80 | 81 | task("deployFxStateRootTunnel", "Deploy FxStateRootTunnel") 82 | .addPositionalParam("maticX") 83 | .setAction(async ({ maticX }, hre: HardhatRuntimeEnvironment) => { 84 | if (!isRootNetwork(hre.network.name)) { 85 | return; 86 | } 87 | await deployDirect( 88 | hre, 89 | "FxStateRootTunnel", 90 | envVars.CHECKPOINT_MANAGER, 91 | envVars.FX_ROOT, 92 | maticX 93 | ); 94 | }); 95 | 96 | task("deployRateProvider", "Deploy RateProvider") 97 | .addPositionalParam("fxStateChildTunnel") 98 | .setAction( 99 | async ({ fxStateChildTunnel }, hre: HardhatRuntimeEnvironment) => { 100 | if (!isChildNetwork(hre.network.name)) { 101 | return; 102 | } 103 | await deployDirect(hre, "RateProvider", fxStateChildTunnel); 104 | } 105 | ); 106 | 107 | task("deployMaticXImpl", "Deploy MaticX Implementation only").setAction( 108 | async (args, hre: HardhatRuntimeEnvironment) => { 109 | if (!isRootNetwork(hre.network.name)) { 110 | return; 111 | } 112 | await deployDirect(hre, "MaticX"); 113 | } 114 | ); 115 | 116 | task( 117 | "deployValidatorRegistryImpl", 118 | "Deploy ValidatorRegistry Implementation only" 119 | ).setAction(async (args, hre: HardhatRuntimeEnvironment) => { 120 | if (!isRootNetwork(hre.network.name)) return; 121 | await deployDirect(hre, "ValidatorRegistry"); 122 | }); 123 | 124 | task("deployChildPoolProxy", "Deploy ChildPool Proxy only") 125 | .addPositionalParam("fxStateChildTunnel") 126 | .addPositionalParam("maticX") 127 | .addPositionalParam("manager") 128 | .addPositionalParam("instantPoolOwner") 129 | .addPositionalParam("treasury") 130 | .addPositionalParam("instantWithdrawalFeeBps") 131 | .setAction( 132 | async ( 133 | { 134 | fxStateChildTunnel, 135 | maticX, 136 | manager, 137 | instantPoolOwner, 138 | treasury, 139 | instantWithdrawalFeeBps, 140 | }, 141 | hre: HardhatRuntimeEnvironment 142 | ) => { 143 | if (!isChildNetwork(hre.network.name)) return; 144 | await deployProxy( 145 | hre, 146 | "ChildPool", 147 | fxStateChildTunnel, 148 | maticX, 149 | manager, 150 | instantPoolOwner, 151 | treasury, 152 | instantWithdrawalFeeBps 153 | ); 154 | } 155 | ); 156 | 157 | task("deployChildPoolImpl", "Deploy ChildPool Implementation only").setAction( 158 | async (args, hre: HardhatRuntimeEnvironment) => { 159 | if (!isChildNetwork(hre.network.name)) { 160 | return; 161 | } 162 | await deployDirect(hre, "ChildPool"); 163 | } 164 | ); 165 | 166 | function isChildNetwork(selected: string) { 167 | const expected = "matic"; 168 | return _isCorrectNetwork(expected, selected); 169 | } 170 | 171 | function isRootNetwork(selected: string) { 172 | const expected = "mainnet"; 173 | return _isCorrectNetwork(expected, selected); 174 | } 175 | 176 | function _isCorrectNetwork(expected: string, selected: string) { 177 | if (selected === expected) { 178 | return true; 179 | } 180 | 181 | console.log( 182 | `Wrong network configuration! Expected: ${expected} Selected: ${selected}` 183 | ); 184 | } 185 | -------------------------------------------------------------------------------- /scripts/types.ts: -------------------------------------------------------------------------------- 1 | interface Multisig { 2 | address: string; 3 | owners: string[]; 4 | } 5 | 6 | export interface DeployDetails { 7 | network: string; 8 | signer: string; 9 | multisig_upgrader: Multisig; 10 | dao: string; 11 | treasury: string; 12 | matic_erc20_address: string; 13 | matic_stake_manager_proxy: string; 14 | maticX_proxy: string; 15 | maticX_implementation: string; 16 | validator_registry_proxy: string; 17 | validator_registry_implementation: string; 18 | default?: string; 19 | } 20 | -------------------------------------------------------------------------------- /scripts/upgradeMaticX.ts: -------------------------------------------------------------------------------- 1 | import hardhat, { ethers, upgrades } from "hardhat"; 2 | import { MaticX__factory } from "../typechain-types"; 3 | import { exportAddresses, getUpgradeContext } from "./utils"; 4 | 5 | const upgradeMaticX = async () => { 6 | const { network, filePath, deployDetails } = getUpgradeContext(hardhat); 7 | 8 | console.log("Start upgrade contracts on:", network); 9 | const MaticXAddress = deployDetails.maticX_proxy; 10 | const MaticXFactory: MaticX__factory = (await ethers.getContractFactory( 11 | "MaticX" 12 | )) as MaticX__factory; 13 | 14 | await upgrades.upgradeProxy(MaticXAddress, MaticXFactory); 15 | const MaticXImplAddress = 16 | await upgrades.erc1967.getImplementationAddress(MaticXAddress); 17 | 18 | console.log("MaticX upgraded"); 19 | console.log("proxy:", MaticXAddress); 20 | console.log("Implementation:", MaticXImplAddress); 21 | 22 | exportAddresses(filePath, { 23 | maticX_proxy: MaticXAddress, 24 | maticX_impl: MaticXImplAddress, 25 | }); 26 | }; 27 | 28 | upgradeMaticX(); 29 | -------------------------------------------------------------------------------- /scripts/upgradeValidatorRegistry.ts: -------------------------------------------------------------------------------- 1 | import hardhat, { ethers, upgrades } from "hardhat"; 2 | import { ValidatorRegistry__factory } from "../typechain-types"; 3 | import { exportAddresses, getUpgradeContext } from "./utils"; 4 | 5 | const upgradeValidatorRegistry = async () => { 6 | const { network, filePath, deployDetails } = getUpgradeContext(hardhat); 7 | 8 | console.log("Start upgrade contract on:", network); 9 | const validatorRegistryAddress = deployDetails.validator_registry_proxy; 10 | const validatorRegistryFactory: ValidatorRegistry__factory = 11 | (await ethers.getContractFactory( 12 | "ValidatorRegistry" 13 | )) as ValidatorRegistry__factory; 14 | 15 | await upgrades.upgradeProxy( 16 | validatorRegistryAddress, 17 | validatorRegistryFactory 18 | ); 19 | const validatorRegistryImplAddress = 20 | await upgrades.erc1967.getImplementationAddress( 21 | validatorRegistryAddress 22 | ); 23 | console.log("ValidatorRegistry upgraded"); 24 | console.log("proxy:", validatorRegistryAddress); 25 | console.log("Implementation:", validatorRegistryImplAddress); 26 | 27 | exportAddresses(filePath, { 28 | validator_registry_proxy: validatorRegistryAddress, 29 | validator_registry_impl: validatorRegistryImplAddress, 30 | }); 31 | }; 32 | 33 | upgradeValidatorRegistry(); 34 | -------------------------------------------------------------------------------- /scripts/utils.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import path from "node:path"; 3 | import { Contract, ethers } from "ethers"; 4 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 5 | import { publicKeyCreate } from "secp256k1"; 6 | import { DeployDetails } from "./types"; 7 | 8 | export const getPublicKey = (privateKey: string): Uint8Array => { 9 | const privKeyBytes = ethers.utils.arrayify(privateKey); 10 | const pubKeyBytes = publicKeyCreate(privKeyBytes, false).slice(1); 11 | 12 | return pubKeyBytes; 13 | }; 14 | 15 | export const attachContract = async ( 16 | hre: HardhatRuntimeEnvironment, 17 | contractAddress: string, 18 | contractName: string, 19 | privateKey?: string 20 | ): Promise => { 21 | const admin = privateKey 22 | ? new ethers.Wallet(privateKey, hre.ethers.provider) 23 | : (await hre.ethers.getSigners())[0]; 24 | 25 | const ContractFactory = await hre.ethers.getContractFactory( 26 | contractName, 27 | admin 28 | ); 29 | const contract = ContractFactory.attach(contractAddress); 30 | 31 | return contract; 32 | }; 33 | 34 | export const predictContractAddress = (address: string, nonce: number) => { 35 | const rlpEncoded = ethers.utils.RLP.encode([ 36 | address, 37 | ethers.BigNumber.from(nonce.toString()).toHexString(), 38 | ]); 39 | const contractAddressLong = ethers.utils.keccak256(rlpEncoded); 40 | const contractAddress = "0x".concat(contractAddressLong.substring(26)); 41 | 42 | return ethers.utils.getAddress(contractAddress); 43 | }; 44 | 45 | export const exportAddresses = (fullFilePath: string, addresses: object) => { 46 | console.log("Export to file..."); 47 | const data = JSON.parse(fs.readFileSync(fullFilePath, "utf8")); 48 | fs.writeFileSync( 49 | fullFilePath, 50 | JSON.stringify({ 51 | ...data, 52 | ...addresses, 53 | }) 54 | ); 55 | }; 56 | 57 | export const getUpgradeContext = (hre: HardhatRuntimeEnvironment) => { 58 | const network = hre.network.name; 59 | const filePath = `${network}-deployment-info.json`; 60 | // eslint-disable-next-line @typescript-eslint/no-require-imports 61 | const deployDetails: DeployDetails = require(path.join("..", filePath)); 62 | 63 | return { 64 | network, 65 | filePath, 66 | deployDetails, 67 | }; 68 | }; 69 | -------------------------------------------------------------------------------- /slither.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "compile_force_framework": "hardhat", 3 | "detectors_to_exclude": "calls-loop,incorrect-equality,naming-convention,pragma,solc-version,unused-return,unused-state", 4 | "include_paths": "(FxStateChildTunnel.sol|FxStateRootTunnel.sol|MaticX.sol|ValidatorRegistry.sol)", 5 | "filter_paths": "(@openzeppelin/|libraries/|mocks/)" 6 | } 7 | -------------------------------------------------------------------------------- /solc.json: -------------------------------------------------------------------------------- 1 | { 2 | "remappings": ["@openzeppelin/=./node_modules/@openzeppelin/"] 3 | } 4 | -------------------------------------------------------------------------------- /tasks/deploy-child-pool.ts: -------------------------------------------------------------------------------- 1 | import { TASK_CLEAN, TASK_COMPILE } from "hardhat/builtin-tasks/task-names"; 2 | import { task, types } from "hardhat/config"; 3 | import { getSigner } from "../utils/account"; 4 | import { isLocalNetwork, Network } from "../utils/network"; 5 | 6 | interface TaskParams { 7 | fxStateChildTunnel: string; 8 | maticX: string; 9 | manager: string; 10 | instantPoolOwner: string; 11 | treasury: string; 12 | instantWithdrawalFeeBps: number; 13 | } 14 | 15 | task("deploy:child-pool") 16 | .setDescription("Deploy the ChildPool contract") 17 | .addParam( 18 | "fxStateChildTunnel", 19 | "Address of the FxStateChildTunnel contract", 20 | undefined, 21 | types.string 22 | ) 23 | .addParam( 24 | "maticX", 25 | "Address of the MaticX contract", 26 | undefined, 27 | types.string 28 | ) 29 | .addParam( 30 | "manager", 31 | "Address of the Manager contract", 32 | undefined, 33 | types.string 34 | ) 35 | .addParam( 36 | "instantPoolOwner", 37 | "Address of the InstantPoolOwner contract", 38 | undefined, 39 | types.string 40 | ) 41 | .addParam( 42 | "treasury", 43 | "Address of the Treasury contract", 44 | undefined, 45 | types.string 46 | ) 47 | .addParam( 48 | "instantWithdrawalFeeBps", 49 | "Instant withdrawal fee base points", 50 | 50, 51 | types.int 52 | ) 53 | .setAction( 54 | async ( 55 | { 56 | fxStateChildTunnel: fxStateChildTunnelAddress, 57 | maticX: maticXAddress, 58 | manager: managerAddress, 59 | instantPoolOwner: instantPoolOwnerAddress, 60 | treasury: treasuryAddress, 61 | instantWithdrawalFeeBps, 62 | }: TaskParams, 63 | { ethers, network, run, upgrades } 64 | ) => { 65 | if (!ethers.isAddress(fxStateChildTunnelAddress)) { 66 | throw new Error("Invalid FxStateChildTunnel address"); 67 | } 68 | if (!ethers.isAddress(maticXAddress)) { 69 | throw new Error("Invalid MaticX address"); 70 | } 71 | if (!ethers.isAddress(managerAddress)) { 72 | throw new Error("Invalid Manager address"); 73 | } 74 | if (!ethers.isAddress(instantPoolOwnerAddress)) { 75 | throw new Error("Invalid InstantPoolOwner address"); 76 | } 77 | if (!ethers.isAddress(treasuryAddress)) { 78 | throw new Error("Invalid Treasury address"); 79 | } 80 | 81 | const networkName = network.name as Network; 82 | console.log(`Network name: ${networkName}`); 83 | if (!isLocalNetwork(networkName)) { 84 | await run(TASK_CLEAN); 85 | } 86 | await run(TASK_COMPILE); 87 | 88 | const signer = await getSigner( 89 | ethers, 90 | network.provider, 91 | network.config.from 92 | ); 93 | const ChildPool = await ethers.getContractFactory( 94 | "ChildPool", 95 | signer 96 | ); 97 | 98 | const childPool = await upgrades.deployProxy( 99 | ChildPool, 100 | [ 101 | fxStateChildTunnelAddress, 102 | maticXAddress, 103 | managerAddress, 104 | instantPoolOwnerAddress, 105 | treasuryAddress, 106 | instantWithdrawalFeeBps, 107 | ], 108 | { kind: "transparent" } 109 | ); 110 | await childPool.waitForDeployment(); 111 | 112 | const childPoolAddress = await childPool.getAddress(); 113 | console.log(`ChildPool Proxy deployed at ${childPoolAddress}`); 114 | 115 | const implementationAddress = 116 | await upgrades.erc1967.getImplementationAddress( 117 | childPoolAddress 118 | ); 119 | console.log( 120 | `ChildPool Implementation deployed at ${implementationAddress}` 121 | ); 122 | const proxyAdminAddress = 123 | await upgrades.erc1967.getAdminAddress(childPoolAddress); 124 | console.log( 125 | `ChildPool ProxyAdmin deployed at ${proxyAdminAddress}` 126 | ); 127 | } 128 | ); 129 | -------------------------------------------------------------------------------- /tasks/deploy-fx-state-child-tunnel.ts: -------------------------------------------------------------------------------- 1 | import { TASK_CLEAN, TASK_COMPILE } from "hardhat/builtin-tasks/task-names"; 2 | import { task, types } from "hardhat/config"; 3 | import { getSigner } from "../utils/account"; 4 | import { isLocalNetwork, Network } from "../utils/network"; 5 | 6 | interface TaskParams { 7 | fxChild: string; 8 | } 9 | 10 | task("deploy:fx-state-child-tunnel") 11 | .setDescription("Deploy the FxStateChildTunnel contract") 12 | .addParam( 13 | "fxChild", 14 | "Address of the FxChild contract", 15 | undefined, 16 | types.string 17 | ) 18 | .setAction( 19 | async ( 20 | { fxChild: fxChildAddress }: TaskParams, 21 | { ethers, network, run } 22 | ) => { 23 | if (!ethers.isAddress(fxChildAddress)) { 24 | throw new Error("Invalid FxChildAddress address"); 25 | } 26 | 27 | const networkName = network.name as Network; 28 | console.log(`Network name: ${networkName}`); 29 | if (!isLocalNetwork(networkName)) { 30 | await run(TASK_CLEAN); 31 | } 32 | await run(TASK_COMPILE); 33 | 34 | const signer = await getSigner( 35 | ethers, 36 | network.provider, 37 | network.config.from 38 | ); 39 | const FxStateChildTunnel = await ethers.getContractFactory( 40 | "FxStateChildTunnel", 41 | signer 42 | ); 43 | 44 | const fxStateChildTunnel = 45 | await FxStateChildTunnel.deploy(fxChildAddress); 46 | await fxStateChildTunnel.waitForDeployment(); 47 | 48 | const fxStateChildTunnelAddress = 49 | await fxStateChildTunnel.getAddress(); 50 | console.log( 51 | `FxStateChildTunnel deployed at ${fxStateChildTunnelAddress}` 52 | ); 53 | } 54 | ); 55 | -------------------------------------------------------------------------------- /tasks/deploy-fx-state-root-tunnel.ts: -------------------------------------------------------------------------------- 1 | import { TASK_CLEAN, TASK_COMPILE } from "hardhat/builtin-tasks/task-names"; 2 | import { task, types } from "hardhat/config"; 3 | import { getSigner } from "../utils/account"; 4 | import { isLocalNetwork, Network } from "../utils/network"; 5 | 6 | interface TaskParams { 7 | checkpointManager: string; 8 | fxRoot: string; 9 | maticX: string; 10 | } 11 | 12 | task("deploy:fx-state-root-tunnel") 13 | .setDescription("Deploy the FxStateRootTunnel contract") 14 | .addParam( 15 | "checkpointManager", 16 | "Address of the CheckpointManager contract", 17 | undefined, 18 | types.string 19 | ) 20 | .addParam( 21 | "fxRoot", 22 | "Address of the FxRoot contract", 23 | undefined, 24 | types.string 25 | ) 26 | .addParam( 27 | "maticX", 28 | "Address of the MaticX contract", 29 | undefined, 30 | types.string 31 | ) 32 | .setAction( 33 | async ( 34 | { 35 | checkpointManager: checkpointManagerAddress, 36 | fxRoot: fxRootAddress, 37 | maticX: maticXAddress, 38 | }: TaskParams, 39 | { ethers, network, run } 40 | ) => { 41 | if (!ethers.isAddress(checkpointManagerAddress)) { 42 | throw new Error("Invalid CheckpointManager address"); 43 | } 44 | if (!ethers.isAddress(fxRootAddress)) { 45 | throw new Error("Invalid FxRootAddress address"); 46 | } 47 | if (!ethers.isAddress(maticXAddress)) { 48 | throw new Error("Invalid MaticX address"); 49 | } 50 | 51 | const networkName = network.name as Network; 52 | console.log(`Network name: ${networkName}`); 53 | if (!isLocalNetwork(networkName)) { 54 | await run(TASK_CLEAN); 55 | } 56 | await run(TASK_COMPILE); 57 | 58 | const signer = await getSigner( 59 | ethers, 60 | network.provider, 61 | network.config.from 62 | ); 63 | const FxStateRootTunnel = await ethers.getContractFactory( 64 | "FxStateRootTunnel", 65 | signer 66 | ); 67 | 68 | const fxStateRootTunnel = await FxStateRootTunnel.deploy( 69 | checkpointManagerAddress, 70 | fxRootAddress, 71 | maticXAddress 72 | ); 73 | await fxStateRootTunnel.waitForDeployment(); 74 | 75 | const fxStateRootTunnelAddress = 76 | await fxStateRootTunnel.getAddress(); 77 | console.log( 78 | `FxStateRootTunnel deployed at ${fxStateRootTunnelAddress}` 79 | ); 80 | } 81 | ); 82 | -------------------------------------------------------------------------------- /tasks/deploy-implementation.ts: -------------------------------------------------------------------------------- 1 | import { task, types } from "hardhat/config"; 2 | import { TASK_CLEAN, TASK_COMPILE } from "hardhat/builtin-tasks/task-names"; 3 | import { getSigner } from "../utils/account"; 4 | import { isLocalNetwork, Network } from "../utils/network"; 5 | 6 | interface TaskParams { 7 | name: string; 8 | } 9 | 10 | task("deploy-implementation") 11 | .setDescription("Deploy a contract implementation") 12 | .addParam("name", "Contract name", undefined, types.string) 13 | .setAction( 14 | async ( 15 | { name: contractName }: TaskParams, 16 | { ethers, upgrades, network, run } 17 | ) => { 18 | const networkName = network.name as Network; 19 | console.log(`Network name: ${networkName}`); 20 | if (!isLocalNetwork(networkName)) { 21 | await run(TASK_CLEAN); 22 | } 23 | await run(TASK_COMPILE); 24 | 25 | const signer = await getSigner( 26 | ethers, 27 | network.provider, 28 | network.config.from 29 | ); 30 | const adjustedContractName = isLocalNetwork(networkName) 31 | ? `${contractName}Mock` 32 | : contractName; 33 | const ContractFactory = await ethers.getContractFactory( 34 | adjustedContractName, 35 | signer 36 | ); 37 | 38 | const implementationAddress = await upgrades.deployImplementation( 39 | ContractFactory, 40 | { kind: "transparent" } 41 | ); 42 | console.log( 43 | `${contractName} Implementation deployed at ${implementationAddress}` 44 | ); 45 | } 46 | ); 47 | -------------------------------------------------------------------------------- /tasks/deploy-matic-x.ts: -------------------------------------------------------------------------------- 1 | import { TASK_CLEAN, TASK_COMPILE } from "hardhat/builtin-tasks/task-names"; 2 | import { task, types } from "hardhat/config"; 3 | import { getSigner } from "../utils/account"; 4 | import { isLocalNetwork, Network } from "../utils/network"; 5 | 6 | interface TaskParams { 7 | validatorRegistry: string; 8 | stakeManager: string; 9 | matic: string; 10 | manager: string; 11 | treasury: string; 12 | } 13 | 14 | task("deploy:matic-x") 15 | .setDescription("Deploy the MaticX contract") 16 | .addParam( 17 | "validatorRegistry", 18 | "Address of the ValidatorRegistry contract", 19 | undefined, 20 | types.string 21 | ) 22 | .addParam( 23 | "stakeManager", 24 | "Address of the StakeManager contract", 25 | undefined, 26 | types.string 27 | ) 28 | .addParam( 29 | "matic", 30 | "Address of the Matic contract", 31 | undefined, 32 | types.string 33 | ) 34 | .addParam( 35 | "manager", 36 | "Address of the Manager contract", 37 | undefined, 38 | types.string 39 | ) 40 | .addParam( 41 | "treasury", 42 | "Address of the Treasury contract", 43 | undefined, 44 | types.string 45 | ) 46 | .setAction( 47 | async ( 48 | { 49 | validatorRegistry: validatorRegistryAddress, 50 | stakeManager: stakeManagerAddress, 51 | matic: maticAddress, 52 | manager: managerAddress, 53 | treasury: treasuryAddress, 54 | }: TaskParams, 55 | { ethers, network, run, upgrades } 56 | ) => { 57 | if (!ethers.isAddress(validatorRegistryAddress)) { 58 | throw new Error("Invalid ValidatorRegistry address"); 59 | } 60 | if (!ethers.isAddress(stakeManagerAddress)) { 61 | throw new Error("Invalid StakeManager address"); 62 | } 63 | if (!ethers.isAddress(maticAddress)) { 64 | throw new Error("Invalid Matic address"); 65 | } 66 | if (!ethers.isAddress(managerAddress)) { 67 | throw new Error("Invalid Manager address"); 68 | } 69 | if (!ethers.isAddress(treasuryAddress)) { 70 | throw new Error("Invalid Treasury address"); 71 | } 72 | 73 | const networkName = network.name as Network; 74 | console.log(`Network name: ${networkName}`); 75 | if (!isLocalNetwork(networkName)) { 76 | await run(TASK_CLEAN); 77 | } 78 | await run(TASK_COMPILE); 79 | 80 | const signer = await getSigner( 81 | ethers, 82 | network.provider, 83 | network.config.from 84 | ); 85 | const MaticX = await ethers.getContractFactory("MaticX", signer); 86 | 87 | const maticX = await upgrades.deployProxy( 88 | MaticX, 89 | [ 90 | validatorRegistryAddress, 91 | stakeManagerAddress, 92 | maticAddress, 93 | managerAddress, 94 | treasuryAddress, 95 | ], 96 | { kind: "transparent" } 97 | ); 98 | await maticX.waitForDeployment(); 99 | 100 | const maticXAddress = await maticX.getAddress(); 101 | console.log(`MaticX Proxy deployed at ${maticXAddress}`); 102 | 103 | const implementationAddress = 104 | await upgrades.erc1967.getImplementationAddress(maticXAddress); 105 | console.log( 106 | `MaticX Implementation deployed at ${implementationAddress}` 107 | ); 108 | const proxyAdminAddress = 109 | await upgrades.erc1967.getAdminAddress(maticXAddress); 110 | console.log(`MaticX ProxyAdmin deployed at ${proxyAdminAddress}`); 111 | } 112 | ); 113 | -------------------------------------------------------------------------------- /tasks/deploy-timelock-controller.ts: -------------------------------------------------------------------------------- 1 | import { TASK_CLEAN, TASK_COMPILE } from "hardhat/builtin-tasks/task-names"; 2 | import { task, types } from "hardhat/config"; 3 | import { getSigner } from "../utils/account"; 4 | import { isLocalNetwork, Network } from "../utils/network"; 5 | 6 | interface TaskParams { 7 | minDelay: bigint; 8 | manager: string; 9 | } 10 | 11 | task("deploy:timelock-controller") 12 | .setDescription("Deploy the TimelockController contract") 13 | .addParam( 14 | "minDelay", 15 | "Mininum delay (in seconds)", 16 | undefined, 17 | types.bigint 18 | ) 19 | .addParam( 20 | "manager", 21 | "Address of the Manager contract", 22 | undefined, 23 | types.string 24 | ) 25 | .setAction( 26 | async ( 27 | { minDelay, manager: managerAddress }: TaskParams, 28 | { ethers, network, run } 29 | ) => { 30 | if (!ethers.isAddress(managerAddress)) { 31 | throw new Error("Invalid Manager address"); 32 | } 33 | 34 | const networkName = network.name as Network; 35 | console.log(`Network name: ${networkName}`); 36 | if (!isLocalNetwork(networkName)) { 37 | await run(TASK_CLEAN); 38 | } 39 | await run(TASK_COMPILE); 40 | 41 | const signer = await getSigner( 42 | ethers, 43 | network.provider, 44 | network.config.from 45 | ); 46 | const TimelockController = await ethers.getContractFactory( 47 | "contracts/TimelockController.sol:TimelockController", 48 | signer 49 | ); 50 | 51 | const timelockController = await TimelockController.deploy( 52 | minDelay, 53 | [managerAddress], 54 | [managerAddress], 55 | managerAddress 56 | ); 57 | await timelockController.waitForDeployment(); 58 | 59 | const timelockContollerAddress = 60 | await timelockController.getAddress(); 61 | console.log( 62 | `TimelockController deployed at ${timelockContollerAddress}` 63 | ); 64 | } 65 | ); 66 | -------------------------------------------------------------------------------- /tasks/deploy-u-child-erc20.ts: -------------------------------------------------------------------------------- 1 | import { TASK_CLEAN, TASK_COMPILE } from "hardhat/builtin-tasks/task-names"; 2 | import { task, types } from "hardhat/config"; 3 | import { getSigner } from "../utils/account"; 4 | import { isLocalNetwork, Network } from "../utils/network"; 5 | 6 | interface TaskParams { 7 | name: string; 8 | symbol: string; 9 | childChainManager: string; 10 | } 11 | 12 | task("deploy:u-child-erc20") 13 | .setDescription("Deploy the UChildERC20 contract") 14 | .addParam("name", "ERC20 name", undefined, types.string) 15 | .addParam("symbol", "ERC20 symbol", undefined, types.string) 16 | .addParam( 17 | "childChainManager", 18 | "Address of the ChildChainManager contract", 19 | undefined, 20 | types.string 21 | ) 22 | .setAction( 23 | async ( 24 | { 25 | name, 26 | symbol, 27 | childChainManager: childChainManagerAddress, 28 | }: TaskParams, 29 | { ethers, network, run, upgrades } 30 | ) => { 31 | if (!name) { 32 | throw new Error("Empty name"); 33 | } 34 | if (!symbol) { 35 | throw new Error("Empty symbol"); 36 | } 37 | if (!ethers.isAddress(childChainManagerAddress)) { 38 | throw new Error("Invalid ChildChainManager address"); 39 | } 40 | 41 | const networkName = network.name as Network; 42 | console.log(`Network name: ${networkName}`); 43 | if (!isLocalNetwork(networkName)) { 44 | await run(TASK_CLEAN); 45 | } 46 | await run(TASK_COMPILE); 47 | 48 | const signer = await getSigner( 49 | ethers, 50 | network.provider, 51 | network.config.from 52 | ); 53 | const UChildERC20 = await ethers.getContractFactory( 54 | "UChildERC20", 55 | signer 56 | ); 57 | 58 | const uChildERC20 = await upgrades.deployProxy( 59 | UChildERC20, 60 | [name, symbol, childChainManagerAddress], 61 | { kind: "transparent" } 62 | ); 63 | await uChildERC20.waitForDeployment(); 64 | 65 | const uChildERC20Address = await uChildERC20.getAddress(); 66 | console.log(`UChildERC20 Proxy deployed at ${uChildERC20Address}`); 67 | 68 | const implementationAddress = 69 | await upgrades.erc1967.getImplementationAddress( 70 | uChildERC20Address 71 | ); 72 | console.log( 73 | `UChildERC20 Implementation deployed at ${implementationAddress}` 74 | ); 75 | const proxyAdminAddress = 76 | await upgrades.erc1967.getAdminAddress(uChildERC20Address); 77 | console.log( 78 | `UChildERC20 ProxyAdmin deployed at ${proxyAdminAddress}` 79 | ); 80 | } 81 | ); 82 | -------------------------------------------------------------------------------- /tasks/deploy-validator-registry.ts: -------------------------------------------------------------------------------- 1 | import { TASK_CLEAN, TASK_COMPILE } from "hardhat/builtin-tasks/task-names"; 2 | import { task, types } from "hardhat/config"; 3 | import { getSigner } from "../utils/account"; 4 | import { isLocalNetwork, Network } from "../utils/network"; 5 | 6 | interface TaskParams { 7 | stakeManager: string; 8 | matic: string; 9 | maticX: string; 10 | manager: string; 11 | } 12 | 13 | task("deploy:validator-registry") 14 | .setDescription("Deploy the ValidatorRegistry contract") 15 | .addParam( 16 | "stakeManager", 17 | "Address of the StakeManager contract", 18 | undefined, 19 | types.string 20 | ) 21 | .addParam( 22 | "matic", 23 | "Address of the Matic contract", 24 | undefined, 25 | types.string 26 | ) 27 | .addParam( 28 | "maticX", 29 | "Address of the MaticX contract", 30 | undefined, 31 | types.string 32 | ) 33 | .addParam( 34 | "manager", 35 | "Address of the Manager contract", 36 | undefined, 37 | types.string 38 | ) 39 | .setAction( 40 | async ( 41 | { 42 | stakeManager: stakeManagerAddress, 43 | matic: maticAddress, 44 | maticX: maticXAddress, 45 | manager: managerAddress, 46 | }: TaskParams, 47 | { ethers, network, run, upgrades } 48 | ) => { 49 | if (!ethers.isAddress(stakeManagerAddress)) { 50 | throw new Error("Invalid StakeManager address"); 51 | } 52 | if (!ethers.isAddress(maticAddress)) { 53 | throw new Error("Invalid Matic address"); 54 | } 55 | if (!ethers.isAddress(maticXAddress)) { 56 | throw new Error("Invalid MaticX address"); 57 | } 58 | if (!ethers.isAddress(managerAddress)) { 59 | throw new Error("Invalid Manager address"); 60 | } 61 | 62 | const networkName = network.name as Network; 63 | console.log(`Network name: ${networkName}`); 64 | if (!isLocalNetwork(networkName)) { 65 | await run(TASK_CLEAN); 66 | } 67 | await run(TASK_COMPILE); 68 | 69 | const signer = await getSigner( 70 | ethers, 71 | network.provider, 72 | network.config.from 73 | ); 74 | const ValidatorRegistry = await ethers.getContractFactory( 75 | "ValidatorRegistry", 76 | signer 77 | ); 78 | 79 | const validatorRegistry = await upgrades.deployProxy( 80 | ValidatorRegistry, 81 | [ 82 | stakeManagerAddress, 83 | maticAddress, 84 | maticXAddress, 85 | managerAddress, 86 | ], 87 | { kind: "transparent" } 88 | ); 89 | await validatorRegistry.waitForDeployment(); 90 | 91 | const validatorRegistryAddress = 92 | await validatorRegistry.getAddress(); 93 | console.log( 94 | `ValidatorRegistry Proxy deployed at ${validatorRegistryAddress}` 95 | ); 96 | 97 | const implementationAddress = 98 | await upgrades.erc1967.getImplementationAddress( 99 | validatorRegistryAddress 100 | ); 101 | console.log( 102 | `ValidatorRegistry Implementation deployed at ${implementationAddress}` 103 | ); 104 | 105 | const proxyAdminAddress = await upgrades.erc1967.getAdminAddress( 106 | validatorRegistryAddress 107 | ); 108 | console.log( 109 | `ValidatorRegistry ProxyAdmin deployed at ${proxyAdminAddress}` 110 | ); 111 | } 112 | ); 113 | -------------------------------------------------------------------------------- /tasks/generate-initializev2-calldata-matic-x.ts: -------------------------------------------------------------------------------- 1 | import { AbiCoder } from "ethers"; 2 | import { task, types } from "hardhat/config"; 3 | 4 | interface TaskParams { 5 | maticXProxy: string; 6 | maticXImplementation: string; 7 | pol: string; 8 | } 9 | 10 | task("generate-initializev2-calldata-matic-x") 11 | .setDescription("Generate initializeV2 calldata for the MaticX contract") 12 | .addParam( 13 | "maticXProxy", 14 | "Address of the MaticX proxy contract", 15 | undefined, 16 | types.string 17 | ) 18 | .addParam( 19 | "maticXImplementation", 20 | "Address of the MaticX implementation contract", 21 | undefined, 22 | types.string 23 | ) 24 | .addParam("pol", "POL contract address", undefined, types.string) 25 | .setAction( 26 | async ( 27 | { 28 | maticXProxy: maticXProxyAddress, 29 | maticXImplementation: maticXImplementationAddress, 30 | pol: polAddress, 31 | }: TaskParams, 32 | { ethers } 33 | ) => { 34 | if (!ethers.isAddress(maticXProxyAddress)) { 35 | throw new Error("Invalid MaticX proxy address"); 36 | } 37 | if (!ethers.isAddress(maticXImplementationAddress)) { 38 | throw new Error("Invalid MaticX implementation address"); 39 | } 40 | if (!ethers.isAddress(polAddress)) { 41 | throw new Error("Invalid POL address"); 42 | } 43 | 44 | const proxyAdmin = await ethers.getContractFactory( 45 | "contracts/ProxyAdmin.sol:ProxyAdmin" 46 | ); 47 | const maticX = await ethers.getContractFactory("MaticX"); 48 | 49 | const initialzeV2Selector = 50 | maticX.interface.getFunction("initializeV2")?.selector; 51 | if (!initialzeV2Selector) { 52 | throw new Error("InitializeV2 selector on MaticX not defined"); 53 | } 54 | 55 | const abiCoder = new AbiCoder(); 56 | const encodedPOLAddress = abiCoder.encode( 57 | ["address"], 58 | [polAddress] 59 | ); 60 | 61 | const initializeV2Calldata = 62 | proxyAdmin.interface.encodeFunctionData("upgradeAndCall", [ 63 | maticXProxyAddress, 64 | maticXImplementationAddress, 65 | `${initialzeV2Selector}${encodedPOLAddress.slice(2)}`, 66 | ]); 67 | 68 | console.log("Initialize v2 calldata:\n%s", initializeV2Calldata); 69 | } 70 | ); 71 | -------------------------------------------------------------------------------- /tasks/generate-initializev2-calldata-validator-registry.ts: -------------------------------------------------------------------------------- 1 | import { AbiCoder } from "ethers"; 2 | import { task, types } from "hardhat/config"; 3 | 4 | interface TaskParams { 5 | validatorRegistryProxy: string; 6 | validatorRegistryImplementation: string; 7 | pol: string; 8 | } 9 | 10 | task("generate-initializev2-calldata-validator-registry") 11 | .setDescription( 12 | "Generate initializeV2 calldata for the ValidatorRegistry contract" 13 | ) 14 | .addParam( 15 | "validatorRegistryProxy", 16 | "Address of the ValidatorRegistry proxy contract", 17 | undefined, 18 | types.string 19 | ) 20 | .addParam( 21 | "validatorRegistryImplementation", 22 | "Address of the ValidatorRegistry implementation contract", 23 | undefined, 24 | types.string 25 | ) 26 | .addParam("pol", "POL contract address", undefined, types.string) 27 | .setAction( 28 | async ( 29 | { 30 | validatorRegistryProxy: validatorRegistryProxyAddress, 31 | validatorRegistryImplementation: 32 | validatorRegistryImplementationAddress, 33 | pol: polAddress, 34 | }: TaskParams, 35 | { ethers } 36 | ) => { 37 | if (!ethers.isAddress(validatorRegistryProxyAddress)) { 38 | throw new Error("Invalid ValidatorRegistry proxy address"); 39 | } 40 | if (!ethers.isAddress(validatorRegistryImplementationAddress)) { 41 | throw new Error( 42 | "Invalid ValidatorRegistry implementation address" 43 | ); 44 | } 45 | if (!ethers.isAddress(polAddress)) { 46 | throw new Error("Invalid POL address"); 47 | } 48 | 49 | const proxyAdmin = await ethers.getContractFactory( 50 | "contracts/ProxyAdmin.sol:ProxyAdmin" 51 | ); 52 | const validatorRegistry = 53 | await ethers.getContractFactory("ValidatorRegistry"); 54 | 55 | const initialzeV2Selector = 56 | validatorRegistry.interface.getFunction( 57 | "initializeV2" 58 | )?.selector; 59 | if (!initialzeV2Selector) { 60 | throw new Error( 61 | "InitializeV2 selector on ValidatorRegistry not defined" 62 | ); 63 | } 64 | 65 | const abiCoder = new AbiCoder(); 66 | const encodedPOLAddress = abiCoder.encode( 67 | ["address"], 68 | [polAddress] 69 | ); 70 | 71 | const initializeV2Calldata = 72 | proxyAdmin.interface.encodeFunctionData("upgradeAndCall", [ 73 | validatorRegistryProxyAddress, 74 | validatorRegistryImplementationAddress, 75 | `${initialzeV2Selector}${encodedPOLAddress.slice(2)}`, 76 | ]); 77 | 78 | console.log("Initialize v2 calldata:\n%s", initializeV2Calldata); 79 | } 80 | ); 81 | -------------------------------------------------------------------------------- /tasks/import-contract.ts: -------------------------------------------------------------------------------- 1 | import { task, types } from "hardhat/config"; 2 | import { TASK_CLEAN, TASK_COMPILE } from "hardhat/builtin-tasks/task-names"; 3 | import { getSigner } from "../utils/account"; 4 | import { isLocalNetwork, Network } from "../utils/network"; 5 | 6 | interface TaskParams { 7 | name: string; 8 | contract: string; 9 | } 10 | 11 | task("import-contract") 12 | .setDescription("Import a contract") 13 | .addParam("name", "Contract name", undefined, types.string) 14 | .addParam("contract", "Contract address", undefined, types.string) 15 | .setAction( 16 | async ( 17 | { name: contractName, contract: contractAddress }: TaskParams, 18 | { ethers, upgrades, network, run } 19 | ) => { 20 | if (!ethers.isAddress(contractAddress)) { 21 | throw new Error("Invalid contract address"); 22 | } 23 | 24 | const networkName = network.name as Network; 25 | console.log(`Network name: ${network.name}`); 26 | if (isLocalNetwork(networkName)) { 27 | throw new Error("Unsupported network"); 28 | } 29 | 30 | await run(TASK_CLEAN); 31 | await run(TASK_COMPILE); 32 | 33 | const signer = await getSigner( 34 | ethers, 35 | network.provider, 36 | network.config.from 37 | ); 38 | const ContractFactory = await ethers.getContractFactory( 39 | contractName, 40 | signer 41 | ); 42 | 43 | await upgrades.forceImport(contractAddress, ContractFactory); 44 | console.log(`${contractName} imported at ${contractAddress}`); 45 | } 46 | ); 47 | -------------------------------------------------------------------------------- /tasks/index.ts: -------------------------------------------------------------------------------- 1 | import "./deploy-child-pool"; 2 | import "./deploy-fx-state-child-tunnel"; 3 | import "./deploy-fx-state-root-tunnel"; 4 | import "./deploy-implementation"; 5 | import "./deploy-matic-x"; 6 | import "./deploy-timelock-controller"; 7 | import "./deploy-u-child-erc20"; 8 | import "./deploy-validator-registry"; 9 | import "./generate-initializev2-calldata-matic-x"; 10 | import "./generate-initializev2-calldata-validator-registry"; 11 | import "./import-contract"; 12 | import "./initialize-v2-matic-x"; 13 | import "./initialize-v2-validator-registry"; 14 | import "./upgrade-contract"; 15 | import "./validate-child-deployment"; 16 | import "./validate-parent-deployment"; 17 | import "./verify-contract"; 18 | -------------------------------------------------------------------------------- /tasks/initialize-v2-matic-x.ts: -------------------------------------------------------------------------------- 1 | import { task, types } from "hardhat/config"; 2 | import { getSigner } from "../utils/account"; 3 | import { isLocalNetwork, Network } from "../utils/network"; 4 | 5 | interface TaskParams { 6 | contract: string; 7 | polToken: string; 8 | } 9 | 10 | task("initialize-v2:matic-x") 11 | .setDescription("Initialize v2 the MaticX contract") 12 | .addParam("contract", "Contract address", undefined, types.string) 13 | .addParam("polToken", "POL token address", undefined, types.string) 14 | .setAction( 15 | async ( 16 | { 17 | contract: contractAddress, 18 | polToken: polTokenAddress, 19 | }: TaskParams, 20 | { ethers, network } 21 | ) => { 22 | if (!ethers.isAddress(contractAddress)) { 23 | throw new Error("Invalid contract address"); 24 | } 25 | if (!ethers.isAddress(polTokenAddress)) { 26 | throw new Error("Invalid POL token address"); 27 | } 28 | 29 | const networkName = network.name as Network; 30 | console.log(`Network name: ${network.name}`); 31 | if (isLocalNetwork(networkName)) { 32 | throw new Error("Unsupported network"); 33 | } 34 | 35 | const signer = await getSigner( 36 | ethers, 37 | network.provider, 38 | network.config.from 39 | ); 40 | const contractFactory = await ethers.getContractAt( 41 | "MaticX", 42 | contractAddress, 43 | signer 44 | ); 45 | 46 | const tx = await contractFactory.initializeV2(polTokenAddress); 47 | await tx.wait(1); 48 | console.log(`MaticX initialized v2 at ${contractAddress}`); 49 | } 50 | ); 51 | -------------------------------------------------------------------------------- /tasks/initialize-v2-validator-registry.ts: -------------------------------------------------------------------------------- 1 | import { task, types } from "hardhat/config"; 2 | import { getSigner } from "../utils/account"; 3 | import { isLocalNetwork, Network } from "../utils/network"; 4 | 5 | interface TaskParams { 6 | contract: string; 7 | polToken: string; 8 | } 9 | 10 | task("initialize-v2:validator-registry") 11 | .setDescription("Initialize v2 the ValidatorRegistry contract") 12 | .addParam("contract", "Contract address", undefined, types.string) 13 | .addParam("polToken", "POL token address", undefined, types.string) 14 | .setAction( 15 | async ( 16 | { 17 | contract: contractAddress, 18 | polToken: polTokenAddress, 19 | }: TaskParams, 20 | { ethers, network } 21 | ) => { 22 | if (!ethers.isAddress(contractAddress)) { 23 | throw new Error("Invalid contract address"); 24 | } 25 | if (!ethers.isAddress(polTokenAddress)) { 26 | throw new Error("Invalid POL token address"); 27 | } 28 | 29 | const networkName = network.name as Network; 30 | console.log(`Network name: ${network.name}`); 31 | if (isLocalNetwork(networkName)) { 32 | throw new Error("Unsupported network"); 33 | } 34 | 35 | const signer = await getSigner( 36 | ethers, 37 | network.provider, 38 | network.config.from 39 | ); 40 | const contractFactory = await ethers.getContractAt( 41 | "ValidatorRegistry", 42 | contractAddress, 43 | signer 44 | ); 45 | 46 | const tx = await contractFactory.initializeV2(polTokenAddress); 47 | await tx.wait(1); 48 | console.log( 49 | `ValidatorRegistry initialized v2 at ${contractAddress}` 50 | ); 51 | } 52 | ); 53 | -------------------------------------------------------------------------------- /tasks/upgrade-contract.ts: -------------------------------------------------------------------------------- 1 | import { TASK_CLEAN, TASK_COMPILE } from "hardhat/builtin-tasks/task-names"; 2 | import { task, types } from "hardhat/config"; 3 | import { getSigner } from "../utils/account"; 4 | import { isLocalNetwork, Network } from "../utils/network"; 5 | 6 | interface TaskParams { 7 | name: string; 8 | contract: string; 9 | } 10 | 11 | task("upgrade-contract") 12 | .setDescription("Upgrade a contract") 13 | .addParam("name", "Contract name", undefined, types.string) 14 | .addParam("contract", "Contract address", undefined, types.string) 15 | .setAction( 16 | async ( 17 | { name: contractName, contract: contractAddress }: TaskParams, 18 | { ethers, upgrades, network, run } 19 | ) => { 20 | if (!ethers.isAddress(contractAddress)) { 21 | throw new Error("Invalid contract address"); 22 | } 23 | 24 | const networkName = network.name as Network; 25 | console.log(`Network name: ${networkName}`); 26 | 27 | if (!isLocalNetwork(networkName)) { 28 | await run(TASK_CLEAN); 29 | } 30 | await run(TASK_COMPILE); 31 | 32 | const signer = await getSigner( 33 | ethers, 34 | network.provider, 35 | network.config.from 36 | ); 37 | const adjustedContractName = isLocalNetwork(networkName) 38 | ? `${contractName}Mock` 39 | : contractName; 40 | const ContractFactory = await ethers.getContractFactory( 41 | adjustedContractName, 42 | signer 43 | ); 44 | 45 | const contract = await upgrades.upgradeProxy( 46 | contractAddress, 47 | ContractFactory, 48 | { kind: "transparent" } 49 | ); 50 | await contract.waitForDeployment(); 51 | 52 | const implementationAddress = 53 | await upgrades.erc1967.getImplementationAddress( 54 | contractAddress 55 | ); 56 | console.log( 57 | `${contractName} upgraded with implementation ${implementationAddress}` 58 | ); 59 | } 60 | ); 61 | -------------------------------------------------------------------------------- /tasks/validate-child-deployment.ts: -------------------------------------------------------------------------------- 1 | import { subtask, task, types } from "hardhat/config"; 2 | import { isLocalNetwork, Network } from "../utils/network"; 3 | import { ChildPool, FxStateChildTunnel, MaticX } from "../typechain-types"; 4 | 5 | interface TaskParams { 6 | childPool: string; 7 | maticX: string; 8 | fxStateRootTunnel: string; 9 | fxStateChildTunnel: string; 10 | stakeManager: string; 11 | checkpointManager: string; 12 | fxChild: string; 13 | matic: string; 14 | manager: string; 15 | treasury: string; 16 | instantPoolOwner: string; 17 | deployer: string; 18 | } 19 | 20 | type AccessControlledContract = ChildPool | FxStateChildTunnel | MaticX; 21 | 22 | task("validate-child-deployment") 23 | .setDescription("Validate child deployment") 24 | .addParam( 25 | "childPool", 26 | "ChildPool contract address", 27 | undefined, 28 | types.string 29 | ) 30 | .addParam( 31 | "maticX", 32 | "MaticX contract address", 33 | undefined, 34 | types.string 35 | ) 36 | .addParam( 37 | "fxStateChildTunnel", 38 | "FxStateChildTunnel contract address", 39 | undefined, 40 | types.string 41 | ) 42 | .addParam( 43 | "fxStateRootTunnel", 44 | "FxStateRootTunnel contract address", 45 | undefined, 46 | types.string 47 | ) 48 | .addParam( 49 | "fxChild", 50 | "FxRoot contract address", 51 | undefined, 52 | types.string 53 | ) 54 | .addParam( 55 | "manager", 56 | "Manager contract address", 57 | undefined, 58 | types.string 59 | ) 60 | .addParam( 61 | "treasury", 62 | "Treasury contract address", 63 | undefined, 64 | types.string 65 | ) 66 | .addParam( 67 | "instantPoolOwner", 68 | "InstantPoolOwner contract address", 69 | undefined, 70 | types.string 71 | ) 72 | .addParam( 73 | "deployer", 74 | "Deployer contract address", 75 | undefined, 76 | types.string 77 | ) 78 | .setAction( 79 | async ( 80 | { 81 | childPool: childPoolAddress, 82 | maticX: maticXAddress, 83 | fxStateChildTunnel: fxStateChildTunnelAddress, 84 | fxStateRootTunnel: fxStateRootTunnelAddress, 85 | fxChild: fxChildAddress, 86 | manager: managerAddress, 87 | treasury: treasuryAddress, 88 | instantPoolOwner: instantPoolOwnerAddress, 89 | deployer: deployerAddress, 90 | }: TaskParams, 91 | { ethers, network, run } 92 | ) => { 93 | if (!ethers.isAddress(childPoolAddress)) { 94 | throw new Error("Invalid ChildPool address"); 95 | } 96 | if (!ethers.isAddress(maticXAddress)) { 97 | throw new Error("Invalid MaticX address"); 98 | } 99 | if (!ethers.isAddress(fxStateRootTunnelAddress)) { 100 | throw new Error("Invalid FxStateRootTunnel address"); 101 | } 102 | if (!ethers.isAddress(fxStateChildTunnelAddress)) { 103 | throw new Error("Invalid FxStateChildTunnel address"); 104 | } 105 | if (!ethers.isAddress(fxChildAddress)) { 106 | throw new Error("Invalid FxRoot address"); 107 | } 108 | if (!ethers.isAddress(managerAddress)) { 109 | throw new Error("Invalid Manager address"); 110 | } 111 | if (!ethers.isAddress(treasuryAddress)) { 112 | throw new Error("Invalid Treasury address"); 113 | } 114 | if (!ethers.isAddress(instantPoolOwnerAddress)) { 115 | throw new Error("Invalid InstantPoolOwner address"); 116 | } 117 | if (!ethers.isAddress(deployerAddress)) { 118 | throw new Error("Invalid Deployer address"); 119 | } 120 | 121 | const networkName = network.name as Network; 122 | console.log(`Network name: ${networkName}`); 123 | if (isLocalNetwork(networkName)) { 124 | throw new Error("Unsupported network"); 125 | } 126 | 127 | await run("validate-child-deployment:child-pool", { 128 | childPoolAddress, 129 | maticXAddress, 130 | fxStateChildTunnelAddress, 131 | managerAddress, 132 | treasuryAddress, 133 | instantPoolOwnerAddress, 134 | }); 135 | 136 | await run("validate-child-deployment:matic-x", { 137 | maticXAddress, 138 | childPoolAddress, 139 | fxStateRootTunnelAddress, 140 | managerAddress, 141 | treasuryAddress, 142 | }); 143 | 144 | await run("validate-child-deployment:fx-state-child-tunnel", { 145 | fxStateRootTunnelAddress, 146 | maticXAddress, 147 | fxStateChildTunnelAddress, 148 | fxChildAddress, 149 | deployerAddress, 150 | }); 151 | } 152 | ); 153 | 154 | subtask("validate-child-deployment:child-pool") 155 | .setDescription("Validate ChildPool deployment") 156 | .addParam("childPoolAddress") 157 | .addParam("maticXAddress") 158 | .addParam("fxStateChildTunnelAddress") 159 | .addParam("managerAddress") 160 | .addParam("treasuryAddress") 161 | .addParam("instantPoolOwnerAddress") 162 | .setAction( 163 | async ( 164 | { 165 | childPoolAddress, 166 | maticXAddress, 167 | fxStateChildTunnelAddress, 168 | managerAddress, 169 | treasuryAddress, 170 | instantPoolOwnerAddress, 171 | }, 172 | { ethers } 173 | ) => { 174 | console.log("ChildPool validation started"); 175 | const childPool = await ethers.getContractAt( 176 | "ChildPool", 177 | childPoolAddress 178 | ); 179 | 180 | await validateAccessControl( 181 | childPool, 182 | "ChildPool", 183 | childPoolAddress, 184 | managerAddress 185 | ); 186 | 187 | const [ 188 | currentFxStateChildTunnelAddress, 189 | currentMaticXAddress, 190 | currentTrustedForwarder, 191 | ] = await childPool.getContracts(); 192 | if ( 193 | currentFxStateChildTunnelAddress !== fxStateChildTunnelAddress 194 | ) { 195 | throw new Error( 196 | `Call setFxStateChildTunnel(${fxStateChildTunnelAddress}) on ChildPool(${childPoolAddress}) contract` 197 | ); 198 | } 199 | if (currentMaticXAddress !== maticXAddress) { 200 | throw new Error( 201 | `Invalid MaticX contract. Redeploy ChildPool(${childPoolAddress}) contract` 202 | ); 203 | } 204 | if (currentTrustedForwarder !== ethers.ZeroAddress) { 205 | throw new Error( 206 | `Call setTrustedForwarder(${ethers.ZeroAddress}) on ChildPool(${childPoolAddress}) contract` 207 | ); 208 | } 209 | 210 | const currentTreasuryAddress = await childPool.treasury(); 211 | if (currentTreasuryAddress !== treasuryAddress) { 212 | throw new Error( 213 | `Call setTreasury(${treasuryAddress}) on ChildPool(${childPoolAddress}) contract` 214 | ); 215 | } 216 | 217 | const currentInstantPoolOwnerAddress = 218 | await childPool.instantPoolOwner(); 219 | if (currentInstantPoolOwnerAddress !== instantPoolOwnerAddress) { 220 | throw new Error( 221 | `Call setInstantPoolOwner(${instantPoolOwnerAddress}) on ChildPool(${childPoolAddress}) contract` 222 | ); 223 | } 224 | 225 | console.log("ChildPool validation finished\n"); 226 | } 227 | ); 228 | 229 | subtask("validate-child-deployment:matic-x") 230 | .setDescription("Validate MaticX deployment") 231 | .addParam("maticXAddress") 232 | .setAction(async ({ maticXAddress }, { ethers }) => { 233 | console.log("MaticX validation started"); 234 | const maticX = await ethers.getContractAt("MaticX", maticXAddress); 235 | 236 | const currentName = await maticX.name(); 237 | if (currentName !== "Liquid Staking Matic (PoS)") { 238 | throw new Error( 239 | `Invalid name. Redeploy MaticX(${maticXAddress}) contract` 240 | ); 241 | } 242 | 243 | const currentSymbol = await maticX.symbol(); 244 | if (currentSymbol !== "MaticX") { 245 | throw new Error( 246 | `Invalid symbol. Redeploy MaticX(${maticXAddress}) contract` 247 | ); 248 | } 249 | 250 | console.log("MaticX validation finished\n"); 251 | }); 252 | 253 | subtask("validate-child-deployment:fx-state-child-tunnel") 254 | .setDescription("Validate FxStateChildTunnel deployment") 255 | .addParam("fxStateChildTunnelAddress") 256 | .addParam("fxStateRootTunnelAddress") 257 | .addParam("fxChildAddress") 258 | .addParam("deployerAddress") 259 | .setAction( 260 | async ( 261 | { 262 | fxStateChildTunnelAddress, 263 | fxStateRootTunnelAddress, 264 | fxChildAddress, 265 | deployerAddress, 266 | }, 267 | { ethers } 268 | ) => { 269 | console.log("FxStateChildTunnel validation started"); 270 | const fxStateChildTunnel = await ethers.getContractAt( 271 | "FxStateChildTunnel", 272 | fxStateChildTunnelAddress 273 | ); 274 | 275 | await validateAccessControl( 276 | fxStateChildTunnel, 277 | "FxStateChildTunnel", 278 | fxStateChildTunnelAddress, 279 | deployerAddress 280 | ); 281 | 282 | const currentFxChildAddress = await fxStateChildTunnel.fxChild(); 283 | if (currentFxChildAddress !== fxChildAddress) { 284 | throw new Error( 285 | `Invalid FxChild contract. Redeploy FxStateChidTunnel(${fxStateChildTunnelAddress}) contract` 286 | ); 287 | } 288 | 289 | const currentFxStateRootTunnelAddress = 290 | await fxStateChildTunnel.fxRootTunnel(); 291 | if (currentFxStateRootTunnelAddress !== fxStateRootTunnelAddress) { 292 | throw new Error( 293 | `Call setFxRootTunnel(${fxStateRootTunnelAddress}) on FxStateChildTunnel(${fxStateChildTunnelAddress}) contract` 294 | ); 295 | } 296 | 297 | console.log("FxStateChildTunnel validation finished\n"); 298 | } 299 | ); 300 | 301 | async function validateAccessControl( 302 | contract: AccessControlledContract, 303 | contractName: string, 304 | contractAddress: string, 305 | defaultAdminAddress: string 306 | ) { 307 | const defaultAdminRole = await contract.DEFAULT_ADMIN_ROLE(); 308 | const hasRole = await contract.hasRole( 309 | defaultAdminRole, 310 | defaultAdminAddress 311 | ); 312 | if (!hasRole) { 313 | throw new Error( 314 | `Call grantRole(${defaultAdminRole} ${defaultAdminAddress}) on ${contractName}(${contractAddress})` 315 | ); 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /tasks/verify-contract.ts: -------------------------------------------------------------------------------- 1 | import { task, types } from "hardhat/config"; 2 | import { TASK_CLEAN, TASK_COMPILE } from "hardhat/builtin-tasks/task-names"; 3 | import { isLocalNetwork, Network } from "../utils/network"; 4 | 5 | interface TaskParams { 6 | contract: string; 7 | contractPath: string; 8 | constructorArguments: string[]; 9 | } 10 | 11 | task("verify-contract") 12 | .setDescription("Verify a contract") 13 | .addParam("contract", "Contract address", undefined, types.string) 14 | .addOptionalParam( 15 | "contractPath", 16 | "Contract path", 17 | undefined, 18 | types.string 19 | ) 20 | .addOptionalVariadicPositionalParam( 21 | "constructorArguments", 22 | "Constructor arguments", 23 | [], 24 | types.string 25 | ) 26 | .setAction( 27 | async ( 28 | { 29 | contract: contractAddress, 30 | contractPath, 31 | constructorArguments, 32 | }: TaskParams, 33 | { ethers, network, run } 34 | ) => { 35 | if (!ethers.isAddress(contractAddress)) { 36 | throw new Error("Invalid contract address"); 37 | } 38 | 39 | const networkName = network.name as Network; 40 | console.log(`Network name: ${networkName}`); 41 | if (isLocalNetwork(networkName)) { 42 | throw new Error("Unsupported network"); 43 | } 44 | 45 | await run(TASK_CLEAN); 46 | await run(TASK_COMPILE); 47 | 48 | const preparedConstructorArguments: (string | string[])[] = []; 49 | for (const argument of constructorArguments) { 50 | preparedConstructorArguments.push( 51 | typeof argument === "string" && 52 | argument[0] === "[" && 53 | argument[argument.length - 1] === "]" 54 | ? [argument.slice(1, -1)] 55 | : argument 56 | ); 57 | } 58 | 59 | await run("verify:verify", { 60 | address: contractAddress, 61 | contract: contractPath, 62 | constructorArguments: preparedConstructorArguments, 63 | force: true, 64 | }); 65 | } 66 | ); 67 | -------------------------------------------------------------------------------- /testnet-deployment-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "network": 3, 3 | "multisig_upgrader": { "address": "0x", "owners": [] }, 4 | "root_deployer": "0xe5f0452A677e05F648642B83f1C2D7bC959C6f7c", 5 | "manager": "0xe5f0452A677e05F648642B83f1C2D7bC959C6f7c", 6 | "treasury": "0xe5f0452A677e05F648642B83f1C2D7bC959C6f7c", 7 | "matic_erc20_address": "0xA108830A23A9a054FfF4470a8e6292da0886A4D4", 8 | "matic_stake_manager_proxy": "0xB36B6963F68dDe1312a9E959817E35fF6b0f0Aa9", 9 | "proxy_admin": "0xcc26428A53746F6EfE2C0c8403F575bb7e7dc2F6", 10 | "maticX_proxy": "0xD3ADB35Cd2c42aEA1fBf3d533Fbb4F40A55c0fEc", 11 | "maticX_impl": "0x6Eb2689e40B8F9643C5E7fB2Fa7208B86acF7A50", 12 | "validator_registry_proxy": "0x54BAC3CD3D7Fd4dA1009E2d84346BF4980039849", 13 | "validator_registry_impl": "0x8F13dF07e5bc679B1a6c739348213dcaE9D2D262" 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node20/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "noEmit": true, 6 | "resolveJsonModule": true, 7 | "typeRoots": ["./types"] 8 | }, 9 | "include": [ 10 | "scripts/**/*.ts", 11 | "tasks/**/*.ts", 12 | "test/**/*.ts", 13 | "typechain-types/**/*.ts", 14 | "types/**/*.ts", 15 | "utils/**/*.ts", 16 | ".solcover.js", 17 | "eslint.config.js", 18 | "hardhat.config.ts", 19 | "prettier.config.js" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /utils/account.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { ethers as ethersType } from "ethers"; 3 | import { EIP1193Provider } from "hardhat/types"; 4 | 5 | type HardhatEthers = typeof ethersType; 6 | 7 | export async function getSigner( 8 | ethers: HardhatEthers, 9 | ethereum: EIP1193Provider, 10 | address?: string 11 | ) { 12 | const provider = new ethers.BrowserProvider(ethereum); 13 | return provider.getSigner(address); 14 | } 15 | 16 | export function generateRandomAddress() { 17 | const privateKey = `0x${Buffer.from(ethers.randomBytes(32)).toString("hex")}`; 18 | return new ethers.Wallet(privateKey).address; 19 | } 20 | -------------------------------------------------------------------------------- /utils/environment.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | import { Provider } from "./network"; 3 | 4 | interface EnvironmentSchema { 5 | RPC_PROVIDER: Provider; 6 | SEPOLIA_API_KEY: string; 7 | AMOY_API_KEY: string; 8 | ETHEREUM_API_KEY: string; 9 | POLYGON_API_KEY: string; 10 | ETHERSCAN_API_KEY: string; 11 | POLYGONSCAN_API_KEY: string; 12 | FORKING_BLOCK_NUMBER: number; 13 | COINMARKETCAP_API_KEY: string; 14 | GAS_REPORTER_NETWORK: string; 15 | GAS_PRICE_GWEI: number; 16 | REPORT_GAS: boolean; 17 | DEPLOYER_MNEMONIC: string; 18 | DEPLOYER_PASSPHRASE: string; 19 | DEPLOYER_ADDRESS: string; 20 | } 21 | 22 | const API_KEY_REGEX = /^[0-9A-Za-z_-]{32,64}$/; 23 | const MNEMONIC_REGEX = /^([a-z ]+){12,24}$/; 24 | const ADDRESS_REGEX = /^0x[0-9A-Fa-f]{40}$/; 25 | 26 | export function extractEnvironmentVariables(): EnvironmentSchema { 27 | const envSchema = Joi.object() 28 | .keys({ 29 | RPC_PROVIDER: Joi.string() 30 | .optional() 31 | .valid("alchemy", "ankr", "infura") 32 | .default("ankr") 33 | .default("RPC provider name"), 34 | SEPOLIA_API_KEY: Joi.string() 35 | .required() 36 | .regex(API_KEY_REGEX) 37 | .description("API key for Sepolia"), 38 | AMOY_API_KEY: Joi.string() 39 | .required() 40 | .regex(API_KEY_REGEX) 41 | .description("API key for Amoy"), 42 | ETHEREUM_API_KEY: Joi.string() 43 | .required() 44 | .regex(API_KEY_REGEX) 45 | .description("API key for Ethereum"), 46 | POLYGON_API_KEY: Joi.string() 47 | .required() 48 | .regex(API_KEY_REGEX) 49 | .description("API key for Polygon"), 50 | ETHERSCAN_API_KEY: Joi.string() 51 | .required() 52 | .length(34) 53 | .alphanum() 54 | .description("API key for Etherscan"), 55 | POLYGONSCAN_API_KEY: Joi.string() 56 | .required() 57 | .length(34) 58 | .alphanum() 59 | .description("API key for Polygonscan"), 60 | FORKING_BLOCK_NUMBER: Joi.number() 61 | .optional() 62 | .integer() 63 | .min(0) 64 | .default(0) 65 | .description("Block number for Hardhat forking on Ethereum"), 66 | COINMARKETCAP_API_KEY: Joi.string() 67 | .optional() 68 | .allow("") 69 | .uuid({ version: "uuidv4" }) 70 | .description("API key for Coinmarketcap"), 71 | GAS_REPORTER_NETWORK: Joi.string() 72 | .optional() 73 | .allow("ethereum", "polygon") 74 | .default("ethereum") 75 | .description("Gas reporter network"), 76 | GAS_PRICE_GWEI: Joi.number() 77 | .optional() 78 | .integer() 79 | .min(0) 80 | .default(0) 81 | .description("Gas price in Gwei"), 82 | REPORT_GAS: Joi.boolean() 83 | .optional() 84 | .default(false) 85 | .description("Flag to report gas price or not"), 86 | DEPLOYER_MNEMONIC: Joi.string() 87 | .optional() 88 | .default( 89 | "test test test test test test test test test test test junk" 90 | ) 91 | .regex(MNEMONIC_REGEX) 92 | .description("Mnemonic phrase of deployer account"), 93 | DEPLOYER_PASSPHRASE: Joi.string() 94 | .optional() 95 | .allow("") 96 | .description("Passphrase of deployer account"), 97 | DEPLOYER_ADDRESS: Joi.string() 98 | .required() 99 | .regex(ADDRESS_REGEX) 100 | .description("Address of deployer account"), 101 | }) 102 | .unknown() as Joi.ObjectSchema; 103 | 104 | const { value: envVars, error } = envSchema 105 | .prefs({ 106 | errors: { 107 | label: "key", 108 | }, 109 | }) 110 | .validate(process.env); 111 | if (error) { 112 | throw new Error(error.annotate()); 113 | } 114 | return envVars; 115 | } 116 | -------------------------------------------------------------------------------- /utils/network.ts: -------------------------------------------------------------------------------- 1 | export enum Provider { 2 | Alchemy = "alchemy", 3 | Ankr = "ankr", 4 | Infura = "infura", 5 | } 6 | 7 | export enum Network { 8 | Hardhat = "hardhat", 9 | Localhost = "localhost", 10 | Sepolia = "sepolia", 11 | Amoy = "amoy", 12 | AmoyAlt = "polygonAmoy", 13 | Ethereum = "ethereum", 14 | EthereumAlt = "mainnet", 15 | Polygon = "polygon", 16 | } 17 | 18 | export function getProviderUrl( 19 | network: Network, 20 | provider: Provider, 21 | apiKey: string 22 | ): string { 23 | if (network === Network.Localhost) { 24 | return "http://127.0.0.1:8545"; 25 | } 26 | 27 | const urls: Record> = { 28 | [Network.Sepolia]: { 29 | [Provider.Alchemy]: "https://eth-sepolia.g.alchemy.com", 30 | [Provider.Ankr]: "https://rpc.ankr.com/eth_sepolia", 31 | [Provider.Infura]: "https://sepolia.infura.io", 32 | }, 33 | [Network.Amoy]: { 34 | [Provider.Alchemy]: "https://polygon-amoy.g.alchemy.com", 35 | [Provider.Ankr]: "https://rpc.ankr.com/polygon_amoy", 36 | [Provider.Infura]: "https://polygon-amoy.infura.io", 37 | }, 38 | [Network.AmoyAlt]: { 39 | [Provider.Alchemy]: "https://polygon-amoy.g.alchemy.com", 40 | [Provider.Ankr]: "https://rpc.ankr.com/polygon_amoy", 41 | [Provider.Infura]: "https://polygon-amoy.infura.io", 42 | }, 43 | [Network.Ethereum]: { 44 | [Provider.Alchemy]: "https://eth-mainnet.g.alchemy.com", 45 | [Provider.Ankr]: "https://rpc.ankr.com/eth", 46 | [Provider.Infura]: "https://mainnet.infura.io", 47 | }, 48 | [Network.EthereumAlt]: { 49 | [Provider.Alchemy]: "https://eth-mainnet.g.alchemy.com", 50 | [Provider.Ankr]: "https://rpc.ankr.com/eth", 51 | [Provider.Infura]: "https://mainnet.infura.io", 52 | }, 53 | [Network.Polygon]: { 54 | [Provider.Alchemy]: "https://polygon-mainnet.g.alchemy.com", 55 | [Provider.Ankr]: "https://rpc.ankr.com/polygon", 56 | [Provider.Infura]: "https://polygon-mainnet.infura.io", 57 | }, 58 | }; 59 | 60 | const apiVersions: Record = { 61 | [Provider.Alchemy]: 2, 62 | [Provider.Infura]: 3, 63 | [Provider.Ankr]: undefined, 64 | }; 65 | 66 | const urlParts = [urls[network][provider]]; 67 | if (typeof apiVersions[provider] !== "undefined") { 68 | urlParts.push(`v${apiVersions[provider]}`); 69 | } 70 | urlParts.push(apiKey); 71 | 72 | return urlParts.join("/"); 73 | } 74 | 75 | export function isLocalNetwork(network: Network): boolean { 76 | return [Network.Hardhat, Network.Localhost].includes(network); 77 | } 78 | 79 | export function isTestNetwork(network: Network): boolean { 80 | return [Network.Sepolia, Network.Amoy, Network.AmoyAlt].includes(network); 81 | } 82 | 83 | export function isMainNetwork(network: Network): boolean { 84 | return [Network.Ethereum, Network.EthereumAlt, Network.Polygon].includes( 85 | network 86 | ); 87 | } 88 | --------------------------------------------------------------------------------