diff --git a/.eslintrc.js b/.eslintrc.js index 11a03cb0a..6cd0fedf5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,17 +1,21 @@ module.exports = { - parser: 'babel-eslint', - extends: 'standard', + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features + sourceType: 'module' // Allows for the use of imports + }, + extends: [ + 'standard', + 'plugin:@typescript-eslint/recommended' + ], env: { node: true, es6: true, mocha: true }, rules: { - 'space-before-function-paren': ['error', 'never'] - }, - globals: { - contract: true, - web3: true, - assert: true + 'space-before-function-paren': ['error', 'never'], + quotes: ['error', 'single'], + semi: ['error', 'never'] } } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9dd55761a..e74d53ae4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,8 @@ on: branches: - main pull_request: - + branches: + - main jobs: build: runs-on: ubuntu-latest @@ -14,18 +15,25 @@ jobs: - name: Setup Node.js environment uses: actions/setup-node@v2-beta with: - node-version: '10.x' + node-version: '14.x' registry-url: 'https://registry.npmjs.org' - - name: Cache npm dependencies + - name: Install yarn + run: npm install -g yarn + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + - name: Cache dependencies uses: actions/cache@v1 with: - path: ~/.npm - key: ${{ runner.OS }}-npm-cache-${{ hashFiles('**/package-lock.json') }} + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | - ${{ runner.OS }}-npm-cache- - - name: Install npm dependencies - run: npm install + ${{ runner.os }}-yarn- + - name: Install dependencies + run: yarn --prefer-offline - name: Process templates - run: npm run template:process + run: yarn template:process + - name: Verify contracts storage + run: yarn verify:ci - name: Run tests - run: npm run test:ci + run: yarn test:ci diff --git a/.gitignore b/.gitignore index 12942d270..a6f418d58 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,9 @@ node_modules build contractAddresses.json +artifacts # ignore generated files -contracts/root/predicates/TransferWithSigUtils.sol -contracts/common/mixin/ChainIdMixin.sol test-blockchain/genesis.json test-blockchain/start.sh test/helpers/marketplaceUtils.js @@ -22,3 +21,13 @@ test-blockchain/data coverage/ coverage.json + +*.dbg.json + +cache/ + +debug.json + +typechain/ + +contracts-out/ diff --git a/.nvmrc b/.nvmrc index 68d8f15e2..0627d197d 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -11.1.0 +14.17.4 diff --git a/README.md b/README.md index 29efe9df1..7d2aed185 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,48 @@ -# Matic contracts +# Polygon contracts ![Build Status](https://github.com/maticnetwork/contracts/workflows/CI/badge.svg) -Ethereum smart contracts that power the [Matic Network](https://matic.network). +Ethereum smart contracts that power [Polygon sidechain](https://polygon.technology). + +### Prerequesties + +Yarn and Node 14 ### Install dependencies with ``` -npm install +yarn ``` -### Compile +### Preprocess templates for local BOR bor-chain-id for Mainnet = 137 -bor-chain-id for TestnetV4 (Mumbai) = 80001 +bor-chain-id for Testnet (Mumbai) = 15001 + +For local development you should go with 15001 chain id ``` npm run template:process -- --bor-chain-id -npm run truffle:compile ``` -### Start main chain and side chain +### Start root chain and child chain -- Start Main chain +- Start Root chain ``` -npm run testrpc +yarn testrpc ``` - Start Matic side chain. Requires docker. ``` -npm run bor:simulate +yarn bor:simulate ``` - If you ran a bor instance before, a dead docker container might still be lying around, clean it with ``` -npm run bor:clean +yarn bor:clean ``` - Run a bor (our matic chain node) instance. @@ -47,13 +52,14 @@ npm run bor:clean - For local development ``` -npm run truffle:migrate +yarn deploy:all --network root --child-url CHILD_URL ``` -- For a properly initialized set of contracts, follow the instructions [here](./deploy-migrations/README.md). +By default child url is http://localhost:8545 + ### Run tests ``` -npm test +yarn test ``` diff --git a/contracts/Migrations.sol b/contracts/Migrations.sol deleted file mode 100644 index d542e7fd0..000000000 --- a/contracts/Migrations.sol +++ /dev/null @@ -1,23 +0,0 @@ -pragma solidity ^0.5.2; - -contract Migrations { - address public owner; - uint256 public last_completed_migration; - - modifier restricted() { - if (msg.sender == owner) _; - } - - constructor() public { - owner = msg.sender; - } - - function setCompleted(uint256 completed) public restricted { - last_completed_migration = completed; - } - - function upgrade(address new_address) public restricted { - Migrations upgraded = Migrations(new_address); - upgraded.setCompleted(last_completed_migration); - } -} diff --git a/contracts/common/mixin/ChainIdMixin.sol b/contracts/common/mixin/ChainIdMixin.sol new file mode 100644 index 000000000..715df6029 --- /dev/null +++ b/contracts/common/mixin/ChainIdMixin.sol @@ -0,0 +1,13 @@ +pragma solidity ^0.5.2; + +contract ChainIdMixin { + #if mainnet root + bytes constant public networkId = hex"89"; + uint256 constant public CHAINID = 137; + #endif + + #if goerli child + bytes constant public networkId = hex"3A99"; + uint256 constant public CHAINID = 15001; + #endif +} diff --git a/contracts/common/mixin/ChainIdMixin.sol.template b/contracts/common/mixin/ChainIdMixin.sol.template deleted file mode 100644 index 79415e1f7..000000000 --- a/contracts/common/mixin/ChainIdMixin.sol.template +++ /dev/null @@ -1,6 +0,0 @@ -pragma solidity ^0.5.2; - -contract ChainIdMixin { - bytes constant public networkId = hex"{{ borChainIdHex }}"; - uint256 constant public CHAINID = {{ borChainId }}; -} diff --git a/contracts/root/RootChainProxy.sol b/contracts/root/RootChainProxy.sol index 7703e838e..91563d9b2 100644 --- a/contracts/root/RootChainProxy.sol +++ b/contracts/root/RootChainProxy.sol @@ -1,6 +1,6 @@ pragma solidity ^0.5.2; -import {RootChainStorage} from "./RootChainStorage.sol"; +import {RootChainStorage} from "./RootChainStorage.sol"; import {Proxy} from "../common/misc/Proxy.sol"; import {Registry} from "../common/Registry.sol"; diff --git a/contracts/root/predicates/TransferWithSigUtils.sol.template b/contracts/root/predicates/TransferWithSigUtils.sol similarity index 94% rename from contracts/root/predicates/TransferWithSigUtils.sol.template rename to contracts/root/predicates/TransferWithSigUtils.sol index 4e081b866..4152d3344 100644 --- a/contracts/root/predicates/TransferWithSigUtils.sol.template +++ b/contracts/root/predicates/TransferWithSigUtils.sol @@ -40,7 +40,12 @@ library TransferWithSigUtils { bytes32 EIP712_DOMAIN_SCHEMA_HASH = keccak256(abi.encodePacked(EIP712_DOMAIN_SCHEMA)); string memory EIP712_DOMAIN_NAME = "Matic Network"; string memory EIP712_DOMAIN_VERSION = "1"; - uint256 EIP712_DOMAIN_CHAINID = {{ borChainId }}; + #if mainnet root + uint256 EIP712_DOMAIN_CHAINID = 137; + #endif + #if goerli child + uint256 EIP712_DOMAIN_CHAINID = 15001; + #endif bytes32 EIP712_DOMAIN_HASH = keccak256(abi.encode( EIP712_DOMAIN_SCHEMA_HASH, keccak256(bytes(EIP712_DOMAIN_NAME)), diff --git a/contracts/staking/stakeManager/StakeManager.sol b/contracts/staking/stakeManager/StakeManager.sol index 875f72638..f2bbafd1b 100644 --- a/contracts/staking/stakeManager/StakeManager.sol +++ b/contracts/staking/stakeManager/StakeManager.sol @@ -535,7 +535,14 @@ contract StakeManager is require(delegationEnabled, "Delegation is disabled"); } - updateTimeline(amount, 0, 0); + uint256 deactivationEpoch = validators[validatorId].deactivationEpoch; + + if (deactivationEpoch == 0) { // modify timeline only if validator didn't unstake + updateTimeline(amount, 0, 0); + } else if (deactivationEpoch > currentEpoch) { // validator just unstaked, need to wait till next checkpoint + revert("unstaking"); + } + if (amount >= 0) { increaseValidatorDelegatedAmount(validatorId, uint256(amount)); @@ -560,11 +567,16 @@ contract StakeManager is address currentSigner = validators[validatorId].signer; // update signer event logger.logSignerChange(validatorId, currentSigner, signer, signerPubkey); + + if (validators[validatorId].deactivationEpoch == 0) { + // didn't unstake, swap signer in the list + _removeSigner(currentSigner); + _insertSigner(signer); + } signerToValidator[currentSigner] = INCORRECT_VALIDATOR_ID; signerToValidator[signer] = validatorId; validators[validatorId].signer = signer; - _updateSigner(currentSigner, signer); // reset update time to current time latestSignerUpdateEpoch[validatorId] = _currentEpoch; @@ -1195,11 +1207,6 @@ contract StakeManager is } } - function _updateSigner(address prevSigner, address newSigner) internal { - _removeSigner(prevSigner); - _insertSigner(newSigner); - } - function _removeSigner(address signerToDelete) internal { uint256 totalSigners = signers.length; address swapSigner = signers[totalSigners - 1]; diff --git a/contracts/staking/validatorShare/ValidatorShare.sol b/contracts/staking/validatorShare/ValidatorShare.sol index 68cdea8ea..26eb03fa4 100644 --- a/contracts/staking/validatorShare/ValidatorShare.sol +++ b/contracts/staking/validatorShare/ValidatorShare.sol @@ -312,9 +312,17 @@ contract ValidatorShare is IValidatorShare, ERC20NonTradable, OwnableLockable, I function _getRatePrecision() private view returns (uint256) { // if foundation validator, use old precision + #if mainnet root if (validatorId < 8) { return EXCHANGE_RATE_PRECISION; } + #endif + + #if goerli + if (validatorId < 9) { + return EXCHANGE_RATE_PRECISION; + } + #endif return EXCHANGE_RATE_HIGH_PRECISION; } diff --git a/contracts/test/StakeManagerTestable.sol b/contracts/test/StakeManagerTestable.sol index 2d19a8362..46a1adc55 100644 --- a/contracts/test/StakeManagerTestable.sol +++ b/contracts/test/StakeManagerTestable.sol @@ -5,7 +5,9 @@ import { IValidatorShare } from "../staking/validatorShare/IValidatorShare.sol"; contract StakeManagerTestable is StakeManager { function advanceEpoch(uint256 delta) public { - currentEpoch = currentEpoch.add(delta); + for (uint256 i = 0; i < delta; ++i) { + _finalizeCommit(); + } } function testLockShareContract(uint256 validatorId, bool lock) public { diff --git a/debug.sh b/debug.sh deleted file mode 100644 index ecb3edf1d..000000000 --- a/debug.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env sh - -cd test-blockchain -bash clean.sh -bash start.sh diff --git a/deploy-migrations/1_initial_migration.js b/deploy-migrations/1_initial_migration.js deleted file mode 100644 index 1eb6f9daf..000000000 --- a/deploy-migrations/1_initial_migration.js +++ /dev/null @@ -1,5 +0,0 @@ -var Migrations = artifacts.require('./Migrations.sol') - -module.exports = function(deployer) { - deployer.deploy(Migrations) -} diff --git a/deploy-migrations/2_deploy_root_contracts.js b/deploy-migrations/2_deploy_root_contracts.js deleted file mode 100644 index 52f15d801..000000000 --- a/deploy-migrations/2_deploy_root_contracts.js +++ /dev/null @@ -1,245 +0,0 @@ -const bluebird = require('bluebird') - -const utils = require('./utils') - -const SafeMath = artifacts.require( - 'SafeMath' -) -const RLPReader = artifacts.require('solidity-rlp/contracts/RLPReader.sol') - -const BytesLib = artifacts.require('BytesLib') -const Common = artifacts.require('Common') -const ECVerify = artifacts.require('ECVerify') -const Merkle = artifacts.require('Merkle') -const MerklePatriciaProof = artifacts.require('MerklePatriciaProof') -const PriorityQueue = artifacts.require('PriorityQueue') -const RLPEncode = artifacts.require('RLPEncode') -const TransferWithSigUtils = artifacts.require('TransferWithSigUtils') - -const Registry = artifacts.require('Registry') -const RootChain = artifacts.require('RootChain') -const Governance = artifacts.require('Governance') -const GovernanceProxy = artifacts.require('GovernanceProxy') -const RootChainProxy = artifacts.require('RootChainProxy') -const DepositManager = artifacts.require('DepositManager') -const DepositManagerProxy = artifacts.require('DepositManagerProxy') -const WithdrawManager = artifacts.require('WithdrawManager') -const WithdrawManagerProxy = artifacts.require('WithdrawManagerProxy') -const StateSender = artifacts.require('StateSender') -const StakeManager = artifacts.require('StakeManager') -const ValidatorShare = artifacts.require('ValidatorShare') -const SlashingManager = artifacts.require('SlashingManager') -const StakeManagerProxy = artifacts.require('StakeManagerProxy') -const StakingInfo = artifacts.require('StakingInfo') -const StakingNFT = artifacts.require('StakingNFT') -const ValidatorShareFactory = artifacts.require('ValidatorShareFactory') -const ERC20Predicate = artifacts.require('ERC20Predicate') -const ERC721Predicate = artifacts.require('ERC721Predicate') -const MintableERC721Predicate = artifacts.require('MintableERC721Predicate') -const MarketplacePredicate = artifacts.require('MarketplacePredicate') -const TransferWithSigPredicate = artifacts.require('TransferWithSigPredicate') -const ExitNFT = artifacts.require('ExitNFT') - -// tokens -const MaticWeth = artifacts.require('MaticWETH') -const TestToken = artifacts.require('TestToken') -const RootERC721 = artifacts.require('RootERC721') - -const libDeps = [ - { - lib: BytesLib, - contracts: [ - WithdrawManager, - ERC20Predicate, - ERC721Predicate, - MintableERC721Predicate - ] - }, - { - lib: Common, - contracts: [ - WithdrawManager, - ERC20Predicate, - ERC721Predicate, - MintableERC721Predicate, - MarketplacePredicate, - TransferWithSigPredicate - ] - }, - { - lib: ECVerify, - contracts: [ - StakeManager, - SlashingManager, - MarketplacePredicate, - TransferWithSigPredicate - ] - }, - { - lib: Merkle, - contracts: [WithdrawManager, ERC20Predicate, ERC721Predicate, StakeManager, MintableERC721Predicate] - }, - { - lib: MerklePatriciaProof, - contracts: [WithdrawManager, ERC20Predicate, ERC721Predicate, MintableERC721Predicate] - }, - { - lib: PriorityQueue, - contracts: [WithdrawManager] - }, - { - lib: RLPEncode, - contracts: [ - WithdrawManager, - ERC20Predicate, - ERC721Predicate, - MarketplacePredicate - ] - }, - { - lib: RLPReader, - contracts: [ - RootChain, - StakeManager, - ERC20Predicate, - ERC721Predicate, - MintableERC721Predicate, - MarketplacePredicate, - TransferWithSigPredicate - ] - }, - { - lib: SafeMath, - contracts: [ - RootChain, - ERC20Predicate, - StakeManager, - SlashingManager, - StateSender, - StakingInfo - ] - }, - { - lib: TransferWithSigUtils, - contracts: [MarketplacePredicate, TransferWithSigPredicate] - } -] - -module.exports = async function(deployer) { - if (!process.env.HEIMDALL_ID) { - throw new Error('Please export HEIMDALL_ID environment variable') - } - - deployer.then(async () => { - console.log('linking libs...') - await bluebird.map(libDeps, async e => { - await deployer.deploy(e.lib) - deployer.link(e.lib, e.contracts) - }) - - console.log('deploying contracts...') - await deployer.deploy(Governance) - await deployer.deploy(GovernanceProxy, Governance.address) - await deployer.deploy(Registry, GovernanceProxy.address) - - await deployer.deploy(RootChain) - await deployer.deploy(RootChainProxy, RootChain.address, Registry.address, process.env.HEIMDALL_ID) - - await deployer.deploy(ValidatorShareFactory) - await deployer.deploy(StakingInfo, Registry.address) - await deployer.deploy(StakingNFT, 'Matic Validator', 'MV') - - console.log('deploying tokens...') - await deployer.deploy(MaticWeth) - await deployer.deploy(TestToken, 'MATIC', 'MATIC') - const testToken = await TestToken.new('Test ERC20', 'TST20') - await deployer.deploy(RootERC721, 'Test ERC721', 'TST721') - - const stakeManager = await deployer.deploy(StakeManager) - const proxy = await deployer.deploy(StakeManagerProxy, '0x0000000000000000000000000000000000000000') - await proxy.updateAndCall(StakeManager.address, stakeManager.contract.methods.initialize(Registry.address, RootChainProxy.address, TestToken.address, StakingNFT.address, StakingInfo.address, ValidatorShareFactory.address, GovernanceProxy.address).encodeABI()) - - await deployer.deploy(SlashingManager, Registry.address, StakingInfo.address, process.env.HEIMDALL_ID) - await deployer.deploy(ValidatorShare, Registry.address, 0/** dummy id */, StakingInfo.address, StakeManagerProxy.address) - await deployer.deploy(StateSender) - - await deployer.deploy(DepositManager) - await deployer.deploy( - DepositManagerProxy, - DepositManager.address, - Registry.address, - RootChainProxy.address, - GovernanceProxy.address - ) - - await deployer.deploy(WithdrawManager) - await deployer.deploy(ExitNFT, Registry.address) - await deployer.deploy( - WithdrawManagerProxy, - WithdrawManager.address, - Registry.address, - RootChainProxy.address, - ExitNFT.address - ) - - console.log('deploying predicates...') - await deployer.deploy( - ERC20Predicate, - WithdrawManagerProxy.address, - DepositManagerProxy.address, - Registry.address - ) - await deployer.deploy( - ERC721Predicate, - WithdrawManagerProxy.address, - DepositManagerProxy.address - ) - await deployer.deploy( - MarketplacePredicate, - RootChainProxy.address, - WithdrawManagerProxy.address, - Registry.address - ) - await deployer.deploy( - TransferWithSigPredicate, - RootChainProxy.address, - WithdrawManagerProxy.address, - Registry.address - ) - - console.log('writing contract addresses to file...') - const contractAddresses = { - root: { - Registry: Registry.address, - RootChain: RootChain.address, - Governance: Governance.address, - GovernanceProxy: GovernanceProxy.address, - RootChainProxy: RootChainProxy.address, - DepositManager: DepositManager.address, - DepositManagerProxy: DepositManagerProxy.address, - WithdrawManager: WithdrawManager.address, - WithdrawManagerProxy: WithdrawManagerProxy.address, - StakeManager: StakeManager.address, - StakeManagerProxy: StakeManagerProxy.address, - ValidatorShare: ValidatorShare.address, - SlashingManager: SlashingManager.address, - StakingInfo: StakingInfo.address, - ExitNFT: ExitNFT.address, - StateSender: StateSender.address, - predicates: { - ERC20Predicate: ERC20Predicate.address, - ERC721Predicate: ERC721Predicate.address, - MarketplacePredicate: MarketplacePredicate.address, - TransferWithSigPredicate: TransferWithSigPredicate.address - }, - tokens: { - MaticWeth: MaticWeth.address, - MaticToken: TestToken.address, - TestToken: testToken.address, - RootERC721: RootERC721.address - } - } - } - utils.writeContractAddresses(contractAddresses) - }) -} diff --git a/deploy-migrations/3_initialize_state.js b/deploy-migrations/3_initialize_state.js deleted file mode 100644 index b578e0b90..000000000 --- a/deploy-migrations/3_initialize_state.js +++ /dev/null @@ -1,120 +0,0 @@ -const ethUtils = require('ethereumjs-util') -const bluebird = require('bluebird') - -const Registry = artifacts.require('Registry') -const DepositManagerProxy = artifacts.require('DepositManagerProxy') -const StateSender = artifacts.require('StateSender') -const WithdrawManagerProxy = artifacts.require('WithdrawManagerProxy') -const StakeManagerProxy = artifacts.require('StakeManagerProxy') -const SlashingManager = artifacts.require('SlashingManager') -const ERC20Predicate = artifacts.require('ERC20Predicate') -const ERC721Predicate = artifacts.require('ERC721Predicate') -const MarketplacePredicate = artifacts.require('MarketplacePredicate') -const TransferWithSigPredicate = artifacts.require('TransferWithSigPredicate') -const MaticWeth = artifacts.require('MaticWETH') -const Governance = artifacts.require('Governance') -const EventsHubProxy = artifacts.require('EventsHubProxy') - -async function updateContractMap(governance, registry, nameHash, value) { - return governance.update( - registry.address, - registry.contract.methods.updateContractMap(nameHash, value).encodeABI() - ) -} - -module.exports = async function(deployer) { - deployer.then(async() => { - await bluebird - .all([ - Governance.deployed(), - Registry.deployed(), - DepositManagerProxy.deployed(), - StateSender.deployed(), - WithdrawManagerProxy.deployed(), - StakeManagerProxy.deployed(), - SlashingManager.deployed(), - ERC20Predicate.deployed(), - ERC721Predicate.deployed(), - MarketplacePredicate.deployed(), - TransferWithSigPredicate.deployed(), - EventsHubProxy.deployed() - ]) - .spread(async function( - governance, - registry, - depositManagerProxy, - stateSender, - withdrawManagerProxy, - stakeManagerProxy, - slashingManager, - ERC20Predicate, - ERC721Predicate, - MarketplacePredicate, - TransferWithSigPredicate, - EventsHubProxy - ) { - await updateContractMap( - governance, - registry, - ethUtils.keccak256('depositManager'), - depositManagerProxy.address - ) - await updateContractMap( - governance, - registry, - ethUtils.keccak256('withdrawManager'), - withdrawManagerProxy.address - ) - await updateContractMap( - governance, - registry, - ethUtils.keccak256('stakeManager'), - stakeManagerProxy.address - ) - await updateContractMap( - governance, - registry, - ethUtils.keccak256('slashingManager'), - slashingManager.address - ) - await updateContractMap( - governance, - registry, - ethUtils.keccak256('stateSender'), - stateSender.address - ) - await updateContractMap( - governance, - registry, - ethUtils.keccak256('wethToken'), - MaticWeth.address - ) - await updateContractMap( - governance, - registry, - ethUtils.keccak256('eventsHub'), - EventsHubProxy.address - ) - - // whitelist predicates - await governance.update( - registry.address, - registry.contract.methods.addErc20Predicate(ERC20Predicate.address).encodeABI() - ) - - await governance.update( - registry.address, - registry.contract.methods.addErc721Predicate(ERC721Predicate.address).encodeABI() - ) - - await governance.update( - registry.address, - registry.contract.methods.addPredicate(MarketplacePredicate.address, 3).encodeABI() - ) - await governance.update( - registry.address, - registry.contract.methods.addPredicate(TransferWithSigPredicate.address, 3).encodeABI() - ) - }) - }) -} diff --git a/deploy-migrations/4_deploy_child_contracts.js b/deploy-migrations/4_deploy_child_contracts.js deleted file mode 100644 index 7194ea664..000000000 --- a/deploy-migrations/4_deploy_child_contracts.js +++ /dev/null @@ -1,64 +0,0 @@ -const utils = require('./utils') - -const SafeMath = artifacts.require( - 'openzeppelin-solidity/contracts/math/SafeMath.sol' -) -const ChildChain = artifacts.require('ChildChain') -const MRC20 = artifacts.require('MRC20') - -module.exports = async function(deployer, network, accounts) { - deployer.then(async() => { - await deployer.deploy(SafeMath) - await deployer.link(SafeMath, [ChildChain]) - await deployer.deploy(ChildChain) - - const childChain = await ChildChain.deployed() - const contractAddresses = utils.getContractAddresses() - - let MaticWeth = await childChain.addToken( - accounts[0], - contractAddresses.root.tokens.MaticWeth, - 'ETH on Matic', - 'ETH', - 18, - false // _isERC721 - ) - - let TestToken = await childChain.addToken( - accounts[0], - contractAddresses.root.tokens.TestToken, - 'Test Token', - 'TST', - 18, - false // _isERC721 - ) - - let RootERC721 = await childChain.addToken( - accounts[0], - contractAddresses.root.tokens.RootERC721, - 'Test ERC721', - 'TST721', - 0, - true // _isERC721 - ) - - const maticToken = await MRC20.at('0x0000000000000000000000000000000000001010') - const maticOwner = await maticToken.owner() - if (maticOwner === '0x0000000000000000000000000000000000000000') { - // matic contract at 0x1010 can only be initialized once, after the bor image starts to run - await maticToken.initialize(ChildChain.address, contractAddresses.root.tokens.MaticToken) - } - await childChain.mapToken(contractAddresses.root.tokens.MaticToken, '0x0000000000000000000000000000000000001010', false) - - contractAddresses.child = { - ChildChain: ChildChain.address, - tokens: { - MaticWeth: MaticWeth.logs.find(log => log.event === 'NewToken').args.token, - MaticToken: '0x0000000000000000000000000000000000001010', - TestToken: TestToken.logs.find(log => log.event === 'NewToken').args.token, - RootERC721: RootERC721.logs.find(log => log.event === 'NewToken').args.token - } - } - utils.writeContractAddresses(contractAddresses) - }) -} diff --git a/deploy-migrations/5_sync_child_state_to_root.js b/deploy-migrations/5_sync_child_state_to_root.js deleted file mode 100644 index 38dcf657e..000000000 --- a/deploy-migrations/5_sync_child_state_to_root.js +++ /dev/null @@ -1,73 +0,0 @@ -const ethUtils = require('ethereumjs-util') -const utils = require('./utils') - -const Registry = artifacts.require('Registry') -const Governance = artifacts.require('Governance') -const StateSender = artifacts.require('StateSender') -const DepositManager = artifacts.require('DepositManager') - -module.exports = async function(deployer, network, accounts) { - deployer.then(async() => { - const contractAddresses = utils.getContractAddresses() - const registry = await Registry.at(contractAddresses.root.Registry) - const governance = await Governance.at(contractAddresses.root.GovernanceProxy) - const tasks = [] - - tasks.push(governance.update( - contractAddresses.root.Registry, - registry.contract.methods.mapToken( - contractAddresses.root.tokens.MaticWeth, - contractAddresses.child.tokens.MaticWeth, - false /* isERC721 */ - ).encodeABI() - )) - await delay(5) - - tasks.push(governance.update( - contractAddresses.root.Registry, - registry.contract.methods.mapToken( - contractAddresses.root.tokens.MaticToken, - contractAddresses.child.tokens.MaticToken, - false /* isERC721 */ - ).encodeABI() - )) - await delay(5) - - tasks.push(governance.update( - contractAddresses.root.Registry, - registry.contract.methods.mapToken( - contractAddresses.root.tokens.TestToken, - contractAddresses.child.tokens.TestToken, - false /* isERC721 */ - ).encodeABI() - )) - await delay(5) - - tasks.push(governance.update( - contractAddresses.root.Registry, - registry.contract.methods.mapToken( - contractAddresses.root.tokens.RootERC721, - contractAddresses.child.tokens.RootERC721, - true /* isERC721 */ - ).encodeABI() - )) - await delay(5) - - tasks.push(governance.update( - contractAddresses.root.Registry, - registry.contract.methods.updateContractMap( - ethUtils.keccak256('childChain'), - contractAddresses.child.ChildChain - ).encodeABI() - )) - await Promise.all(tasks) - - await (await StateSender.at(contractAddresses.root.StateSender)).register(contractAddresses.root.DepositManagerProxy, contractAddresses.child.ChildChain) - let depositManager = await DepositManager.at(contractAddresses.root.DepositManagerProxy) - await depositManager.updateChildChainAndStateSender() - }) -} - -function delay(s) { - return new Promise(resolve => setTimeout(resolve, s * 1000)); -} diff --git a/deploy-migrations/README.md b/deploy-migrations/README.md deleted file mode 100644 index 4dd155f4d..000000000 --- a/deploy-migrations/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# Deploy Contracts - -### :one: We have some network configrations available. - -| Network Name | URL | Network ID | -| ------------ | :-------------------------------------: | ---------: | -| ropsten | https://ropsten.infura.io/v3/${API_KEY} | 3 | -| mainnet | https://mainnet.infura.io/v3/${API_KEY} | 1 | -| development | http://localhost:9545 | \* | -| bor | http://localhost:8545 | \* | - -Feel free to add your own. Update the chain url in `networks.matic` key in [truffle-config.js](../truffle-config.js). - -### :two: Export variables - -``` -// (Optional) Only if you are using ethereum testnets -export API_KEY= - -// For `developement` networks you can use the mnemonic present in package.json -export MNEMONIC= - -export HEIMDALL_ID=<> -e.g. export HEIMDALL_ID="heimdall-P5rXwg" -``` - -### :three: Choose Bor Chain Id - -``` -npm run template:process -- --bor-chain-id -for instance, npm run template:process -- --bor-chain-id 80001 -``` - -### :four: Compile contracts - -``` -npm run truffle:compile -``` - -### :five: Deploy contracts - -We need to deploy our set of contracts on 2 chains: - -- Base Chain: Ideally a higher security EVM chain which can be used for dispute resolution. For testing ganache or any other EVM chain should work. -- Child Chain: EVM compatible chain to work as our sidechain. For testing note that using `ganache` for child-chain is not recommended, instead invoking `npm run bor:simulate` would be better. - -``` - -// Root contracts are deployed on base chain -npm run truffle:migrate -- --reset --to 4 --network - -// Contracts like ChildERC20Token are deployed on child chain aka BOR chain -// NOTE: You need to deploy or simulate BOR before running the below command -npm run truffle:migrate -- --reset -f 5 --to 5 --network - - -// Contracts deployed on BOR are mapped to the registry contract deployed on-chain -npm run truffle:migrate -- -f 6 --to 6 --network -``` - -Post successfull deployment all contract addresses will be written to a `contractAddresses.json` file. - -> Check account that you are deploying from has ether for the network you are deploying on. - -### :six: Stake to become a validator - -``` -// (Optional) Export mnemonic or the private key (without the 0x prefix) -// This account needs to have test token -export MNEMONIC=<> - -// (Optional) Infura PROJECT ID, if required -export API_KEY= - -npm run truffle exec scripts/stake.js -- --network <# tokens to stake> -``` diff --git a/deploy-migrations/utils.js b/deploy-migrations/utils.js deleted file mode 100644 index 6457f02c7..000000000 --- a/deploy-migrations/utils.js +++ /dev/null @@ -1,12 +0,0 @@ -const fs = require('fs') - -export function getContractAddresses() { - return JSON.parse(fs.readFileSync(`${process.cwd()}/contractAddresses.json`).toString()) -} - -export function writeContractAddresses(contractAddresses) { - fs.writeFileSync( - `${process.cwd()}/contractAddresses.json`, - JSON.stringify(contractAddresses, null, 2) // Indent 2 spaces - ) -} diff --git a/hardhat.config.ts b/hardhat.config.ts new file mode 100644 index 000000000..c66ee6b51 --- /dev/null +++ b/hardhat.config.ts @@ -0,0 +1,93 @@ +import { HardhatUserConfig } from 'hardhat/types' + +import '@nomiclabs/hardhat-truffle5' +import 'hardhat-contract-sizer' +import '@typechain/hardhat' +import '@nomiclabs/hardhat-etherscan' + +import './lib/type-extensions' +import './tasks' + +const MNEMONIC = process.env.MNEMONIC || 'clock radar mass judge dismiss just intact mind resemble fringe diary casino' +const API_KEY = process.env.API_KEY + +const config: HardhatUserConfig = { + defaultNetwork: 'hardhat', + networks: { + hardhat: { + accounts: { mnemonic: MNEMONIC }, + allowUnlimitedContractSize: true + }, + root: { + url: 'http://127.0.0.1:9545', + accounts: { mnemonic: MNEMONIC }, + allowUnlimitedContractSize: true + }, + child: { + url: 'http://localhost:8545', + gas: 20e6, + chainId: 15001, + allowUnlimitedContractSize: true + }, + mainnet: { + accounts: { mnemonic: MNEMONIC }, + url: `https://mainnet.infura.io/v3/${API_KEY}`, + chainId: 1 + }, + goerli: { + accounts: { mnemonic: MNEMONIC }, + url: `https://goerli.infura.io/v3/${API_KEY}`, + chainId: 5 + } + }, + mocha: { + timeout: 500000 + }, + solidity: { + version: '0.5.17', + settings: { + optimizer: { + enabled: true, + runs: 200 + }, + evmVersion: 'constantinople' + } + }, + contractSizer: { + alphaSort: true, + disambiguatePaths: false, + runOnCompile: true, + strict: false + }, + typechain: { + outDir: 'typechain', + target: 'truffle-v5', + alwaysGenerateOverloads: false + }, + etherscan: { + apiKey: process.env.ETHERSCAN_KEY + }, + verify: { + contracts: [ + 'StakeManagerProxy', + 'StakeManager', + 'StakeManagerExtension', + 'RootChainProxy', + 'RootChain', + 'WithdrawManagerProxy', + 'WithdrawManager', + 'EventsHubProxy', + 'EventsHub', + 'ValidatorShareFactory', + 'ExitNFT', + 'DepositManagerProxy', + 'DepositManager' + ] + }, + paths: { + sourceTemplates: 'contracts', + sources: 'contracts-out' + } +} + +export default config diff --git a/lib/bytecode.ts b/lib/bytecode.ts new file mode 100644 index 000000000..ebafdf07b --- /dev/null +++ b/lib/bytecode.ts @@ -0,0 +1,143 @@ +// tslint:disable: max-classes-per-file +/* + * The Solidity compiler appends a Swarm Hash of compilation metadata to the end + * of bytecode. We find this hash based on the specification here: + * https://solidity.readthedocs.io/en/develop/metadata.html#encoding-of-the-metadata-hash-in-the-bytecode + */ + +const NULL_ADDRESS = '0x0' + +function trimLeading0x(addr) { + return addr.substr(2) +} + +const CONTRACT_METADATA_REGEXPS = [ + // 0.5.8 + 'a165627a7a72305820.{64}0029', + // 0.5.13 + 'a265627a7a72315820.{64}64736f6c6343.{6}0032', +] + +const GENERAL_METADATA_REGEXP = new RegExp( + `^(.*)(${CONTRACT_METADATA_REGEXPS.map((r) => '(' + r + ')').join('|')})$`, + 'i' // Use i flag to make search case insensitive. +) + +export const stripMetadata = (bytecode: string): string => { + if (bytecode === '0x') { + return '0x' + } + + const match = bytecode.match(GENERAL_METADATA_REGEXP) + if (match === null) { + throw new Error( + 'Only support stripping metadata from bytecodes generated by solc up to v0.5.13 with no experimental features.' + ) + } + return match[1] +} + +// Maps library names to their onchain addresses (formatted without "0x" prefix). +export interface LibraryLinks { + [name: string]: string +} + +/* + * Unresolved libraries appear as "__LibraryName___..." in bytecode output by + * solc. The length of the entire string is 40 characters (accounting for the 20 + * bytes of the address that should be substituted in). + */ +const padForLink = (name: string): string => { + return `__${name}`.padEnd(40, '_') +} + +export const linkLibraries = (bytecode: string, libraryLinks: LibraryLinks): string => { + Object.keys(libraryLinks).forEach((libraryName) => { + const linkString = padForLink(libraryName) + // Use g flag to iterate through for all occurences. + bytecode = bytecode.replace(RegExp(linkString, 'g'), libraryLinks[libraryName]) + }) + + return bytecode +} + +const ADDRESS_LENGTH = 40 +const PUSH20_OPCODE = '73' +// To check that a library isn't being called directly, the Solidity +// compiler starts a library's bytecode with a comparison of the current +// address with the address the library was deployed to (it has to differ +// to ensure the library is being called with CALLCODE or DELEGATECALL +// instead of a regular CALL). +// The address is only known at contract construction time, so +// the compiler's output contains a placeholder 0-address, while the onchain +// bytecode has the correct address inserted. +// Reference: https://solidity.readthedocs.io/en/v0.5.12/contracts.html#call-protection-for-libraries +export const verifyAndStripLibraryPrefix = (bytecode: string, address = NULL_ADDRESS) => { + if (bytecode.slice(2, 4) !== PUSH20_OPCODE) { + throw new Error(`Library bytecode doesn't start with address load`) + } else if (bytecode.slice(4, 4 + ADDRESS_LENGTH) !== trimLeading0x(address).toLowerCase()) { + throw new Error(`Library bytecode loads unexpected address at start`) + } + + return bytecode.slice(4 + ADDRESS_LENGTH, bytecode.length) +} + +export class LibraryPositions { + static libraryLinkRegExpString = '__([A-Z][A-Za-z0-9]*)_{2,}' + + positions: { [library: string]: number[] } + + /* + * Creates a LibraryPositions object, which, for each yet to be linked library, + * contains the bytecode offsets of where the library address should be + * inserted. + */ + constructor(bytecode: string) { + this.positions = {} + // Use g flag to iterate through for all occurences. + const libraryLinkRegExp = new RegExp(LibraryPositions.libraryLinkRegExpString, 'g') + let match = libraryLinkRegExp.exec(bytecode) + while (match != null) { + // The first capture group is the library's name + this.addPosition(match[1], match.index) + match = libraryLinkRegExp.exec(bytecode) + } + } + + private addPosition(library: string, position: number) { + if (!this.positions[library]) { + this.positions[library] = [] + } + + this.positions[library].push(position) + } +} + +export class LibraryAddresses { + addresses: { [library: string]: string } + + constructor() { + this.addresses = {} + } + + collect = (bytecode: string, libraryPositions: LibraryPositions) => + Object.keys(libraryPositions.positions).forEach((library) => + libraryPositions.positions[library].forEach((position) => { + if (!this.addAddress(library, bytecode.slice(position, position + ADDRESS_LENGTH))) { + throw new Error(`Mismatched addresses for ${library} at ${position}`) + } + }) + ) + + /* + * Tries to add a library name -> address mapping. If the library has already + * had an address added, checks that the new address matches the old one. + */ + private addAddress(library: string, address: string): boolean { + if (!this.addresses[library]) { + this.addresses[library] = address + } + + return this.addresses[library] === address + } +} diff --git a/lib/contracts.ts b/lib/contracts.ts new file mode 100644 index 000000000..e69de29bb diff --git a/lib/errors.ts b/lib/errors.ts new file mode 100644 index 000000000..55def4a9e --- /dev/null +++ b/lib/errors.ts @@ -0,0 +1,29 @@ +export class StorageError { + public warning: boolean + public message: string + + constructor(isWarning: boolean, messsage: string) { + this.warning = isWarning + this.message = messsage + } +} + +export class VerificationFailed extends Error { + contractName: string + + constructor(contractName: string) { + super(`${contractName} does not match compiled bytecode`) + + this.contractName = contractName + } +} + +export class ProxyImplementationHasChanged extends Error { + contractName: string + + constructor(contractName: string) { + super(`${contractName} doesn't match deployed implementation`) + + this.contractName = contractName + } +} diff --git a/lib/index.ts b/lib/index.ts new file mode 100644 index 000000000..0f44c0552 --- /dev/null +++ b/lib/index.ts @@ -0,0 +1,8 @@ +export * from './registry' +export * from './bytecode' +export * from './utils' +export * from './types' +export * from './registry' +export * from './storage' +export * from './errors' +export * from './migration' diff --git a/lib/migration.ts b/lib/migration.ts new file mode 100644 index 000000000..1aef6fa1a --- /dev/null +++ b/lib/migration.ts @@ -0,0 +1,66 @@ +import chalk from 'chalk' +import { ReleaseRegistry, cc, GovernanceRepositoryLink } from './index' + +import { RegistryContract } from '../typechain' + +import '@nomiclabs/hardhat-truffle5' +import { Artifacts } from 'hardhat/types' +import { extractParameters } from './utils' +import { ethers } from 'ethers' + +export async function printGovernanceUpdateCommand(target: string, contract: string, data: string) { + cc.log(`Use this command in our ${GovernanceRepositoryLink} repository to generate multisig transaction data:`) + + const encodedCall = `npx hardhat encode-update-governance-data --target ${target} --contract ${contract} --data ${data}` + + await cc.intendGroup(async() => cc.log(chalk.bgBlack(chalk.yellowBright(encodedCall)))) +} + +export async function printProxyUpdateCommand(proxy: string, implementation: string) { + cc.log(`Use this command in our ${GovernanceRepositoryLink} repository to generate transaction data:`) + + const encodedCall = `npx hardhat encode-update-proxy-data --proxy ${proxy} --implementation ${implementation}` + + await cc.intendGroup(async() => cc.log(chalk.bgBlack(chalk.yellowBright(encodedCall)))) +} + +export async function deployProxyImplementation(artifacts: Artifacts, contractName: string, network: string, from: string) { + const artifact = artifacts.require(contractName) as any + const instance = await artifact.new({ from }) + const deployedAddress = instance.address + + const registry = new ReleaseRegistry(network) + await registry.load() + + cc.logLn() + await cc.intendGroup(async() => { + cc.logLn(`Deployed ${contractName} at ${deployedAddress}`) + + printProxyUpdateCommand(registry.getAddress(`${contractName}Proxy`), registry.getAddress(contractName)) + cc.logLn() + }, 'Deployment result:') + + return { address: deployedAddress } +} + +export async function deployPredicate(artifacts: Artifacts, network: string, from: string, params: string) { + const [contractName, ...ctorArgs] = extractParameters(params) + const artifact = artifacts.require(contractName) as any + const instance = await artifact.new(ctorArgs, { from }) + const deployedAddress = instance.address + + const registry = new ReleaseRegistry(network) + await registry.load() + + cc.logLn() + await cc.intendGroup(async() => { + cc.logLn(`Deployed ${contractName} at ${deployedAddress}`) + + const registryContract = await (artifacts.require('Registry') as RegistryContract).at(registry.getAddress('Registry')) + const calldata = registryContract.contract.methods.updateContractMap(ethers.utils.keccak256(contractName), deployedAddress).encodeABI() + printGovernanceUpdateCommand(registry.getAddress('GovernanceProxy'), registry.getAddress('Registry'), calldata) + cc.logLn() + }, 'Deployment result:') + + return { address: deployedAddress, args: ctorArgs } +} diff --git a/lib/registry.ts b/lib/registry.ts new file mode 100644 index 000000000..10c1f5666 --- /dev/null +++ b/lib/registry.ts @@ -0,0 +1,139 @@ +import { readFileSync, readdirSync, statSync, writeFileSync, existsSync, mkdirSync } from 'fs' +import { Artifacts } from 'hardhat/types' +import { StorageSlot } from 'lib' +import { join, basename } from 'path' +import { getPkgJsonDir } from './utils' + +const RELEASE_DATA_DIR = 'release-data/versions' +const RELEASE_BASENAME = 'release.' +const NO_VERSION = -1 + +export class ReleaseRegistry { + private contracts: { [key: string]: any } + private version: number + private network: string + private sealed: boolean + + constructor(network: string, version: number = NO_VERSION) { + this.contracts = {} + this.network = network + this.version = version + this.sealed = false + } + + async createNewRelease() { + await this.load() + + if (this.sealed) { + this.sealed = false + this.version = (this.version || 0) + 1 + await this.save() + } + } + + async load() { + const folder = await this.getWorkingFolder() + let versionToOpen = this.version + + try { + if (this.version === NO_VERSION) { + versionToOpen = await this.getLatestVersion() + } + + if (versionToOpen === NO_VERSION) { + throw new Error('ReleaseRegistry::load Can\'t find suitable version number') + } + + const fileContent = readFileSync(join(folder, `${RELEASE_BASENAME}${versionToOpen}.json`)) + const releaseData = JSON.parse(fileContent.toString()) + this.sealed = releaseData.sealed + for (const name in releaseData.contracts) { + this.contracts[name] = releaseData.contracts[name] + } + + this.version = versionToOpen || 0 + } catch (exc) { + console.error(`ReleaseRegistry::load can't open release data at ${folder} with ${exc}`) + throw exc + } + + return versionToOpen + } + + getStorage(contractName: string): StorageSlot[] { + return this.contracts[contractName].storage + } + + getAddress(contractName: string) { + return this.contracts[contractName].address + } + + replaceStorageLayout(contractName: string, layout: StorageSlot[]) { + this.contracts[contractName].storage = layout + } + + replaceAddress(contractName: string, address: string) { + this.contracts[contractName].address = address + } + + async save(seal: boolean = false) { + if (seal) { + this.sealed = seal + } else if (this.sealed) { + throw new Error('ReleaseRegistry::save can\'t override sealed release') + } + + const data = { + date: Math.floor(new Date().getTime()/1000) + new Date().getTimezoneOffset() * 60, + contracts: this.contracts, + sealed: this.sealed + } + + const folder = await this.getWorkingFolder() + + if (!existsSync(folder)) { + mkdirSync(folder, { recursive: true }) + } + + writeFileSync(join(folder, `${RELEASE_BASENAME}${this.version}.json`), JSON.stringify(data, null, 2)) + } + + private async getLatestVersion() { + const folder = await this.getWorkingFolder() + const files = readdirSync(folder) + let latestVersion = NO_VERSION + // get latest version + for (const file of files) { + if (statSync(join(folder, file)).isDirectory()) { + continue + } + + const currentVersion = +file.replace(RELEASE_BASENAME, '').replace('.json', '') + if (currentVersion > latestVersion) { + latestVersion = currentVersion + } + } + + return latestVersion + } + + private async getWorkingFolder() { + return join( + await getPkgJsonDir(), + RELEASE_DATA_DIR, + this.network + ) + } +} + +export async function contractsPaths(artifacts: Artifacts) { + const paths = await artifacts.getArtifactPaths() + const contractIndex = {} + for (const fullPath of paths) { + // clean up absolute paths + // regex will not work if contract is from the node_modules + contractIndex[basename(fullPath, '.json')] = fullPath.replace(/(.*artifacts\/)/, '').replace(/(\/\w+\.json$)/, '') + } + + return contractIndex +} diff --git a/lib/storage.ts b/lib/storage.ts new file mode 100644 index 000000000..dae97fe9d --- /dev/null +++ b/lib/storage.ts @@ -0,0 +1,66 @@ + +import { StorageError } from '.' +import { ReleaseRegistry } from './registry' +import { StorageSlot, StorageVerificationReport } from './types' + +const SOURCE_FILENAME = 'source.sol' + +function printSlot(original: StorageSlot, updated: StorageSlot) { + return ` Original: ${original.type} ${original.label} Updated: ${updated.type} ${updated.label}` +} + +export async function verifyStorageLayout(registry: ReleaseRegistry, contractName: string, sourceCode: string, compiler: any) { + const currentStorage = registry.getStorage(contractName) + const report: StorageVerificationReport = { + errors: [] + } + if (!currentStorage || currentStorage.length === 0) { + return report + } + + const storageFromSource = await getStorageLayout(contractName, sourceCode, compiler) + // check for label and type changes + for (let i = 0; i < currentStorage.length; ++i) { + const original = currentStorage[i] + const updated = storageFromSource[i] + + if (original.type !== updated.type) { + report.errors.push(new StorageError(false, `Collision with currently deployed contract! ${printSlot(original, updated)}`)) + } + + if (original.label !== updated.label) { + report.errors.push(new StorageError(true, `Slot has been renamed. ${printSlot(original, updated)}`)) + } + } + + return report +} + +export async function getStorageLayout(contractName: string, sourceCode: string, compiler: any): Promise { + const input = { + language: 'Solidity', + sources: { + [SOURCE_FILENAME]: { + content: sourceCode + } + }, + settings: { + outputSelection: { + '*': { + '*': ['*'] + } + } + } + } + + const output = JSON.parse( + compiler.compile(JSON.stringify(input)) + ) + + return output.contracts[SOURCE_FILENAME][contractName].storageLayout.storage.map(x => { + return { + type: x.type.match(/^t_\w+((\([\w,]*\))|(\([\w,]+\([\w,]+\))|)/)[0], // extract type without id, it looks like it might change for some hidden reason on a different machine + label: x.label + } + }) as StorageSlot[] +} diff --git a/lib/type-extensions.ts b/lib/type-extensions.ts new file mode 100644 index 000000000..243469a33 --- /dev/null +++ b/lib/type-extensions.ts @@ -0,0 +1,48 @@ +// If your plugin extends types from another plugin, you should import the plugin here. + +// To extend one of Hardhat's types, you need to import the module where it has been defined, and redeclare it. +import 'hardhat/types/config' +import { extendConfig } from 'hardhat/config' +import { HardhatConfig, HardhatUserConfig } from 'hardhat/types' + +declare module 'hardhat/types/config' { + // by the users. Things are normally optional here. + export interface HardhatUserConfig { + verify?: { + contracts: string[]; + } + } + + export interface HardhatConfig { + verify: { + contracts: string[]; + } + } + + export interface ProjectPathsUserConfig { + sourceTemplates: string + } + + export interface ProjectPathsConfig { + sourceTemplates: string + } +} + +extendConfig( + (config: HardhatConfig, userConfig: Readonly) => { + let contracts = userConfig.verify?.contracts + + if (contracts === undefined) { + contracts = [] + } + + if (!config.verify) { + config.verify = { + contracts: [] + } + } + + config.verify.contracts = contracts + config.paths.sourceTemplates = userConfig.paths?.sourceTemplates! + } +) diff --git a/lib/types.ts b/lib/types.ts new file mode 100644 index 000000000..070c5cafa --- /dev/null +++ b/lib/types.ts @@ -0,0 +1,20 @@ +import { Artifacts } from 'hardhat/types' +import { StorageError } from 'lib' + +export interface StorageSlot { + type: string + label: string + astId: number + slot: string +} + +export interface StorageVerificationReport { + errors: StorageError[] +} + +export interface DeployResult { + address: string + args: string[] +} + +export type DeployFunction = (artifacts: Artifacts, network: string, from: string|null, params: string) => Promise diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 000000000..46da545cf --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,59 @@ +import solc from 'solc' + +const SolidityBinaries = { + '0.5.17': 'v0.5.17+commit.d19bba13' +} + +export async function getPkgJsonDir() { + const { dirname } = require('path') + const { constants, promises: { access } } = require('fs') + + for (const path of module.paths) { + try { + const prospectivePkgJsonDir = dirname(path) + await access(path, constants.F_OK) + return prospectivePkgJsonDir + } catch (e) {} + } +} + +export function hasValue(value: any) { + return value !== undefined && value !== null +} + +export function loadSolcBinary(solidityVerstion: string) { + return new Promise((resolve, reject) => { + solc.loadRemoteVersion(SolidityBinaries[solidityVerstion], function(err: any, snapshot: any) { + if (err) { + reject(err) + } else { + resolve(snapshot) + } + }) + }) +} + +export const cc = { + async intendGroup(f: () => Promise, ...labels: string[]) { + console.group(...labels) + try { + await f() + } finally { + console.groupEnd() + } + }, + logLn(str: string = '') { + console.log(`${str}\n`) + }, + log: console.log +} + +export const GovernanceRepositoryLink = 'Governance Repository (https://github.com/maticnetwork/governance)' +export const ZeroAddress = '0x0000000000000000000000000000000000000000' + +export function extractParameters(params: string) { + if (!params) { + return [] + } + return params.split(',').map(x => x.trim()) +} diff --git a/migrations/1_initial_migration.js b/migrations/1_initial_migration.js deleted file mode 100644 index 1eb6f9daf..000000000 --- a/migrations/1_initial_migration.js +++ /dev/null @@ -1,5 +0,0 @@ -var Migrations = artifacts.require('./Migrations.sol') - -module.exports = function(deployer) { - deployer.deploy(Migrations) -} diff --git a/migrations/2_deploy_root_contracts.js b/migrations/2_deploy_root_contracts.js deleted file mode 100644 index bad2271c7..000000000 --- a/migrations/2_deploy_root_contracts.js +++ /dev/null @@ -1,308 +0,0 @@ -// Deploy minimal number of contracts to link the libraries with the contracts -const utils = require('./utils') - -const bluebird = require('bluebird') - -const SafeMath = artifacts.require( - 'openzeppelin-solidity/contracts/math/SafeMath.sol' -) - -const RLPReader = artifacts.require('solidity-rlp/contracts/RLPReader.sol') - -const BytesLib = artifacts.require('BytesLib') -const Common = artifacts.require('Common') -const ECVerify = artifacts.require('ECVerify') -const Merkle = artifacts.require('Merkle') -const MerklePatriciaProof = artifacts.require('MerklePatriciaProof') -const PriorityQueue = artifacts.require('PriorityQueue') -const RLPEncode = artifacts.require('RLPEncode') - -const Registry = artifacts.require('Registry') -const Governance = artifacts.require('Governance') -const GovernanceProxy = artifacts.require('GovernanceProxy') -const RootChain = artifacts.require('RootChain') -const RootChainProxy = artifacts.require('RootChainProxy') -const DepositManager = artifacts.require('DepositManager') -const DepositManagerProxy = artifacts.require('DepositManagerProxy') -const WithdrawManager = artifacts.require('WithdrawManager') -const WithdrawManagerProxy = artifacts.require('WithdrawManagerProxy') -const StateSender = artifacts.require('StateSender') -const StakeManager = artifacts.require('StakeManager') -const StakeManagerProxy = artifacts.require('StakeManagerProxy') -const SlashingManager = artifacts.require('SlashingManager') -const StakingInfo = artifacts.require('StakingInfo') -const StakingNFT = artifacts.require('StakingNFT') -const ValidatorShareFactory = artifacts.require('ValidatorShareFactory') -const ValidatorShare = artifacts.require('ValidatorShare') -const ERC20Predicate = artifacts.require('ERC20Predicate') -const ERC721Predicate = artifacts.require('ERC721Predicate') -const MintableERC721Predicate = artifacts.require('MintableERC721Predicate') -const Marketplace = artifacts.require('Marketplace') -const MarketplacePredicate = artifacts.require('MarketplacePredicate') -const MarketplacePredicateTest = artifacts.require('MarketplacePredicateTest') -const TransferWithSigPredicate = artifacts.require('TransferWithSigPredicate') -const TransferWithSigUtils = artifacts.require('TransferWithSigUtils') - -const StakeManagerTestable = artifacts.require('StakeManagerTestable') -const StakeManagerTest = artifacts.require('StakeManagerTest') - -const ExitNFT = artifacts.require('ExitNFT') -const MaticWeth = artifacts.require('MaticWETH') -const TestToken = artifacts.require('TestToken') -const RootERC721 = artifacts.require('RootERC721') - -const StakeManagerExtension = artifacts.require('StakeManagerExtension') -const EventsHub = artifacts.require('EventsHub') -const EventsHubProxy = artifacts.require('EventsHubProxy') - -const ZeroAddress = '0x0000000000000000000000000000000000000000'; - -const libDeps = [ - { - lib: BytesLib, - contracts: [WithdrawManager, ERC20Predicate, ERC721Predicate, MintableERC721Predicate] - }, - { - lib: Common, - contracts: [ - WithdrawManager, - ERC20Predicate, - ERC721Predicate, - MintableERC721Predicate, - MarketplacePredicate, - MarketplacePredicateTest, - TransferWithSigPredicate - ] - }, - { - lib: ECVerify, - contracts: [ - StakeManager, - SlashingManager, - StakeManagerTestable, - MarketplacePredicate, - MarketplacePredicateTest, - TransferWithSigPredicate - ] - }, - { - lib: Merkle, - contracts: [ - WithdrawManager, - ERC20Predicate, - ERC721Predicate, - MintableERC721Predicate, - StakeManager, - StakeManagerTestable, - StakeManagerTest - ] - }, - { - lib: MerklePatriciaProof, - contracts: [WithdrawManager, ERC20Predicate, ERC721Predicate, MintableERC721Predicate] - }, - { - lib: PriorityQueue, - contracts: [WithdrawManager] - }, - { - lib: RLPEncode, - contracts: [ - WithdrawManager, - ERC20Predicate, - ERC721Predicate, - MintableERC721Predicate, - MarketplacePredicate, - MarketplacePredicateTest - ] - }, - { - lib: RLPReader, - contracts: [ - RootChain, - StakeManager, - ERC20Predicate, - ERC721Predicate, - MintableERC721Predicate, - MarketplacePredicate, - MarketplacePredicateTest - ] - }, - { - lib: SafeMath, - contracts: [ - RootChain, - ERC20Predicate, - ERC721Predicate, - MintableERC721Predicate, - MarketplacePredicate, - MarketplacePredicateTest, - TransferWithSigPredicate, - StakeManager, - SlashingManager, - StakingInfo, - StateSender, - StakeManagerExtension - ] - }, - { - lib: SafeMath, - contracts: [RootChain, ERC20Predicate] - }, - { - lib: TransferWithSigUtils, - contracts: [ - TransferWithSigPredicate, - MarketplacePredicate, - MarketplacePredicateTest - ] - } -] - -module.exports = async function(deployer, network, accounts) { - if (!process.env.HEIMDALL_ID) { - console.log('HEIMDALL_ID is not set; defaulting to heimdall-P5rXwg') - process.env.HEIMDALL_ID = 'heimdall-P5rXwg' - } - - deployer.then(async() => { - await bluebird.map(libDeps, async e => { - await deployer.deploy(e.lib) - deployer.link(e.lib, e.contracts) - }) - - await deployer.deploy(Governance) - await deployer.deploy(GovernanceProxy, Governance.address) - await deployer.deploy(Registry, GovernanceProxy.address) - await deployer.deploy(ValidatorShareFactory) - await deployer.deploy(ValidatorShare) - const maticToken = await deployer.deploy(TestToken, 'MATIC', 'MATIC') - await deployer.deploy(TestToken, 'Test ERC20', 'TEST20') - await deployer.deploy(RootERC721, 'Test ERC721', 'TST721') - await deployer.deploy(StakingInfo, Registry.address) - await deployer.deploy(StakingNFT, 'Matic Validator', 'MV') - - await deployer.deploy(RootChain) - await deployer.deploy(RootChainProxy, RootChain.address, Registry.address, process.env.HEIMDALL_ID) - await deployer.deploy(StateSender) - await deployer.deploy(StakeManagerTestable) - await deployer.deploy(StakeManagerTest) - - await deployer.deploy(DepositManager) - await deployer.deploy( - DepositManagerProxy, - DepositManager.address, - Registry.address, - RootChainProxy.address, - GovernanceProxy.address - ) - - await deployer.deploy(ExitNFT, Registry.address) - await deployer.deploy(WithdrawManager) - await deployer.deploy( - WithdrawManagerProxy, - WithdrawManager.address, - Registry.address, - RootChainProxy.address, - ExitNFT.address - ) - - { - let eventsHubImpl = await deployer.deploy(EventsHub) - let proxy = await deployer.deploy(EventsHubProxy, ZeroAddress) - await proxy.updateAndCall(eventsHubImpl.address, eventsHubImpl.contract.methods.initialize( - Registry.address - ).encodeABI()) - } - - const stakeManager = await deployer.deploy(StakeManager) - const stakeMangerProxy = await deployer.deploy(StakeManagerProxy, ZeroAddress) - const auctionImpl = await deployer.deploy(StakeManagerExtension) - await stakeMangerProxy.updateAndCall( - StakeManager.address, - stakeManager.contract.methods.initialize( - Registry.address, - RootChainProxy.address, - maticToken.address, - StakingNFT.address, - StakingInfo.address, - ValidatorShareFactory.address, - GovernanceProxy.address, - accounts[0], - auctionImpl.address - ).encodeABI() - ) - - await deployer.deploy(SlashingManager, Registry.address, StakingInfo.address, process.env.HEIMDALL_ID) - let stakingNFT = await StakingNFT.deployed() - await stakingNFT.transferOwnership(StakeManagerProxy.address) - - await deployer.deploy(MaticWeth) - - await Promise.all([ - deployer.deploy( - ERC20Predicate, - WithdrawManagerProxy.address, - DepositManagerProxy.address, - Registry.address - ), - deployer.deploy( - ERC721Predicate, - WithdrawManagerProxy.address, - DepositManagerProxy.address - ), - deployer.deploy( - MintableERC721Predicate, - WithdrawManagerProxy.address, - DepositManagerProxy.address - ), - deployer.deploy(Marketplace), - deployer.deploy(MarketplacePredicateTest), - deployer.deploy( - MarketplacePredicate, - RootChain.address, - WithdrawManagerProxy.address, - Registry.address - ), - deployer.deploy( - TransferWithSigPredicate, - RootChain.address, - WithdrawManagerProxy.address, - Registry.address - ) - ]) - - const contractAddresses = { - root: { - Registry: Registry.address, - RootChain: RootChain.address, - GovernanceProxy: GovernanceProxy.address, - RootChainProxy: RootChainProxy.address, - DepositManager: DepositManager.address, - DepositManagerProxy: DepositManagerProxy.address, - WithdrawManager: WithdrawManager.address, - WithdrawManagerProxy: WithdrawManagerProxy.address, - StakeManager: StakeManager.address, - StakeManagerProxy: StakeManagerProxy.address, - SlashingManager: SlashingManager.address, - StakingInfo: StakingInfo.address, - ExitNFT: ExitNFT.address, - StateSender: StateSender.address, - predicates: { - ERC20Predicate: ERC20Predicate.address, - ERC721Predicate: ERC721Predicate.address, - MarketplacePredicate: MarketplacePredicate.address, - TransferWithSigPredicate: TransferWithSigPredicate.address - }, - tokens: { - MaticToken: maticToken.address, - MaticWeth: MaticWeth.address, - TestToken: TestToken.address, - RootERC721: RootERC721.address - } - } - } - - utils.writeContractAddresses(contractAddresses) - }) -} diff --git a/migrations/3_drain_stake_manager.js b/migrations/3_drain_stake_manager.js deleted file mode 100644 index 66d348c83..000000000 --- a/migrations/3_drain_stake_manager.js +++ /dev/null @@ -1,7 +0,0 @@ -const DrainStakeManager = artifacts.require('DrainStakeManager') - -module.exports = async function(deployer) { - deployer.then(async() => { - await deployer.deploy(DrainStakeManager) - }) -} diff --git a/migrations/4_initialize_state.js b/migrations/4_initialize_state.js deleted file mode 100644 index 7535a1eda..000000000 --- a/migrations/4_initialize_state.js +++ /dev/null @@ -1,130 +0,0 @@ -const ethUtils = require('ethereumjs-util') -const bluebird = require('bluebird') -const utils = require('./utils') -const Registry = artifacts.require('Registry') -const ValidatorShare = artifacts.require('ValidatorShare') -const DepositManagerProxy = artifacts.require('DepositManagerProxy') -const StateSender = artifacts.require('StateSender') -const WithdrawManagerProxy = artifacts.require('WithdrawManagerProxy') -const StakeManagerProxy = artifacts.require('StakeManagerProxy') -const SlashingManager = artifacts.require('SlashingManager') -const ERC20Predicate = artifacts.require('ERC20Predicate') -const ERC721Predicate = artifacts.require('ERC721Predicate') -const MarketplacePredicate = artifacts.require('MarketplacePredicate') -const TransferWithSigPredicate = artifacts.require('TransferWithSigPredicate') -const MaticWeth = artifacts.require('MaticWETH') -const Governance = artifacts.require('Governance') -const EventsHubProxy = artifacts.require('EventsHubProxy') - -async function updateContractMap(governance, registry, nameHash, value) { - return governance.update( - registry.address, - registry.contract.methods.updateContractMap(nameHash, value).encodeABI() - ) -} - -module.exports = async function(deployer) { - deployer.then(async() => { - const contractAddresses = utils.getContractAddresses() - const governance = await Governance.at(contractAddresses.root.GovernanceProxy) - - await bluebird - .all([ - Registry.deployed(), - ValidatorShare.deployed(), - DepositManagerProxy.deployed(), - StateSender.deployed(), - WithdrawManagerProxy.deployed(), - StakeManagerProxy.deployed(), - SlashingManager.deployed(), - ERC20Predicate.deployed(), - ERC721Predicate.deployed(), - MarketplacePredicate.deployed(), - TransferWithSigPredicate.deployed(), - EventsHubProxy.deployed() - ]) - .spread(async function( - registry, - validatorShare, - depositManagerProxy, - stateSender, - withdrawManagerProxy, - stakeManagerProxy, - slashingManager, - ERC20Predicate, - ERC721Predicate, - MarketplacePredicate, - TransferWithSigPredicate, - EventsHubProxy - ) { - await updateContractMap( - governance, - registry, - ethUtils.keccak256('validatorShare'), - validatorShare.address - ) - await updateContractMap( - governance, - registry, - ethUtils.keccak256('depositManager'), - depositManagerProxy.address - ) - await updateContractMap( - governance, - registry, - ethUtils.keccak256('withdrawManager'), - withdrawManagerProxy.address - ) - await updateContractMap( - governance, - registry, - ethUtils.keccak256('stakeManager'), - stakeManagerProxy.address - ) - await updateContractMap( - governance, - registry, - ethUtils.keccak256('slashingManager'), - slashingManager.address - ) - await updateContractMap( - governance, - registry, - ethUtils.keccak256('stateSender'), - stateSender.address - ) - await updateContractMap( - governance, - registry, - ethUtils.keccak256('wethToken'), - MaticWeth.address - ) - await updateContractMap( - governance, - registry, - ethUtils.keccak256('eventsHub'), - EventsHubProxy.address - ) - - // whitelist predicates - await governance.update( - registry.address, - registry.contract.methods.addErc20Predicate(ERC20Predicate.address).encodeABI() - ) - - await governance.update( - registry.address, - registry.contract.methods.addErc721Predicate(ERC721Predicate.address).encodeABI() - ) - - await governance.update( - registry.address, - registry.contract.methods.addPredicate(MarketplacePredicate.address, 3).encodeABI() - ) - await governance.update( - registry.address, - registry.contract.methods.addPredicate(TransferWithSigPredicate.address, 3).encodeABI() - ) - }) - }) -} diff --git a/migrations/5_deploy_child_contracts.js b/migrations/5_deploy_child_contracts.js deleted file mode 100644 index a634357be..000000000 --- a/migrations/5_deploy_child_contracts.js +++ /dev/null @@ -1,70 +0,0 @@ -const utils = require('./utils') - -const SafeMath = artifacts.require( - 'openzeppelin-solidity/contracts/math/SafeMath.sol' -) -const ChildChain = artifacts.require('ChildChain') -const MRC20 = artifacts.require('MRC20') - -module.exports = async function(deployer, network, accounts) { - if (deployer.network !== 'bor') { - return - } - - console.log(deployer.network); - - deployer.then(async() => { - await deployer.deploy(SafeMath) - await deployer.link(SafeMath, [ChildChain]) - await deployer.deploy(ChildChain) - - const childChain = await ChildChain.deployed() - const contractAddresses = utils.getContractAddresses() - - let MaticWeth = await childChain.addToken( - accounts[0], - contractAddresses.root.tokens.MaticWeth, - 'ETH on Matic', - 'ETH', - 18, - false // _isERC721 - ) - - let TestToken = await childChain.addToken( - accounts[0], - contractAddresses.root.tokens.TestToken, - 'Test Token', - 'TST', - 18, - false // _isERC721 - ) - - let RootERC721 = await childChain.addToken( - accounts[0], - contractAddresses.root.tokens.RootERC721, - 'Test ERC721', - 'TST721', - 0, - true // _isERC721 - ) - - const maticToken = await MRC20.at('0x0000000000000000000000000000000000001010') - const maticOwner = await maticToken.owner() - if (maticOwner === '0x0000000000000000000000000000000000000000') { - // matic contract at 0x1010 can only be initialized once, after the bor image starts to run - await maticToken.initialize(ChildChain.address, contractAddresses.root.tokens.MaticToken) - } - await childChain.mapToken(contractAddresses.root.tokens.MaticToken, '0x0000000000000000000000000000000000001010', false) - - contractAddresses.child = { - ChildChain: ChildChain.address, - tokens: { - MaticWeth: MaticWeth.logs.find(log => log.event === 'NewToken').args.token, - MaticToken: '0x0000000000000000000000000000000000001010', - TestToken: TestToken.logs.find(log => log.event === 'NewToken').args.token, - RootERC721: RootERC721.logs.find(log => log.event === 'NewToken').args.token - } - } - utils.writeContractAddresses(contractAddresses) - }) -} diff --git a/migrations/6_sync_child_state_to_root.js b/migrations/6_sync_child_state_to_root.js deleted file mode 100644 index 0b8b17b7b..000000000 --- a/migrations/6_sync_child_state_to_root.js +++ /dev/null @@ -1,69 +0,0 @@ -const ethUtils = require('ethereumjs-util') -const utils = require('./utils') - -const Registry = artifacts.require('Registry') -const Governance = artifacts.require('Governance') -const StateSender = artifacts.require('StateSender') -const DepositManager = artifacts.require('DepositManager') - -module.exports = async function(deployer, network, accounts) { - deployer.then(async() => { - const contractAddresses = utils.getContractAddresses() - - if (!contractAddresses.child) { - return - } - - const registry = await Registry.at(contractAddresses.root.Registry) - const governance = await Governance.at(contractAddresses.root.GovernanceProxy) - - await governance.update( - registry.address, - registry.contract.methods.mapToken( - contractAddresses.root.tokens.MaticWeth, - contractAddresses.child.tokens.MaticWeth, - false /* isERC721 */ - ).encodeABI() - ) - - await governance.update( - registry.address, - registry.contract.methods.mapToken( - contractAddresses.root.tokens.MaticToken, - contractAddresses.child.tokens.MaticToken, - false /* isERC721 */ - ).encodeABI() - ) - - await governance.update( - registry.address, - registry.contract.methods.mapToken( - contractAddresses.root.tokens.TestToken, - contractAddresses.child.tokens.TestToken, - false /* isERC721 */ - ).encodeABI() - ) - - await governance.update( - registry.address, - registry.contract.methods.mapToken( - contractAddresses.root.tokens.RootERC721, - contractAddresses.child.tokens.RootERC721, - true /* isERC721 */ - ).encodeABI() - ) - - await governance.update( - registry.address, - registry.contract.methods.updateContractMap( - ethUtils.keccak256('childChain'), - contractAddresses.child.ChildChain - ).encodeABI() - ) - - const stateSenderContract = await StateSender.at(contractAddresses.root.StateSender) - await stateSenderContract.register(contractAddresses.root.DepositManagerProxy, contractAddresses.child.ChildChain) - let depositManager = await DepositManager.at(contractAddresses.root.DepositManagerProxy) - await depositManager.updateChildChainAndStateSender() - }) -} diff --git a/migrations/DepositManager.ts b/migrations/DepositManager.ts new file mode 100644 index 000000000..c9762cd83 --- /dev/null +++ b/migrations/DepositManager.ts @@ -0,0 +1,6 @@ +import { deployProxyImplementation } from '../lib' +import { Artifacts } from 'hardhat/types' + +export async function deploy(artifacts: Artifacts, network: string, from: string) { + return deployProxyImplementation(artifacts, 'DepositManager', network, from) +} diff --git a/migrations/EventsHub.ts b/migrations/EventsHub.ts new file mode 100644 index 000000000..20528331d --- /dev/null +++ b/migrations/EventsHub.ts @@ -0,0 +1,6 @@ +import { deployProxyImplementation } from '../lib' +import { Artifacts } from 'hardhat/types' + +export async function deploy(artifacts: Artifacts, network: string, from: string) { + return deployProxyImplementation(artifacts, 'EventsHub', network, from) +} diff --git a/migrations/Governance.ts b/migrations/Governance.ts new file mode 100644 index 000000000..914488817 --- /dev/null +++ b/migrations/Governance.ts @@ -0,0 +1,6 @@ +import { deployProxyImplementation } from '../lib' +import { Artifacts } from 'hardhat/types' + +export async function deploy(artifacts: Artifacts, network: string, from: string) { + return deployProxyImplementation(artifacts, 'Governance', network, from) +} diff --git a/migrations/Predicate.ts b/migrations/Predicate.ts new file mode 100644 index 000000000..5386cb3e8 --- /dev/null +++ b/migrations/Predicate.ts @@ -0,0 +1,6 @@ +import { deployPredicate } from '../lib' +import { Artifacts } from 'hardhat/types' + +export async function deploy(artifacts: Artifacts, network: string, from: string, params: string) { + return deployPredicate(artifacts, network, from, params) +} diff --git a/migrations/RootChain.ts b/migrations/RootChain.ts new file mode 100644 index 000000000..69bc7ff1d --- /dev/null +++ b/migrations/RootChain.ts @@ -0,0 +1,6 @@ +import { deployProxyImplementation } from '../lib' +import { Artifacts } from 'hardhat/types' + +export async function deploy(artifacts: Artifacts, network: string, from: string) { + return deployProxyImplementation(artifacts, 'WithdrawManager', network, from) +} diff --git a/migrations/StakeManager.ts b/migrations/StakeManager.ts new file mode 100644 index 000000000..fb3a158fe --- /dev/null +++ b/migrations/StakeManager.ts @@ -0,0 +1,7 @@ +import { deployProxyImplementation } from '../lib' +import '@nomiclabs/hardhat-truffle5' +import { Artifacts } from 'hardhat/types' + +export async function deploy(artifacts: Artifacts, network: string, from: string) { + return deployProxyImplementation(artifacts, 'StakeManager', network, from) +} diff --git a/migrations/StakeManagerExtension.ts b/migrations/StakeManagerExtension.ts new file mode 100644 index 000000000..8bcb72fb7 --- /dev/null +++ b/migrations/StakeManagerExtension.ts @@ -0,0 +1,30 @@ +import { StakeManagerExtensionContract, RegistryContract, StakeManagerContract } from '../typechain' +import '@nomiclabs/hardhat-truffle5' +import { Artifacts } from 'hardhat/types' +import { cc, printGovernanceUpdateCommand, ReleaseRegistry } from 'lib' + +export async function deploy(artifacts: Artifacts, network: string, from: string) { + const stakeManagerExtension = artifacts.require('StakeManagerExtension') as StakeManagerExtensionContract + const instance = await stakeManagerExtension.new({ from }) + const deployedAddress = instance.address + + cc.logLn() + await cc.intendGroup(async() => { + cc.logLn(`Deployed StakeManagerExtension at ${deployedAddress}`) + + // generate calldata for multisig + const registry = new ReleaseRegistry(network) + await registry.load() + const stakeManagerContract = await (artifacts.require('StakeManager') as StakeManagerContract).at(registry.getAddress('StakeManagerProxy')) + + const nftContract = await stakeManagerContract.NFTContract() + const logger = await stakeManagerContract.logger() + const validatorShareFactory = await stakeManagerContract.validatorShareFactory() + + const calldata = stakeManagerContract.contract.methods.reinitialize(nftContract, logger, validatorShareFactory, deployedAddress).encodeABI() + printGovernanceUpdateCommand(registry.getAddress('GovernanceProxy'), registry.getAddress('StakeManagerProxy'), calldata) + cc.logLn() + }, 'Deployment result:') + + return { address: deployedAddress } +} diff --git a/migrations/ValidatorShare.ts b/migrations/ValidatorShare.ts new file mode 100644 index 000000000..5bc0ebd32 --- /dev/null +++ b/migrations/ValidatorShare.ts @@ -0,0 +1,27 @@ +import { ReleaseRegistry, cc, printGovernanceUpdateCommand } from '../lib' + +import { ValidatorShareContract, RegistryContract } from '../typechain' + +import '@nomiclabs/hardhat-truffle5' +import { Artifacts } from 'hardhat/types' + +export async function deploy(artifacts: Artifacts, network: string, from: string) { + const validatorShare = artifacts.require('ValidatorShare') as ValidatorShareContract + const instance = await validatorShare.new({ from }) + const deployedAddress = instance.address + + cc.logLn() + await cc.intendGroup(async() => { + cc.logLn(`Deployed ValidatorShare at ${deployedAddress}`) + + // generate calldata for multisig + const registry = new ReleaseRegistry(network) + await registry.load() + const registryContract = await (artifacts.require('Registry') as RegistryContract).at(registry.getAddress('Registry')) + const calldata = registryContract.contract.methods.updateContractMap('0xf32233bced9bbd82f0754425f51b5ffaf897dacec3c8ac3384a66e38ea701ec8', deployedAddress).encodeABI() + printGovernanceUpdateCommand(registry.getAddress('GovernanceProxy'), registry.getAddress('Registry'), calldata) + cc.logLn() + }, 'Deployment result:') + + return { address: deployedAddress } +} diff --git a/migrations/WithdrawManager.ts b/migrations/WithdrawManager.ts new file mode 100644 index 000000000..69bc7ff1d --- /dev/null +++ b/migrations/WithdrawManager.ts @@ -0,0 +1,6 @@ +import { deployProxyImplementation } from '../lib' +import { Artifacts } from 'hardhat/types' + +export async function deploy(artifacts: Artifacts, network: string, from: string) { + return deployProxyImplementation(artifacts, 'WithdrawManager', network, from) +} diff --git a/migrations/utils.js b/migrations/utils.js deleted file mode 100644 index 6457f02c7..000000000 --- a/migrations/utils.js +++ /dev/null @@ -1,12 +0,0 @@ -const fs = require('fs') - -export function getContractAddresses() { - return JSON.parse(fs.readFileSync(`${process.cwd()}/contractAddresses.json`).toString()) -} - -export function writeContractAddresses(contractAddresses) { - fs.writeFileSync( - `${process.cwd()}/contractAddresses.json`, - JSON.stringify(contractAddresses, null, 2) // Indent 2 spaces - ) -} diff --git a/moonwalker-migrations/1_queueJobs.js b/moonwalker-migrations/1_queueJobs.js deleted file mode 100644 index b53fb77bb..000000000 --- a/moonwalker-migrations/1_queueJobs.js +++ /dev/null @@ -1,73 +0,0 @@ -const EthDeployer = require('moonwalker').default - -let id = 0 - -async function deploy() { - if (!process.env.HEIMDALL_ID) { - throw new Error('Please export HEIMDALL_ID environment variable') - } - if (!process.env.MATIC_NAME) { - throw new Error('Please export MATIC_NAME environment variable') - } - - const qClient = await EthDeployer.getQueue() - const deployer = new EthDeployer.Sender(qClient) - - // Libs - await deployer.deploy(transformArtifact('BytesLib')) - await deployer.deploy(transformArtifact('Common')) - await deployer.deploy(transformArtifact('ECVerify')) - await deployer.deploy(transformArtifact('Merkle')) - await deployer.deploy(transformArtifact('MerklePatriciaProof')) - await deployer.deploy(transformArtifact('PriorityQueue')) - await deployer.deploy(transformArtifact('RLPEncode')) - await deployer.deploy(transformArtifact('RLPReader')) - await deployer.deploy(transformArtifact('SafeMath')) - - // contracts, id = 9 - await deployer.deploy(transformArtifact('Governance')) - await deployer.deploy(transformArtifact('GovernanceProxy', ['Governance'])) - await deployer.deploy(transformArtifact('Registry', ['GovernanceProxy'])) - await deployer.deploy(transformArtifact('RootChain')) - await deployer.deploy(transformArtifact('RootChainProxy', [ - 'RootChain', - 'Registry', - { value: process.env.HEIMDALL_ID } - ])) - - await deployer.deploy(transformArtifact('ValidatorShareFactory')) - await deployer.deploy(transformArtifact('StakingInfo', ['Registry'])) - await deployer.deploy(transformArtifact('StakingNFT', [{ value: 'Matic Validator' }, { value: 'MV' }])) - - await deployer.deploy(transformArtifact('TestToken', [{ value: process.env.MATIC_NAME }, { value: process.env.MATIC_NAME }])) - await deployer.deploy(transformArtifact('TestToken', [{ value: `ERC20-${process.env.MATIC_NAME}` }, { value: `ERC20-${process.env.MATIC_NAME}` }])) - await deployer.deploy(transformArtifact('RootERC721', [{ value: `ERC721-${process.env.MATIC_NAME}` }, { value: `ERC721-${process.env.MATIC_NAME}` }])) - await deployer.deploy(transformArtifact('MaticWETH')) - - await deployer.deploy(transformArtifact('StakeManager')) - await deployer.deploy(transformArtifact('StakeManagerProxy', ['StakeManager'])) - await deployer.deploy(transformArtifact('SlashingManager', ['Registry', 'StakingInfo', { value: process.env.HEIMDALL_ID }])) - await deployer.deploy(transformArtifact('ValidatorShare', ['Registry', { value: '0' }, 'StakingNFT', 'StakeManagerProxy'])) - - await deployer.deploy(transformArtifact('StateSender')) - await deployer.deploy(transformArtifact('DepositManager')) - await deployer.deploy(transformArtifact('DepositManagerProxy', ['DepositManager', 'Registry', 'RootChainProxy', 'GovernanceProxy'])) - - await deployer.deploy(transformArtifact('WithdrawManager')) - await deployer.deploy(transformArtifact('ExitNFT', ['Registry'])) - await deployer.deploy(transformArtifact('WithdrawManagerProxy', ['WithdrawManager', 'Registry', 'RootChainProxy', 'ExitNFT'])) - await deployer.deploy(transformArtifact('ERC20PredicateBurnOnly', ['WithdrawManagerProxy', 'DepositManagerProxy', 'Registry'])) - await deployer.deploy(transformArtifact('ERC721PredicateBurnOnly', ['WithdrawManagerProxy', 'DepositManagerProxy'])) -} - -function transformArtifact(contract, args = []) { - const res = { - contract, // abi - args, - id: id++, - type: 'deploy' - } - return JSON.stringify(res) -} - -deploy().then() diff --git a/moonwalker-migrations/2_queueJobs.js b/moonwalker-migrations/2_queueJobs.js deleted file mode 100644 index 111cf8d78..000000000 --- a/moonwalker-migrations/2_queueJobs.js +++ /dev/null @@ -1,221 +0,0 @@ -const fs = require('fs') - -const Registry = artifacts.require('Registry') - -const ethUtils = require('ethereumjs-util') -const EthDeployer = require('moonwalker').default - -let id = 33 // THIS SHOULD BE NUMBER OF JOBS PROCESSED IN THE PREVIOUS SCRIPT - -async function deploy() { - const qClient = await EthDeployer.getQueue() - const deployer = new EthDeployer.Sender(qClient) - - // just need this for encodeABI() - const registry = await Registry.new('0x0000000000000000000000000000000000000000') - - await deployer.deploy( - tx('Governance', 'update', - [ - 'Registry', - { - value: - registry.contract.methods.updateContractMap( - ethUtils.bufferToHex(ethUtils.keccak256('depositManager')), - getAddressForContract('DepositManagerProxy') - ).encodeABI() - } - ], - 'GovernanceProxy' - ) - ) - - await deployer.deploy( - tx('Governance', 'update', - [ - 'Registry', - { - value: - registry.contract.methods.updateContractMap( - ethUtils.bufferToHex(ethUtils.keccak256('withdrawManager')), - getAddressForContract('WithdrawManagerProxy') - ).encodeABI() - } - ], - 'GovernanceProxy' - ) - ) - - await deployer.deploy( - tx('Governance', 'update', - [ - 'Registry', - { - value: - registry.contract.methods.updateContractMap( - ethUtils.bufferToHex(ethUtils.keccak256('slashingManager')), - getAddressForContract('SlashingManager') - ).encodeABI() - } - ], - 'GovernanceProxy' - ) - ) - - await deployer.deploy( - tx('Governance', 'update', - [ - 'Registry', - { - value: - registry.contract.methods.updateContractMap( - ethUtils.bufferToHex(ethUtils.keccak256('stakeManager')), - getAddressForContract('StakeManagerProxy') - ).encodeABI() - } - ], - 'GovernanceProxy' - ) - ) - await deployer.deploy( - tx('Governance', 'update', - [ - 'Registry', - { - value: - registry.contract.methods.updateContractMap( - ethUtils.bufferToHex(ethUtils.keccak256('validatorShare')), - getAddressForContract('ValidatorShare') - ).encodeABI() - } - ], - 'GovernanceProxy' - ) - ) - - - await deployer.deploy( - tx('Governance', 'update', - [ - 'Registry', - { - value: - registry.contract.methods.updateContractMap( - ethUtils.bufferToHex(ethUtils.keccak256('stateSender')), - getAddressForContract('StateSender') - ).encodeABI() - }, - ], - 'GovernanceProxy' - ) - ) - - await deployer.deploy( - tx('Governance', 'update', - [ - 'Registry', - { - value: - registry.contract.methods.updateContractMap( - ethUtils.bufferToHex(ethUtils.keccak256('wethToken')), - getAddressForContract('MaticWETH') - ).encodeABI() - }, - ], - 'GovernanceProxy' - ) - ) - - await deployer.deploy( - tx('Governance', 'update', - [ - 'Registry', - { - value: - registry.contract.methods.addErc20Predicate( - getAddressForContract('ERC20Predicate') - ).encodeABI() - }, - ], - 'GovernanceProxy' - ) - ) - - await deployer.deploy( - tx('Governance', 'update', - [ - 'Registry', - { - value: - registry.contract.methods.addErc721Predicate( - getAddressForContract('ERC721Predicate') - ).encodeABI() - }, - ], - 'GovernanceProxy' - ) - ) - - await deployer.deploy(tx('StakingNFT', 'transferOwnership', ['StakeManagerProxy'])) - await deployer.deploy(tx('StakeManager', 'initialize', [ - 'Registry', - 'RootChainProxy', - 'TestToken', - 'StakingNFT', - 'StakingInfo', - 'ValidatorShareFactory', - 'GovernanceProxy', - { value: process.env.FROM } // owner - ], - 'StakeManagerProxy' -)) -} - -function tx(contract, method, args, addressArtifact) { - return JSON.stringify({ - contract, // abi - addressArtifact, // allowed to be undefined - method, - args, - id: id++, - type: 'transaction' - }) -} - -function getStatus() { - let status = {} - const statusFile = `${process.cwd()}/build/status.json` - if (fs.existsSync(statusFile)) { - try { - status = JSON.parse(fs.readFileSync(statusFile).toString()) - } catch (e) { - console.log(e) - } - } - return status -} - -function getAddressForContract(contract) { - const status = getStatus() - for (let i = 0; i < Object.keys(status).length; i++) { - if (status[i].contract === contract) return status[i].address - } - throw new Error(`${contract} not found in status file`) -} - -function wait(ms) { - return new Promise((resolve, reject) => { - setTimeout(function() { resolve() }, ms); - }) -} - -module.exports = async function(callback) { - try { - await deploy() - await wait(3000) // otherwise the tasks are not queued - } catch (e) { - // truffle exec