diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 89c92e9b8..6adb263b2 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -6,39 +6,38 @@ if npm run lint; then npx hardhat compile --force npm run docgen git add docs - cp artifacts/contracts/PolygonZkEVMBridge.sol/PolygonZkEVMBridge.json compiled-contracts/ - cp artifacts/contracts/PolygonZkEVMGlobalExitRoot.sol/PolygonZkEVMGlobalExitRoot.json compiled-contracts/ - cp artifacts/contracts/PolygonZkEVMGlobalExitRootL2.sol/PolygonZkEVMGlobalExitRootL2.json compiled-contracts/ + cp artifacts/contracts/outdated/PolygonZkEVMBridge.sol/PolygonZkEVMBridge.json compiled-contracts/ + cp artifacts/contracts/outdated/PolygonZkEVMGlobalExitRoot.sol/PolygonZkEVMGlobalExitRoot.json compiled-contracts/ + cp artifacts/contracts/outdated/PolygonZkEVMGlobalExitRootL2.sol/PolygonZkEVMGlobalExitRootL2.json compiled-contracts/ cp artifacts/contracts/lib/TokenWrapped.sol/TokenWrapped.json compiled-contracts/ - cp artifacts/contracts/mocks/PolygonZkEVMBridgeMock.sol/PolygonZkEVMBridgeMock.json compiled-contracts/ + cp artifacts/contracts/outdated/mocks/PolygonZkEVMBridgeMock.sol/PolygonZkEVMBridgeMock.json compiled-contracts/ cp artifacts/contracts/mocks/ERC20PermitMock.sol/ERC20PermitMock.json compiled-contracts/ - cp artifacts/contracts/mocks/PolygonZkEVMGlobalExitRootL2Mock.sol/PolygonZkEVMGlobalExitRootL2Mock.json compiled-contracts/ - cp artifacts/contracts/mocks/PolygonZkEVMGlobalExitRootMock.sol/PolygonZkEVMGlobalExitRootMock.json compiled-contracts/ - cp artifacts/contracts/mocks/PolygonZkEVMMock.sol/PolygonZkEVMMock.json compiled-contracts/ + cp artifacts/contracts/outdated/mocks/PolygonZkEVMGlobalExitRootL2Mock.sol/PolygonZkEVMGlobalExitRootL2Mock.json compiled-contracts/ + cp artifacts/contracts/outdated/mocks/PolygonZkEVMGlobalExitRootMock.sol/PolygonZkEVMGlobalExitRootMock.json compiled-contracts/ + cp artifacts/contracts/outdated/mocks/PolygonZkEVMMock.sol/PolygonZkEVMMock.json compiled-contracts/ cp artifacts/contracts/mocks/VerifierRollupHelperMock.sol/VerifierRollupHelperMock.json compiled-contracts/ - cp artifacts/contracts/PolygonZkEVM.sol/PolygonZkEVM.json compiled-contracts/ + cp artifacts/contracts/outdated/PolygonZkEVM.sol/PolygonZkEVM.json compiled-contracts/ cp artifacts/contracts/verifiers/FflonkVerifier.sol/FflonkVerifier.json compiled-contracts/ cp artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json compiled-contracts/ cp artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json compiled-contracts/ cp artifacts/contracts/deployment/PolygonZkEVMDeployer.sol/PolygonZkEVMDeployer.json compiled-contracts/ cp artifacts/contracts/PolygonZkEVMTimelock.sol/PolygonZkEVMTimelock.json compiled-contracts/ - cp artifacts/contracts/v2/PolygonRollupManager.sol/PolygonRollupManager.json compiled-contracts/ - cp artifacts/contracts/v2/mocks/PolygonRollupManagerMock.sol/PolygonRollupManagerMock.json compiled-contracts/ - cp artifacts/contracts/v2/mocks/PolygonRollupManagerMockInternalTest.sol/PolygonRollupManagerMockInternalTest.json compiled-contracts/ + cp artifacts/contracts/PolygonRollupManager.sol/PolygonRollupManager.json compiled-contracts/ + cp artifacts/contracts/mocks/PolygonRollupManagerMock.sol/PolygonRollupManagerMock.json compiled-contracts/ + cp artifacts/contracts/mocks/PolygonRollupManagerMockInternalTest.sol/PolygonRollupManagerMockInternalTest.json compiled-contracts/ - cp artifacts/contracts/v2/PolygonZkEVMBridgeV2.sol/PolygonZkEVMBridgeV2.json compiled-contracts/ - cp artifacts/contracts/v2/PolygonZkEVMGlobalExitRootV2.sol/PolygonZkEVMGlobalExitRootV2.json compiled-contracts/ + cp artifacts/contracts/PolygonZkEVMBridgeV2.sol/PolygonZkEVMBridgeV2.json compiled-contracts/ + cp artifacts/contracts/PolygonZkEVMGlobalExitRootV2.sol/PolygonZkEVMGlobalExitRootV2.json compiled-contracts/ - cp artifacts/contracts/v2/PolygonZkEVMGlobalExitRootV2.sol/PolygonZkEVMGlobalExitRootV2.json compiled-contracts/ - cp artifacts/contracts/v2/consensus/zkEVM/PolygonZkEVMEtrog.sol/PolygonZkEVMEtrog.json compiled-contracts/ - cp artifacts/contracts/v2/consensus/zkEVM/PolygonZkEVMExistentEtrog.sol/PolygonZkEVMExistentEtrog.json compiled-contracts/ - cp artifacts/contracts/v2/previousVersions/PolygonZkEVMEtrogPrevious.sol/PolygonZkEVMEtrogPrevious.json compiled-contracts/ + cp artifacts/contracts/consensus/zkEVM/PolygonZkEVMEtrog.sol/PolygonZkEVMEtrog.json compiled-contracts/ + cp artifacts/contracts/consensus/zkEVM/PolygonZkEVMExistentEtrog.sol/PolygonZkEVMExistentEtrog.json compiled-contracts/ + cp artifacts/contracts/previousVersions/PolygonZkEVMEtrogPrevious.sol/PolygonZkEVMEtrogPrevious.json compiled-contracts/ - cp artifacts/contracts/v2/consensus/validium/PolygonValidiumEtrog.sol/PolygonValidiumEtrog.json compiled-contracts/ - cp artifacts/contracts/v2/consensus/validium/PolygonDataCommittee.sol/PolygonDataCommittee.json compiled-contracts/ + cp artifacts/contracts/consensus/validium/PolygonValidiumEtrog.sol/PolygonValidiumEtrog.json compiled-contracts/ + cp artifacts/contracts/consensus/validium/PolygonDataCommittee.sol/PolygonDataCommittee.json compiled-contracts/ - cp artifacts/contracts/v2/utils/ClaimCompressor.sol/ClaimCompressor.json compiled-contracts/ + cp artifacts/contracts/utils/ClaimCompressor.sol/ClaimCompressor.json compiled-contracts/ git add compiled-contracts exit 0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..b80fd0773 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,41 @@ +name: Foundry CI + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + strategy: + fail-fast: true + + name: Build and Test + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Install NPM dependencies + run: npm ci + + - name: Run Forge build + run: | + forge --version + forge build + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test diff --git a/.gitignore b/.gitignore index ca2367c8b..442716288 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,22 @@ typechain-types/ create_rollup_parameters.json docker/deploymentOutput + +# From foundry-template: + +/target +/out +/cache +/coverage +lcov.info +.DS_Store +.env +.vscode +.password + +broadcast/*/31337 +deployments/**/31337.* + +# storage layout checker library +storage_check_cache +storage_check_report \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..bc13cb5be --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std +[submodule "lib/deployer-kit"] + path = lib/deployer-kit + url = https://github.com/0xPolygon/deployer-kit +[submodule "lib/solmate"] + path = lib/solmate + url = https://github.com/transmissions11/solmate diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000..3c032078a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +18 diff --git a/contracts/mocks/DaiMock.sol b/contracts-ignored-originals/DaiMock.sol similarity index 100% rename from contracts/mocks/DaiMock.sol rename to contracts-ignored-originals/DaiMock.sol diff --git a/contracts/v2/PolygonZkEVMBridgeV2.sol b/contracts-ignored-originals/PolygonZkEVMBridgeV2.sol similarity index 99% rename from contracts/v2/PolygonZkEVMBridgeV2.sol rename to contracts-ignored-originals/PolygonZkEVMBridgeV2.sol index 8f88fa8e5..8136fdf07 100644 --- a/contracts/v2/PolygonZkEVMBridgeV2.sol +++ b/contracts-ignored-originals/PolygonZkEVMBridgeV2.sol @@ -2,15 +2,15 @@ pragma solidity 0.8.20; -import "./lib/DepositContractV2.sol"; -import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "contracts/interfaces/IBasePolygonZkEVMGlobalExitRoot.sol"; +import "contracts/interfaces/IBridgeMessageReceiver.sol"; +import "contracts/interfaces/IPolygonZkEVMBridgeV2.sol"; +import "contracts/lib/DepositContractV2.sol"; +import "contracts/lib/EmergencyManager.sol"; +import "contracts/lib/GlobalExitRootLib.sol"; +import "contracts/lib/TokenWrapped.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; -import "../lib/TokenWrapped.sol"; -import "../interfaces/IBasePolygonZkEVMGlobalExitRoot.sol"; -import "../interfaces/IBridgeMessageReceiver.sol"; -import "./interfaces/IPolygonZkEVMBridgeV2.sol"; -import "../lib/EmergencyManager.sol"; -import "../lib/GlobalExitRootLib.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; /** * PolygonZkEVMBridge that will be deployed on Ethereum and all Polygon rollups diff --git a/contracts/mocks/Uni.sol b/contracts-ignored-originals/Uni.sol similarity index 100% rename from contracts/mocks/Uni.sol rename to contracts-ignored-originals/Uni.sol diff --git a/contracts/v2/PolygonRollupManager.sol b/contracts/PolygonRollupManager.sol similarity index 99% rename from contracts/v2/PolygonRollupManager.sol rename to contracts/PolygonRollupManager.sol index 33b4fe196..415520567 100644 --- a/contracts/v2/PolygonRollupManager.sol +++ b/contracts/PolygonRollupManager.sol @@ -4,10 +4,10 @@ pragma solidity 0.8.20; import "./interfaces/IPolygonRollupManager.sol"; import "./interfaces/IPolygonZkEVMGlobalExitRootV2.sol"; -import "../interfaces/IPolygonZkEVMBridge.sol"; +import "./interfaces/IPolygonZkEVMBridge.sol"; import "./interfaces/IPolygonRollupBase.sol"; -import "../interfaces/IVerifierRollup.sol"; -import "../lib/EmergencyManager.sol"; +import "./interfaces/IVerifierRollup.sol"; +import "./lib/EmergencyManager.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; import "./lib/PolygonTransparentProxy.sol"; import "./lib/PolygonAccessControlUpgradeable.sol"; diff --git a/contracts/PolygonZkEVMBridgeV2.sol.ignored b/contracts/PolygonZkEVMBridgeV2.sol.ignored new file mode 100644 index 000000000..fb204ece8 --- /dev/null +++ b/contracts/PolygonZkEVMBridgeV2.sol.ignored @@ -0,0 +1,1160 @@ +// SPDX-License-Identifier: AGPL-3.0 + +pragma solidity 0.8.20; + +import "./lib/DepositContractV2.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; +import "./lib/TokenWrapped.sol"; +import "./interfaces/IBasePolygonZkEVMGlobalExitRoot.sol"; +import "./interfaces/IBridgeMessageReceiver.sol"; +import "./interfaces/IPolygonZkEVMBridgeV2.sol"; +import "./lib/EmergencyManager.sol"; +import "./lib/GlobalExitRootLib.sol"; + +/** + * PolygonZkEVMBridge that will be deployed on Ethereum and all Polygon rollups + * Contract responsible to manage the token interactions with other networks + */ +contract PolygonZkEVMBridgeV2 is + DepositContractV2, + EmergencyManager, + IPolygonZkEVMBridgeV2 +{ + using SafeERC20Upgradeable for IERC20Upgradeable; + + // Wrapped Token information struct + struct TokenInformation { + uint32 originNetwork; + address originTokenAddress; + } + + // bytes4(keccak256(bytes("permit(address,address,uint256,uint256,uint8,bytes32,bytes32)"))); + bytes4 private constant _PERMIT_SIGNATURE = 0xd505accf; + + // bytes4(keccak256(bytes("permit(address,address,uint256,uint256,bool,uint8,bytes32,bytes32)"))); + bytes4 private constant _PERMIT_SIGNATURE_DAI = 0x8fcbaf0c; + + // Mainnet identifier + uint32 private constant _MAINNET_NETWORK_ID = 0; + + // ZkEVM identifier + uint32 private constant _ZKEVM_NETWORK_ID = 1; + + // Leaf type asset + uint8 private constant _LEAF_TYPE_ASSET = 0; + + // Leaf type message + uint8 private constant _LEAF_TYPE_MESSAGE = 1; + + // Nullifier offset + uint256 private constant _MAX_LEAFS_PER_NETWORK = 2 ** 32; + + // Indicate where's the mainnet flag bit in the global index + uint256 private constant _GLOBAL_INDEX_MAINNET_FLAG = 2 ** 64; + + // Init code of the erc20 wrapped token, to deploy a wrapped token the constructor parameters must be appended + bytes public constant BASE_INIT_BYTECODE_WRAPPED_TOKEN = + hex"6101006040523480156200001257600080fd5b5060405162001b6638038062001b6683398101604081905262000035916200028d565b82826003620000458382620003a1565b506004620000548282620003a1565b50503360c0525060ff811660e052466080819052620000739062000080565b60a052506200046d915050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f620000ad6200012e565b805160209182012060408051808201825260018152603160f81b90840152805192830193909352918101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc66060820152608081018390523060a082015260c001604051602081830303815290604052805190602001209050919050565b6060600380546200013f9062000312565b80601f01602080910402602001604051908101604052809291908181526020018280546200016d9062000312565b8015620001be5780601f106200019257610100808354040283529160200191620001be565b820191906000526020600020905b815481529060010190602001808311620001a057829003601f168201915b5050505050905090565b634e487b7160e01b600052604160045260246000fd5b600082601f830112620001f057600080fd5b81516001600160401b03808211156200020d576200020d620001c8565b604051601f8301601f19908116603f01168101908282118183101715620002385762000238620001c8565b816040528381526020925086838588010111156200025557600080fd5b600091505b838210156200027957858201830151818301840152908201906200025a565b600093810190920192909252949350505050565b600080600060608486031215620002a357600080fd5b83516001600160401b0380821115620002bb57600080fd5b620002c987838801620001de565b94506020860151915080821115620002e057600080fd5b50620002ef86828701620001de565b925050604084015160ff811681146200030757600080fd5b809150509250925092565b600181811c908216806200032757607f821691505b6020821081036200034857634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200039c57600081815260208120601f850160051c81016020861015620003775750805b601f850160051c820191505b81811015620003985782815560010162000383565b5050505b505050565b81516001600160401b03811115620003bd57620003bd620001c8565b620003d581620003ce845462000312565b846200034e565b602080601f8311600181146200040d5760008415620003f45750858301515b600019600386901b1c1916600185901b17855562000398565b600085815260208120601f198616915b828110156200043e578886015182559484019460019091019084016200041d565b50858210156200045d5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60805160a05160c05160e0516116aa620004bc6000396000610237015260008181610307015281816105c001526106a70152600061053a015260008181610379015261050401526116aa6000f3fe608060405234801561001057600080fd5b50600436106101775760003560e01c806370a08231116100d8578063a457c2d71161008c578063d505accf11610066578063d505accf1461039b578063dd62ed3e146103ae578063ffa1ad74146103f457600080fd5b8063a457c2d71461034e578063a9059cbb14610361578063cd0d00961461037457600080fd5b806395d89b41116100bd57806395d89b41146102e75780639dc29fac146102ef578063a3c573eb1461030257600080fd5b806370a08231146102915780637ecebe00146102c757600080fd5b806330adf81f1161012f5780633644e515116101145780633644e51514610261578063395093511461026957806340c10f191461027c57600080fd5b806330adf81f14610209578063313ce5671461023057600080fd5b806318160ddd1161016057806318160ddd146101bd57806320606b70146101cf57806323b872dd146101f657600080fd5b806306fdde031461017c578063095ea7b31461019a575b600080fd5b610184610430565b60405161019191906113e4565b60405180910390f35b6101ad6101a8366004611479565b6104c2565b6040519015158152602001610191565b6002545b604051908152602001610191565b6101c17f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f81565b6101ad6102043660046114a3565b6104dc565b6101c17f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b60405160ff7f0000000000000000000000000000000000000000000000000000000000000000168152602001610191565b6101c1610500565b6101ad610277366004611479565b61055c565b61028f61028a366004611479565b6105a8565b005b6101c161029f3660046114df565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205490565b6101c16102d53660046114df565b60056020526000908152604090205481565b610184610680565b61028f6102fd366004611479565b61068f565b6103297f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610191565b6101ad61035c366004611479565b61075e565b6101ad61036f366004611479565b61082f565b6101c17f000000000000000000000000000000000000000000000000000000000000000081565b61028f6103a9366004611501565b61083d565b6101c16103bc366004611574565b73ffffffffffffffffffffffffffffffffffffffff918216600090815260016020908152604080832093909416825291909152205490565b6101846040518060400160405280600181526020017f310000000000000000000000000000000000000000000000000000000000000081525081565b60606003805461043f906115a7565b80601f016020809104026020016040519081016040528092919081815260200182805461046b906115a7565b80156104b85780601f1061048d576101008083540402835291602001916104b8565b820191906000526020600020905b81548152906001019060200180831161049b57829003601f168201915b5050505050905090565b6000336104d0818585610b73565b60019150505b92915050565b6000336104ea858285610d27565b6104f5858585610dfe565b506001949350505050565b60007f00000000000000000000000000000000000000000000000000000000000000004614610537576105324661106d565b905090565b507f000000000000000000000000000000000000000000000000000000000000000090565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff871684529091528120549091906104d090829086906105a3908790611629565b610b73565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610672576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f546f6b656e577261707065643a3a6f6e6c794272696467653a204e6f7420506f60448201527f6c79676f6e5a6b45564d4272696467650000000000000000000000000000000060648201526084015b60405180910390fd5b61067c8282611135565b5050565b60606004805461043f906115a7565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610754576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f546f6b656e577261707065643a3a6f6e6c794272696467653a204e6f7420506f60448201527f6c79676f6e5a6b45564d427269646765000000000000000000000000000000006064820152608401610669565b61067c8282611228565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716845290915281205490919083811015610822576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760448201527f207a65726f0000000000000000000000000000000000000000000000000000006064820152608401610669565b6104f58286868403610b73565b6000336104d0818585610dfe565b834211156108cc576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f546f6b656e577261707065643a3a7065726d69743a204578706972656420706560448201527f726d6974000000000000000000000000000000000000000000000000000000006064820152608401610669565b73ffffffffffffffffffffffffffffffffffffffff8716600090815260056020526040812080547f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9918a918a918a9190866109268361163c565b9091555060408051602081019690965273ffffffffffffffffffffffffffffffffffffffff94851690860152929091166060840152608083015260a082015260c0810186905260e0016040516020818303038152906040528051906020012090506000610991610500565b6040517f19010000000000000000000000000000000000000000000000000000000000006020820152602281019190915260428101839052606201604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181528282528051602091820120600080855291840180845281905260ff89169284019290925260608301879052608083018690529092509060019060a0016020604051602081039080840390855afa158015610a55573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff811615801590610ad057508973ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b610b5c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602760248201527f546f6b656e577261707065643a3a7065726d69743a20496e76616c696420736960448201527f676e6174757265000000000000000000000000000000000000000000000000006064820152608401610669565b610b678a8a8a610b73565b50505050505050505050565b73ffffffffffffffffffffffffffffffffffffffff8316610c15576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460448201527f72657373000000000000000000000000000000000000000000000000000000006064820152608401610669565b73ffffffffffffffffffffffffffffffffffffffff8216610cb8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f20616464726560448201527f73730000000000000000000000000000000000000000000000000000000000006064820152608401610669565b73ffffffffffffffffffffffffffffffffffffffff83811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b73ffffffffffffffffffffffffffffffffffffffff8381166000908152600160209081526040808320938616835292905220547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114610df85781811015610deb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610669565b610df88484848403610b73565b50505050565b73ffffffffffffffffffffffffffffffffffffffff8316610ea1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f20616460448201527f64726573730000000000000000000000000000000000000000000000000000006064820152608401610669565b73ffffffffffffffffffffffffffffffffffffffff8216610f44576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201527f65737300000000000000000000000000000000000000000000000000000000006064820152608401610669565b73ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604090205481811015610ffa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e742065786365656473206260448201527f616c616e636500000000000000000000000000000000000000000000000000006064820152608401610669565b73ffffffffffffffffffffffffffffffffffffffff848116600081815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a3610df8565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f611098610430565b8051602091820120604080518082018252600181527f310000000000000000000000000000000000000000000000000000000000000090840152805192830193909352918101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc66060820152608081018390523060a082015260c001604051602081830303815290604052805190602001209050919050565b73ffffffffffffffffffffffffffffffffffffffff82166111b2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610669565b80600260008282546111c49190611629565b909155505073ffffffffffffffffffffffffffffffffffffffff8216600081815260208181526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b73ffffffffffffffffffffffffffffffffffffffff82166112cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f2061646472657360448201527f73000000000000000000000000000000000000000000000000000000000000006064820152608401610669565b73ffffffffffffffffffffffffffffffffffffffff821660009081526020819052604090205481811015611381576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e60448201527f63650000000000000000000000000000000000000000000000000000000000006064820152608401610669565b73ffffffffffffffffffffffffffffffffffffffff83166000818152602081815260408083208686039055600280548790039055518581529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9101610d1a565b600060208083528351808285015260005b81811015611411578581018301518582016040015282016113f5565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461147457600080fd5b919050565b6000806040838503121561148c57600080fd5b61149583611450565b946020939093013593505050565b6000806000606084860312156114b857600080fd5b6114c184611450565b92506114cf60208501611450565b9150604084013590509250925092565b6000602082840312156114f157600080fd5b6114fa82611450565b9392505050565b600080600080600080600060e0888a03121561151c57600080fd5b61152588611450565b965061153360208901611450565b95506040880135945060608801359350608088013560ff8116811461155757600080fd5b9699959850939692959460a0840135945060c09093013592915050565b6000806040838503121561158757600080fd5b61159083611450565b915061159e60208401611450565b90509250929050565b600181811c908216806115bb57607f821691505b6020821081036115f4577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b808201808211156104d6576104d66115fa565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361166d5761166d6115fa565b506001019056fea26469706673582212208d88fee561cff7120d381c345cfc534cef8229a272dc5809d4bbb685ad67141164736f6c63430008110033"; + + // Network identifier + uint32 public networkID; + + // Global Exit Root address + IBasePolygonZkEVMGlobalExitRoot public globalExitRootManager; + + // Last updated deposit count to the global exit root manager + uint32 public lastUpdatedDepositCount; + + // Leaf index --> claimed bit map + mapping(uint256 => uint256) public claimedBitMap; + + // keccak256(OriginNetwork || tokenAddress) --> Wrapped token address + mapping(bytes32 => address) public tokenInfoToWrappedToken; + + // Wrapped token Address --> Origin token information + mapping(address => TokenInformation) public wrappedTokenToTokenInfo; + + // Rollup manager address, previously PolygonZkEVM + /// @custom:oz-renamed-from polygonZkEVMaddress + address public polygonRollupManager; + + // Native address + address public gasTokenAddress; + + // Native address + uint32 public gasTokenNetwork; + + // Gas token metadata + bytes public gasTokenMetadata; + + // WETH address + TokenWrapped public WETHToken; + + /** + * @dev Emitted when bridge assets or messages to another network + */ + event BridgeEvent( + uint8 leafType, + uint32 originNetwork, + address originAddress, + uint32 destinationNetwork, + address destinationAddress, + uint256 amount, + bytes metadata, + uint32 depositCount + ); + + /** + * @dev Emitted when a claim is done from another network + */ + event ClaimEvent( + uint256 globalIndex, + uint32 originNetwork, + address originAddress, + address destinationAddress, + uint256 amount + ); + + /** + * @dev Emitted when a new wrapped token is created + */ + event NewWrappedToken( + uint32 originNetwork, + address originTokenAddress, + address wrappedTokenAddress, + bytes metadata + ); + + /** + * Disable initalizers on the implementation following the best practices + */ + constructor() { + _disableInitializers(); + } + + /** + * @param _networkID networkID + * @param _gasTokenAddress gas token address + * @param _gasTokenNetwork gas token network + * @param _globalExitRootManager global exit root manager address + * @param _polygonRollupManager polygonZkEVM address + * @notice The value of `_polygonRollupManager` on the L2 deployment of the contract will be address(0), so + * emergency state is not possible for the L2 deployment of the bridge, intentionally + * @param _gasTokenMetadata Abi encoded gas token metadata + */ + function initialize( + uint32 _networkID, + address _gasTokenAddress, + uint32 _gasTokenNetwork, + IBasePolygonZkEVMGlobalExitRoot _globalExitRootManager, + address _polygonRollupManager, + bytes memory _gasTokenMetadata + ) external virtual initializer { + networkID = _networkID; + globalExitRootManager = _globalExitRootManager; + polygonRollupManager = _polygonRollupManager; + + // Set gas token + if (_gasTokenAddress == address(0)) { + // Gas token will be ether + if (_gasTokenNetwork != 0) { + revert GasTokenNetworkMustBeZeroOnEther(); + } + // WETHToken, gasTokenAddress and gasTokenNetwork will be 0 + // gasTokenMetadata will be empty + } else { + // Gas token will be an erc20 + gasTokenAddress = _gasTokenAddress; + gasTokenNetwork = _gasTokenNetwork; + gasTokenMetadata = _gasTokenMetadata; + + // Create a wrapped token for WETH, with salt == 0 + WETHToken = _deployWrappedToken( + 0, // salt + abi.encode("Wrapped Ether", "WETH", 18) + ); + } + + // Initialize OZ contracts + __ReentrancyGuard_init(); + } + + modifier onlyRollupManager() { + if (polygonRollupManager != msg.sender) { + revert OnlyRollupManager(); + } + _; + } + + /** + * @notice Deposit add a new leaf to the merkle tree + * note If this function is called with a reentrant token, it would be possible to `claimTokens` in the same call + * Reducing the supply of tokens on this contract, and actually locking tokens in the contract. + * Therefore we recommend to third parties bridges that if they do implement reentrant call of `beforeTransfer` of some reentrant tokens + * do not call any external address in that case + * note User/UI must be aware of the existing/available networks when choosing the destination network + * @param destinationNetwork Network destination + * @param destinationAddress Address destination + * @param amount Amount of tokens + * @param token Token address, 0 address is reserved for ether + * @param forceUpdateGlobalExitRoot Indicates if the new global exit root is updated or not + * @param permitData Raw data of the call `permit` of the token + */ + function bridgeAsset( + uint32 destinationNetwork, + address destinationAddress, + uint256 amount, + address token, + bool forceUpdateGlobalExitRoot, + bytes calldata permitData + ) public payable virtual ifNotEmergencyState nonReentrant { + if (destinationNetwork == networkID) { + revert DestinationNetworkInvalid(); + } + + address originTokenAddress; + uint32 originNetwork; + bytes memory metadata; + uint256 leafAmount = amount; + + if (token == address(0)) { + // Check gas token transfer + if (msg.value != amount) { + revert AmountDoesNotMatchMsgValue(); + } + + // Set gas token parameters + originNetwork = gasTokenNetwork; + originTokenAddress = gasTokenAddress; + metadata = gasTokenMetadata; + } else { + // Check msg.value is 0 if tokens are bridged + if (msg.value != 0) { + revert MsgValueNotZero(); + } + + // Check if it's WETH, this only applies on L2 networks with gasTokens + // In case ether is the native token, WETHToken will be 0, and the address 0 is already checked + if (token == address(WETHToken)) { + // Burn tokens + TokenWrapped(token).burn(msg.sender, amount); + + // Both origin network and originTokenAddress will be 0 + // Metadata will be empty + } else { + TokenInformation memory tokenInfo = wrappedTokenToTokenInfo[ + token + ]; + + if (tokenInfo.originTokenAddress != address(0)) { + // The token is a wrapped token from another network + + // Burn tokens + TokenWrapped(token).burn(msg.sender, amount); + + originTokenAddress = tokenInfo.originTokenAddress; + originNetwork = tokenInfo.originNetwork; + } else { + // Use permit if any + if (permitData.length != 0) { + _permit(token, amount, permitData); + } + + // In order to support fee tokens check the amount received, not the transferred + uint256 balanceBefore = IERC20Upgradeable(token).balanceOf( + address(this) + ); + IERC20Upgradeable(token).safeTransferFrom( + msg.sender, + address(this), + amount + ); + uint256 balanceAfter = IERC20Upgradeable(token).balanceOf( + address(this) + ); + + // Override leafAmount with the received amount + leafAmount = balanceAfter - balanceBefore; + + originTokenAddress = token; + originNetwork = networkID; + } + // Encode metadata + metadata = getTokenMetadata(token); + } + } + + emit BridgeEvent( + _LEAF_TYPE_ASSET, + originNetwork, + originTokenAddress, + destinationNetwork, + destinationAddress, + leafAmount, + metadata, + uint32(depositCount) + ); + + _addLeaf( + getLeafValue( + _LEAF_TYPE_ASSET, + originNetwork, + originTokenAddress, + destinationNetwork, + destinationAddress, + leafAmount, + keccak256(metadata) + ) + ); + + // Update the new root to the global exit root manager if set by the user + if (forceUpdateGlobalExitRoot) { + _updateGlobalExitRoot(); + } + } + + /** + * @notice Bridge message and send ETH value + * note User/UI must be aware of the existing/available networks when choosing the destination network + * @param destinationNetwork Network destination + * @param destinationAddress Address destination + * @param forceUpdateGlobalExitRoot Indicates if the new global exit root is updated or not + * @param metadata Message metadata + */ + function bridgeMessage( + uint32 destinationNetwork, + address destinationAddress, + bool forceUpdateGlobalExitRoot, + bytes calldata metadata + ) external payable ifNotEmergencyState { + // If exist a gas token, only allow call this function without value + if (msg.value != 0 && address(WETHToken) != address(0)) { + revert NoValueInMessagesOnGasTokenNetworks(); + } + + _bridgeMessage( + destinationNetwork, + destinationAddress, + msg.value, + forceUpdateGlobalExitRoot, + metadata + ); + } + + /** + * @notice Bridge message and send ETH value + * note User/UI must be aware of the existing/available networks when choosing the destination network + * @param destinationNetwork Network destination + * @param destinationAddress Address destination + * @param amountWETH Amount of WETH tokens + * @param forceUpdateGlobalExitRoot Indicates if the new global exit root is updated or not + * @param metadata Message metadata + */ + function bridgeMessageWETH( + uint32 destinationNetwork, + address destinationAddress, + uint256 amountWETH, + bool forceUpdateGlobalExitRoot, + bytes calldata metadata + ) external ifNotEmergencyState { + // If native token is ether, disable this function + if (address(WETHToken) == address(0)) { + revert NativeTokenIsEther(); + } + + // Burn wETH tokens + WETHToken.burn(msg.sender, amountWETH); + + _bridgeMessage( + destinationNetwork, + destinationAddress, + amountWETH, + forceUpdateGlobalExitRoot, + metadata + ); + } + + /** + * @notice Bridge message and send ETH value + * @param destinationNetwork Network destination + * @param destinationAddress Address destination + * @param amountEther Amount of ether along with the message + * @param forceUpdateGlobalExitRoot Indicates if the new global exit root is updated or not + * @param metadata Message metadata + */ + function _bridgeMessage( + uint32 destinationNetwork, + address destinationAddress, + uint256 amountEther, + bool forceUpdateGlobalExitRoot, + bytes calldata metadata + ) internal { + if (destinationNetwork == networkID) { + revert DestinationNetworkInvalid(); + } + + emit BridgeEvent( + _LEAF_TYPE_MESSAGE, + networkID, + msg.sender, + destinationNetwork, + destinationAddress, + amountEther, + metadata, + uint32(depositCount) + ); + + _addLeaf( + getLeafValue( + _LEAF_TYPE_MESSAGE, + networkID, + msg.sender, + destinationNetwork, + destinationAddress, + amountEther, + keccak256(metadata) + ) + ); + + // Update the new root to the global exit root manager if set by the user + if (forceUpdateGlobalExitRoot) { + _updateGlobalExitRoot(); + } + } + + /** + * @notice Verify merkle proof and withdraw tokens/ether + * @param smtProofLocalExitRoot Smt proof to proof the leaf against the network exit root + * @param smtProofRollupExitRoot Smt proof to proof the rollupLocalExitRoot against the rollups exit root + * @param globalIndex Global index is defined as: + * | 191 bits | 1 bit | 32 bits | 32 bits | + * | 0 | mainnetFlag | rollupIndex | localRootIndex | + * note that only the rollup index will be used only in case the mainnet flag is 0 + * note that global index do not assert the unused bits to 0. + * This means that when synching the events, the globalIndex must be decoded the same way that in the Smart contract + * to avoid possible synch attacks + * @param mainnetExitRoot Mainnet exit root + * @param rollupExitRoot Rollup exit root + * @param originNetwork Origin network + * @param originTokenAddress Origin token address, 0 address is reserved for ether + * @param destinationNetwork Network destination + * @param destinationAddress Address destination + * @param amount Amount of tokens + * @param metadata Abi encoded metadata if any, empty otherwise + */ + function claimAsset( + bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofLocalExitRoot, + bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofRollupExitRoot, + uint256 globalIndex, + bytes32 mainnetExitRoot, + bytes32 rollupExitRoot, + uint32 originNetwork, + address originTokenAddress, + uint32 destinationNetwork, + address destinationAddress, + uint256 amount, + bytes calldata metadata + ) external ifNotEmergencyState { + // Destination network must be this networkID + if (destinationNetwork != networkID) { + revert DestinationNetworkInvalid(); + } + + // Verify leaf exist and it does not have been claimed + _verifyLeaf( + smtProofLocalExitRoot, + smtProofRollupExitRoot, + globalIndex, + mainnetExitRoot, + rollupExitRoot, + getLeafValue( + _LEAF_TYPE_ASSET, + originNetwork, + originTokenAddress, + destinationNetwork, + destinationAddress, + amount, + keccak256(metadata) + ) + ); + + // Transfer funds + if (originTokenAddress == address(0)) { + if (address(WETHToken) == address(0)) { + // Ether is the native token + /* solhint-disable avoid-low-level-calls */ + (bool success, ) = destinationAddress.call{value: amount}( + new bytes(0) + ); + if (!success) { + revert EtherTransferFailed(); + } + } else { + // Claim wETH + WETHToken.mint(destinationAddress, amount); + } + } else { + // Check if it's gas token + if ( + originTokenAddress == gasTokenAddress && + gasTokenNetwork == originNetwork + ) { + // Transfer gas token + /* solhint-disable avoid-low-level-calls */ + (bool success, ) = destinationAddress.call{value: amount}( + new bytes(0) + ); + if (!success) { + revert EtherTransferFailed(); + } + } else { + // Transfer tokens + if (originNetwork == networkID) { + // The token is an ERC20 from this network + IERC20Upgradeable(originTokenAddress).safeTransfer( + destinationAddress, + amount + ); + } else { + // The tokens is not from this network + // Create a wrapper for the token if not exist yet + bytes32 tokenInfoHash = keccak256( + abi.encodePacked(originNetwork, originTokenAddress) + ); + address wrappedToken = tokenInfoToWrappedToken[ + tokenInfoHash + ]; + + if (wrappedToken == address(0)) { + // Get ERC20 metadata + + // Create a new wrapped erc20 using create2 + TokenWrapped newWrappedToken = _deployWrappedToken( + tokenInfoHash, + metadata + ); + + // Mint tokens for the destination address + newWrappedToken.mint(destinationAddress, amount); + + // Create mappings + tokenInfoToWrappedToken[tokenInfoHash] = address( + newWrappedToken + ); + + wrappedTokenToTokenInfo[ + address(newWrappedToken) + ] = TokenInformation(originNetwork, originTokenAddress); + + emit NewWrappedToken( + originNetwork, + originTokenAddress, + address(newWrappedToken), + metadata + ); + } else { + // Use the existing wrapped erc20 + TokenWrapped(wrappedToken).mint( + destinationAddress, + amount + ); + } + } + } + } + + emit ClaimEvent( + globalIndex, + originNetwork, + originTokenAddress, + destinationAddress, + amount + ); + } + + /** + * @notice Verify merkle proof and execute message + * If the receiving address is an EOA, the call will result as a success + * Which means that the amount of ether will be transferred correctly, but the message + * will not trigger any execution + * @param smtProofLocalExitRoot Smt proof to proof the leaf against the exit root + * @param smtProofRollupExitRoot Smt proof to proof the rollupLocalExitRoot against the rollups exit root + * @param globalIndex Global index is defined as: + * | 191 bits | 1 bit | 32 bits | 32 bits | + * | 0 | mainnetFlag | rollupIndex | localRootIndex | + * note that only the rollup index will be used only in case the mainnet flag is 0 + * note that global index do not assert the unused bits to 0. + * This means that when synching the events, the globalIndex must be decoded the same way that in the Smart contract + * to avoid possible synch attacks + * @param mainnetExitRoot Mainnet exit root + * @param rollupExitRoot Rollup exit root + * @param originNetwork Origin network + * @param originAddress Origin address + * @param destinationNetwork Network destination + * @param destinationAddress Address destination + * @param amount message value + * @param metadata Abi encoded metadata if any, empty otherwise + */ + function claimMessage( + bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofLocalExitRoot, + bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofRollupExitRoot, + uint256 globalIndex, + bytes32 mainnetExitRoot, + bytes32 rollupExitRoot, + uint32 originNetwork, + address originAddress, + uint32 destinationNetwork, + address destinationAddress, + uint256 amount, + bytes calldata metadata + ) external ifNotEmergencyState { + // Destination network must be this networkID + if (destinationNetwork != networkID) { + revert DestinationNetworkInvalid(); + } + + // Verify leaf exist and it does not have been claimed + _verifyLeaf( + smtProofLocalExitRoot, + smtProofRollupExitRoot, + globalIndex, + mainnetExitRoot, + rollupExitRoot, + getLeafValue( + _LEAF_TYPE_MESSAGE, + originNetwork, + originAddress, + destinationNetwork, + destinationAddress, + amount, + keccak256(metadata) + ) + ); + + // Execute message + bool success; + if (address(WETHToken) == address(0)) { + // Native token is ether + // Transfer ether + /* solhint-disable avoid-low-level-calls */ + (success, ) = destinationAddress.call{value: amount}( + abi.encodeCall( + IBridgeMessageReceiver.onMessageReceived, + (originAddress, originNetwork, metadata) + ) + ); + } else { + // Mint wETH tokens + WETHToken.mint(destinationAddress, amount); + + // Execute message + /* solhint-disable avoid-low-level-calls */ + (success, ) = destinationAddress.call( + abi.encodeCall( + IBridgeMessageReceiver.onMessageReceived, + (originAddress, originNetwork, metadata) + ) + ); + } + + if (!success) { + revert MessageFailed(); + } + + emit ClaimEvent( + globalIndex, + originNetwork, + originAddress, + destinationAddress, + amount + ); + } + + /** + * @notice Returns the precalculated address of a wrapper using the token information + * Note Updating the metadata of a token is not supported. + * Since the metadata has relevance in the address deployed, this function will not return a valid + * wrapped address if the metadata provided is not the original one. + * @param originNetwork Origin network + * @param originTokenAddress Origin token address, 0 address is reserved for ether + * @param name Name of the token + * @param symbol Symbol of the token + * @param decimals Decimals of the token + */ + function precalculatedWrapperAddress( + uint32 originNetwork, + address originTokenAddress, + string memory name, + string memory symbol, + uint8 decimals + ) public view returns (address) { + bytes32 salt = keccak256( + abi.encodePacked(originNetwork, originTokenAddress) + ); + + bytes32 hashCreate2 = keccak256( + abi.encodePacked( + bytes1(0xff), + address(this), + salt, + keccak256( + abi.encodePacked( + BASE_INIT_BYTECODE_WRAPPED_TOKEN, + abi.encode(name, symbol, decimals) + ) + ) + ) + ); + + // Last 20 bytes of hash to address + return address(uint160(uint256(hashCreate2))); + } + + /** + * @notice Returns the address of a wrapper using the token information if already exist + * @param originNetwork Origin network + * @param originTokenAddress Origin token address, 0 address is reserved for ether + */ + function getTokenWrappedAddress( + uint32 originNetwork, + address originTokenAddress + ) external view returns (address) { + return + tokenInfoToWrappedToken[ + keccak256(abi.encodePacked(originNetwork, originTokenAddress)) + ]; + } + + /** + * @notice Function to activate the emergency state + " Only can be called by the Polygon ZK-EVM in extreme situations + */ + function activateEmergencyState() external onlyRollupManager { + _activateEmergencyState(); + } + + /** + * @notice Function to deactivate the emergency state + " Only can be called by the Polygon ZK-EVM + */ + function deactivateEmergencyState() external onlyRollupManager { + _deactivateEmergencyState(); + } + + /** + * @notice Verify leaf and checks that it has not been claimed + * @param smtProofLocalExitRoot Smt proof + * @param smtProofRollupExitRoot Smt proof + * @param globalIndex Index of the leaf + * @param mainnetExitRoot Mainnet exit root + * @param rollupExitRoot Rollup exit root + * @param leafValue leaf value + */ + function _verifyLeaf( + bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofLocalExitRoot, + bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofRollupExitRoot, + uint256 globalIndex, + bytes32 mainnetExitRoot, + bytes32 rollupExitRoot, + bytes32 leafValue + ) internal { + // Check blockhash where the global exit root was set + // Note that previusly timestamps were setted, since in only checked if != 0 it's ok + uint256 blockHashGlobalExitRoot = globalExitRootManager + .globalExitRootMap( + GlobalExitRootLib.calculateGlobalExitRoot( + mainnetExitRoot, + rollupExitRoot + ) + ); + + // check that this global exit root exist + if (blockHashGlobalExitRoot == 0) { + revert GlobalExitRootInvalid(); + } + + uint32 leafIndex; + uint32 sourceBridgeNetwork; + + // Get origin network from global index + if (globalIndex & _GLOBAL_INDEX_MAINNET_FLAG != 0) { + // the network is mainnet, therefore sourceBridgeNetwork is 0 + + // Last 32 bits are leafIndex + leafIndex = uint32(globalIndex); + + if ( + !verifyMerkleProof( + leafValue, + smtProofLocalExitRoot, + leafIndex, + mainnetExitRoot + ) + ) { + revert InvalidSmtProof(); + } + } else { + // the network is a rollup, therefore sourceBridgeNetwork must be decoded + uint32 indexRollup = uint32(globalIndex >> 32); + sourceBridgeNetwork = indexRollup + 1; + + // Last 32 bits are leafIndex + leafIndex = uint32(globalIndex); + + // Verify merkle proof agains rollup exit root + if ( + !verifyMerkleProof( + calculateRoot(leafValue, smtProofLocalExitRoot, leafIndex), + smtProofRollupExitRoot, + indexRollup, + rollupExitRoot + ) + ) { + revert InvalidSmtProof(); + } + } + + // Set and check nullifier + _setAndCheckClaimed(leafIndex, sourceBridgeNetwork); + } + + /** + * @notice Function to check if an index is claimed or not + * @param leafIndex Index + * @param sourceBridgeNetwork Origin network + */ + function isClaimed( + uint32 leafIndex, + uint32 sourceBridgeNetwork + ) external view returns (bool) { + uint256 globalIndex; + + // For consistency with the previous setted nullifiers + if ( + networkID == _MAINNET_NETWORK_ID && + sourceBridgeNetwork == _ZKEVM_NETWORK_ID + ) { + globalIndex = uint256(leafIndex); + } else { + globalIndex = + uint256(leafIndex) + + uint256(sourceBridgeNetwork) * + _MAX_LEAFS_PER_NETWORK; + } + (uint256 wordPos, uint256 bitPos) = _bitmapPositions(globalIndex); + uint256 mask = (1 << bitPos); + return (claimedBitMap[wordPos] & mask) == mask; + } + + /** + * @notice Function to check that an index is not claimed and set it as claimed + * @param leafIndex Index + * @param sourceBridgeNetwork Origin network + */ + function _setAndCheckClaimed( + uint32 leafIndex, + uint32 sourceBridgeNetwork + ) private { + uint256 globalIndex; + + // For consistency with the previous setted nullifiers + if ( + networkID == _MAINNET_NETWORK_ID && + sourceBridgeNetwork == _ZKEVM_NETWORK_ID + ) { + globalIndex = uint256(leafIndex); + } else { + globalIndex = + uint256(leafIndex) + + uint256(sourceBridgeNetwork) * + _MAX_LEAFS_PER_NETWORK; + } + (uint256 wordPos, uint256 bitPos) = _bitmapPositions(globalIndex); + uint256 mask = 1 << bitPos; + uint256 flipped = claimedBitMap[wordPos] ^= mask; + if (flipped & mask == 0) { + revert AlreadyClaimed(); + } + } + + /** + * @notice Function to update the globalExitRoot if the last deposit is not submitted + */ + function updateGlobalExitRoot() external { + if (lastUpdatedDepositCount < depositCount) { + _updateGlobalExitRoot(); + } + } + + /** + * @notice Function to update the globalExitRoot + */ + function _updateGlobalExitRoot() internal { + lastUpdatedDepositCount = uint32(depositCount); + globalExitRootManager.updateExitRoot(getRoot()); + } + + /** + * @notice Function decode an index into a wordPos and bitPos + * @param index Index + */ + function _bitmapPositions( + uint256 index + ) private pure returns (uint256 wordPos, uint256 bitPos) { + wordPos = uint248(index >> 8); + bitPos = uint8(index); + } + + /** + * @notice Function to call token permit method of extended ERC20 + + @param token ERC20 token address + * @param amount Quantity that is expected to be allowed + * @param permitData Raw data of the call `permit` of the token + */ + function _permit( + address token, + uint256 amount, + bytes calldata permitData + ) internal { + bytes4 sig = bytes4(permitData[:4]); + if (sig == _PERMIT_SIGNATURE) { + ( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) = abi.decode( + permitData[4:], + ( + address, + address, + uint256, + uint256, + uint8, + bytes32, + bytes32 + ) + ); + if (owner != msg.sender) { + revert NotValidOwner(); + } + if (spender != address(this)) { + revert NotValidSpender(); + } + + if (value != amount) { + revert NotValidAmount(); + } + + // we call without checking the result, in case it fails and he doesn't have enough balance + // the following transferFrom should be fail. This prevents DoS attacks from using a signature + // before the smartcontract call + /* solhint-disable avoid-low-level-calls */ + address(token).call( + abi.encodeWithSelector( + _PERMIT_SIGNATURE, + owner, + spender, + value, + deadline, + v, + r, + s + ) + ); + } else { + if (sig != _PERMIT_SIGNATURE_DAI) { + revert NotValidSignature(); + } + + ( + address holder, + address spender, + uint256 nonce, + uint256 expiry, + bool allowed, + uint8 v, + bytes32 r, + bytes32 s + ) = abi.decode( + permitData[4:], + ( + address, + address, + uint256, + uint256, + bool, + uint8, + bytes32, + bytes32 + ) + ); + + if (holder != msg.sender) { + revert NotValidOwner(); + } + + if (spender != address(this)) { + revert NotValidSpender(); + } + + // we call without checking the result, in case it fails and he doesn't have enough balance + // the following transferFrom should be fail. This prevents DoS attacks from using a signature + // before the smartcontract call + /* solhint-disable avoid-low-level-calls */ + address(token).call( + abi.encodeWithSelector( + _PERMIT_SIGNATURE_DAI, + holder, + spender, + nonce, + expiry, + allowed, + v, + r, + s + ) + ); + } + } + + /** + * @notice Internal function that uses create2 to deploy the wrapped tokens + * @param salt Salt used in create2 params, + * tokenInfoHash will be used as salt for all wrappeds except for bridge native WETH, that will be bytes32(0) + * @param constructorArgs Encoded constructor args for the wrapped token + */ + function _deployWrappedToken( + bytes32 salt, + bytes memory constructorArgs + ) internal returns (TokenWrapped newWrappedToken) { + bytes memory initBytecode = abi.encodePacked( + BASE_INIT_BYTECODE_WRAPPED_TOKEN, + constructorArgs + ); + + /// @solidity memory-safe-assembly + assembly { + newWrappedToken := create2( + 0, + add(initBytecode, 0x20), + mload(initBytecode), + salt + ) + } + if (address(newWrappedToken) == address(0)) + revert FailedTokenWrappedDeployment(); + } + + // Helpers to safely get the metadata from a token, inspired by https://github.com/traderjoe-xyz/joe-core/blob/main/contracts/MasterChefJoeV3.sol#L55-L95 + + /** + * @notice Provides a safe ERC20.symbol version which returns 'NO_SYMBOL' as fallback string + * @param token The address of the ERC-20 token contract + */ + function _safeSymbol(address token) internal view returns (string memory) { + (bool success, bytes memory data) = address(token).staticcall( + abi.encodeCall(IERC20MetadataUpgradeable.symbol, ()) + ); + return success ? _returnDataToString(data) : "NO_SYMBOL"; + } + + /** + * @notice Provides a safe ERC20.name version which returns 'NO_NAME' as fallback string. + * @param token The address of the ERC-20 token contract. + */ + function _safeName(address token) internal view returns (string memory) { + (bool success, bytes memory data) = address(token).staticcall( + abi.encodeCall(IERC20MetadataUpgradeable.name, ()) + ); + return success ? _returnDataToString(data) : "NO_NAME"; + } + + /** + * @notice Provides a safe ERC20.decimals version which returns '18' as fallback value. + * Note Tokens with (decimals > 255) are not supported + * @param token The address of the ERC-20 token contract + */ + function _safeDecimals(address token) internal view returns (uint8) { + (bool success, bytes memory data) = address(token).staticcall( + abi.encodeCall(IERC20MetadataUpgradeable.decimals, ()) + ); + return success && data.length == 32 ? abi.decode(data, (uint8)) : 18; + } + + /** + * @notice Function to convert returned data to string + * returns 'NOT_VALID_ENCODING' as fallback value. + * @param data returned data + */ + function _returnDataToString( + bytes memory data + ) internal pure returns (string memory) { + if (data.length >= 64) { + return abi.decode(data, (string)); + } else if (data.length == 32) { + // Since the strings on bytes32 are encoded left-right, check the first zero in the data + uint256 nonZeroBytes; + while (nonZeroBytes < 32 && data[nonZeroBytes] != 0) { + nonZeroBytes++; + } + + // If the first one is 0, we do not handle the encoding + if (nonZeroBytes == 0) { + return "NOT_VALID_ENCODING"; + } + // Create a byte array with nonZeroBytes length + bytes memory bytesArray = new bytes(nonZeroBytes); + for (uint256 i = 0; i < nonZeroBytes; i++) { + bytesArray[i] = data[i]; + } + return string(bytesArray); + } else { + return "NOT_VALID_ENCODING"; + } + } + + /** + * @notice Returns the encoded token metadata + * @param token Address of the token + */ + + function getTokenMetadata( + address token + ) public view returns (bytes memory) { + return + abi.encode( + _safeName(token), + _safeSymbol(token), + _safeDecimals(token) + ); + } + + /** + * @notice Returns the precalculated address of a wrapper using the token address + * Note Updating the metadata of a token is not supported. + * Since the metadata has relevance in the address deployed, this function will not return a valid + * wrapped address if the metadata provided is not the original one. + * @param originNetwork Origin network + * @param originTokenAddress Origin token address, 0 address is reserved for ether + * @param token Address of the token to calculate the wrapper address + */ + function calculateTokenWrapperAddress( + uint32 originNetwork, + address originTokenAddress, + address token + ) external view returns (address) { + return + precalculatedWrapperAddress( + originNetwork, + originTokenAddress, + _safeName(token), + _safeSymbol(token), + _safeDecimals(token) + ); + } +} diff --git a/contracts/v2/PolygonZkEVMGlobalExitRootV2.sol b/contracts/PolygonZkEVMGlobalExitRootV2.sol similarity index 99% rename from contracts/v2/PolygonZkEVMGlobalExitRootV2.sol rename to contracts/PolygonZkEVMGlobalExitRootV2.sol index c904686b5..b22a51992 100644 --- a/contracts/v2/PolygonZkEVMGlobalExitRootV2.sol +++ b/contracts/PolygonZkEVMGlobalExitRootV2.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.20; import "./interfaces/IPolygonZkEVMGlobalExitRootV2.sol"; import "./lib/PolygonZkEVMGlobalExitRootBaseStorage.sol"; -import "../lib/GlobalExitRootLib.sol"; +import "./lib/GlobalExitRootLib.sol"; import "./lib/DepositContractBase.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; diff --git a/contracts/PolygonZkEVMTimelock.sol b/contracts/PolygonZkEVMTimelock.sol index e61735f54..83d3cde67 100644 --- a/contracts/PolygonZkEVMTimelock.sol +++ b/contracts/PolygonZkEVMTimelock.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.20; import "@openzeppelin/contracts/governance/TimelockController.sol"; -import "./PolygonZkEVM.sol"; +import "./outdated/PolygonZkEVM.sol"; /** * @dev Contract module which acts as a timelocked controller. diff --git a/contracts/v2/consensus/validium/PolygonDataCommittee.sol b/contracts/consensus/validium/PolygonDataCommittee.sol similarity index 100% rename from contracts/v2/consensus/validium/PolygonDataCommittee.sol rename to contracts/consensus/validium/PolygonDataCommittee.sol diff --git a/contracts/v2/consensus/validium/PolygonValidiumEtrog.sol b/contracts/consensus/validium/PolygonValidiumEtrog.sol similarity index 100% rename from contracts/v2/consensus/validium/PolygonValidiumEtrog.sol rename to contracts/consensus/validium/PolygonValidiumEtrog.sol diff --git a/contracts/v2/consensus/zkEVM/PolygonZkEVMEtrog.sol b/contracts/consensus/zkEVM/PolygonZkEVMEtrog.sol similarity index 100% rename from contracts/v2/consensus/zkEVM/PolygonZkEVMEtrog.sol rename to contracts/consensus/zkEVM/PolygonZkEVMEtrog.sol diff --git a/contracts/v2/consensus/zkEVM/PolygonZkEVMExistentEtrog.sol b/contracts/consensus/zkEVM/PolygonZkEVMExistentEtrog.sol similarity index 100% rename from contracts/v2/consensus/zkEVM/PolygonZkEVMExistentEtrog.sol rename to contracts/consensus/zkEVM/PolygonZkEVMExistentEtrog.sol diff --git a/contracts/v2/interfaces/IDataAvailabilityProtocol.sol b/contracts/interfaces/IDataAvailabilityProtocol.sol similarity index 100% rename from contracts/v2/interfaces/IDataAvailabilityProtocol.sol rename to contracts/interfaces/IDataAvailabilityProtocol.sol diff --git a/contracts/v2/interfaces/IPolygonDataCommitteeErrors.sol b/contracts/interfaces/IPolygonDataCommitteeErrors.sol similarity index 100% rename from contracts/v2/interfaces/IPolygonDataCommitteeErrors.sol rename to contracts/interfaces/IPolygonDataCommitteeErrors.sol diff --git a/contracts/v2/interfaces/IPolygonRollupBase.sol b/contracts/interfaces/IPolygonRollupBase.sol similarity index 100% rename from contracts/v2/interfaces/IPolygonRollupBase.sol rename to contracts/interfaces/IPolygonRollupBase.sol diff --git a/contracts/v2/interfaces/IPolygonRollupManager.sol b/contracts/interfaces/IPolygonRollupManager.sol similarity index 100% rename from contracts/v2/interfaces/IPolygonRollupManager.sol rename to contracts/interfaces/IPolygonRollupManager.sol diff --git a/contracts/v2/interfaces/IPolygonValidium.sol b/contracts/interfaces/IPolygonValidium.sol similarity index 100% rename from contracts/v2/interfaces/IPolygonValidium.sol rename to contracts/interfaces/IPolygonValidium.sol diff --git a/contracts/v2/interfaces/IPolygonZkEVMBridgeV2.sol b/contracts/interfaces/IPolygonZkEVMBridgeV2.sol similarity index 98% rename from contracts/v2/interfaces/IPolygonZkEVMBridgeV2.sol rename to contracts/interfaces/IPolygonZkEVMBridgeV2.sol index aab8c6af8..73268a441 100644 --- a/contracts/v2/interfaces/IPolygonZkEVMBridgeV2.sol +++ b/contracts/interfaces/IPolygonZkEVMBridgeV2.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; -import "../../interfaces/IBasePolygonZkEVMGlobalExitRoot.sol"; +import "./IBasePolygonZkEVMGlobalExitRoot.sol"; interface IPolygonZkEVMBridgeV2 { /** diff --git a/contracts/interfaces/IPolygonZkEVMBridgeV2Extended.sol b/contracts/interfaces/IPolygonZkEVMBridgeV2Extended.sol new file mode 100644 index 000000000..3d15f2c19 --- /dev/null +++ b/contracts/interfaces/IPolygonZkEVMBridgeV2Extended.sol @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: AGPL-3.0 + +pragma solidity ^0.8.20; +import "./IBasePolygonZkEVMGlobalExitRoot.sol"; + +interface IPolygonZkEVMBridgeV2Extended { + error AlreadyClaimed(); + error AmountDoesNotMatchMsgValue(); + error DestinationNetworkInvalid(); + error EtherTransferFailed(); + error FailedTokenWrappedDeployment(); + error GasTokenNetworkMustBeZeroOnEther(); + error GlobalExitRootInvalid(); + error InvalidSmtProof(); + error MerkleTreeFull(); + error MessageFailed(); + error MsgValueNotZero(); + error NativeTokenIsEther(); + error NoValueInMessagesOnGasTokenNetworks(); + error NotValidAmount(); + error NotValidOwner(); + error NotValidSignature(); + error NotValidSpender(); + error OnlyEmergencyState(); + error OnlyNotEmergencyState(); + error OnlyRollupManager(); + + event BridgeEvent( + uint8 leafType, + uint32 originNetwork, + address originAddress, + uint32 destinationNetwork, + address destinationAddress, + uint256 amount, + bytes metadata, + uint32 depositCount + ); + event ClaimEvent( + uint256 globalIndex, + uint32 originNetwork, + address originAddress, + address destinationAddress, + uint256 amount + ); + event EmergencyStateActivated(); + event EmergencyStateDeactivated(); + event Initialized(uint8 version); + event NewWrappedToken( + uint32 originNetwork, + address originTokenAddress, + address wrappedTokenAddress, + bytes metadata + ); + + function BASE_INIT_BYTECODE_WRAPPED_TOKEN() + external + view + returns (bytes memory); + + function WETHToken() external view returns (address); + + function activateEmergencyState() external; + + function bridgeAsset( + uint32 destinationNetwork, + address destinationAddress, + uint256 amount, + address token, + bool forceUpdateGlobalExitRoot, + bytes calldata permitData + ) external payable; + + function bridgeMessage( + uint32 destinationNetwork, + address destinationAddress, + bool forceUpdateGlobalExitRoot, + bytes calldata metadata + ) external payable; + + function bridgeMessageWETH( + uint32 destinationNetwork, + address destinationAddress, + uint256 amountWETH, + bool forceUpdateGlobalExitRoot, + bytes calldata metadata + ) external; + + function calculateRoot( + bytes32 leafHash, + bytes32[32] calldata smtProof, + uint32 index + ) external pure returns (bytes32); + + function calculateTokenWrapperAddress( + uint32 originNetwork, + address originTokenAddress, + address token + ) external view returns (address); + + function claimAsset( + bytes32[32] calldata smtProofLocalExitRoot, + bytes32[32] calldata smtProofRollupExitRoot, + uint256 globalIndex, + bytes32 mainnetExitRoot, + bytes32 rollupExitRoot, + uint32 originNetwork, + address originTokenAddress, + uint32 destinationNetwork, + address destinationAddress, + uint256 amount, + bytes calldata metadata + ) external; + + function claimMessage( + bytes32[32] calldata smtProofLocalExitRoot, + bytes32[32] calldata smtProofRollupExitRoot, + uint256 globalIndex, + bytes32 mainnetExitRoot, + bytes32 rollupExitRoot, + uint32 originNetwork, + address originAddress, + uint32 destinationNetwork, + address destinationAddress, + uint256 amount, + bytes calldata metadata + ) external; + + function claimedBitMap(uint256) external view returns (uint256); + + function deactivateEmergencyState() external; + + function depositCount() external view returns (uint256); + + function gasTokenAddress() external view returns (address); + + function gasTokenMetadata() external view returns (bytes memory); + + function gasTokenNetwork() external view returns (uint32); + + function getLeafValue( + uint8 leafType, + uint32 originNetwork, + address originAddress, + uint32 destinationNetwork, + address destinationAddress, + uint256 amount, + bytes32 metadataHash + ) external pure returns (bytes32); + + function getRoot() external view returns (bytes32); + + function getTokenMetadata( + address token + ) external view returns (bytes memory); + + function getTokenWrappedAddress( + uint32 originNetwork, + address originTokenAddress + ) external view returns (address); + + function globalExitRootManager() external view returns (address); + + function initialize( + uint32 _networkID, + address _gasTokenAddress, + uint32 _gasTokenNetwork, + IBasePolygonZkEVMGlobalExitRoot _globalExitRootManager, + address _polygonRollupManager, + bytes calldata _gasTokenMetadata + ) external; + + function isClaimed( + uint32 leafIndex, + uint32 sourceBridgeNetwork + ) external view returns (bool); + + function isEmergencyState() external view returns (bool); + + function lastUpdatedDepositCount() external view returns (uint32); + + function networkID() external view returns (uint32); + + function polygonRollupManager() external view returns (address); + + function precalculatedWrapperAddress( + uint32 originNetwork, + address originTokenAddress, + string calldata name, + string calldata symbol, + uint8 decimals + ) external view returns (address); + + function tokenInfoToWrappedToken(bytes32) external view returns (address); + + function updateGlobalExitRoot() external; + + function verifyMerkleProof( + bytes32 leafHash, + bytes32[32] calldata smtProof, + uint32 index, + bytes32 root + ) external pure returns (bool); + + function wrappedTokenToTokenInfo( + address destinationAddress + ) external view returns (uint32, address); +} diff --git a/contracts/v2/interfaces/IPolygonZkEVMGlobalExitRootV2.sol b/contracts/interfaces/IPolygonZkEVMGlobalExitRootV2.sol similarity index 85% rename from contracts/v2/interfaces/IPolygonZkEVMGlobalExitRootV2.sol rename to contracts/interfaces/IPolygonZkEVMGlobalExitRootV2.sol index e4110754b..03e95d566 100644 --- a/contracts/v2/interfaces/IPolygonZkEVMGlobalExitRootV2.sol +++ b/contracts/interfaces/IPolygonZkEVMGlobalExitRootV2.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; -import "../../interfaces/IBasePolygonZkEVMGlobalExitRoot.sol"; +import "./IBasePolygonZkEVMGlobalExitRoot.sol"; interface IPolygonZkEVMGlobalExitRootV2 is IBasePolygonZkEVMGlobalExitRoot { function getLastGlobalExitRoot() external view returns (bytes32); diff --git a/contracts/v2/interfaces/IPolygonZkEVMVEtrogErrors.sol b/contracts/interfaces/IPolygonZkEVMVEtrogErrors.sol similarity index 97% rename from contracts/v2/interfaces/IPolygonZkEVMVEtrogErrors.sol rename to contracts/interfaces/IPolygonZkEVMVEtrogErrors.sol index 1d38fd371..5b027ae97 100644 --- a/contracts/v2/interfaces/IPolygonZkEVMVEtrogErrors.sol +++ b/contracts/interfaces/IPolygonZkEVMVEtrogErrors.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; -import "../../interfaces/IPolygonZkEVMErrors.sol"; +import "./IPolygonZkEVMErrors.sol"; interface IPolygonZkEVMVEtrogErrors is IPolygonZkEVMErrors { /** diff --git a/contracts/v2/lib/DepositContractBase.sol b/contracts/lib/DepositContractBase.sol similarity index 100% rename from contracts/v2/lib/DepositContractBase.sol rename to contracts/lib/DepositContractBase.sol diff --git a/contracts/v2/lib/DepositContractV2.sol b/contracts/lib/DepositContractV2.sol similarity index 100% rename from contracts/v2/lib/DepositContractV2.sol rename to contracts/lib/DepositContractV2.sol diff --git a/contracts/v2/lib/LegacyZKEVMStateVariables.sol b/contracts/lib/LegacyZKEVMStateVariables.sol similarity index 100% rename from contracts/v2/lib/LegacyZKEVMStateVariables.sol rename to contracts/lib/LegacyZKEVMStateVariables.sol diff --git a/contracts/v2/lib/PolygonAccessControlUpgradeable.sol b/contracts/lib/PolygonAccessControlUpgradeable.sol similarity index 100% rename from contracts/v2/lib/PolygonAccessControlUpgradeable.sol rename to contracts/lib/PolygonAccessControlUpgradeable.sol diff --git a/contracts/v2/lib/PolygonConstantsBase.sol b/contracts/lib/PolygonConstantsBase.sol similarity index 100% rename from contracts/v2/lib/PolygonConstantsBase.sol rename to contracts/lib/PolygonConstantsBase.sol diff --git a/contracts/v2/lib/PolygonRollupBaseEtrog.sol b/contracts/lib/PolygonRollupBaseEtrog.sol similarity index 99% rename from contracts/v2/lib/PolygonRollupBaseEtrog.sol rename to contracts/lib/PolygonRollupBaseEtrog.sol index 2004eee5c..bfbdaa595 100644 --- a/contracts/v2/lib/PolygonRollupBaseEtrog.sol +++ b/contracts/lib/PolygonRollupBaseEtrog.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.20; import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; import "../interfaces/IPolygonZkEVMGlobalExitRootV2.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import "../../interfaces/IPolygonZkEVMErrors.sol"; +import "../interfaces/IPolygonZkEVMErrors.sol"; import "../interfaces/IPolygonZkEVMVEtrogErrors.sol"; import "../PolygonRollupManager.sol"; import "../interfaces/IPolygonRollupBase.sol"; diff --git a/contracts/v2/lib/PolygonTransparentProxy.sol b/contracts/lib/PolygonTransparentProxy.sol similarity index 100% rename from contracts/v2/lib/PolygonTransparentProxy.sol rename to contracts/lib/PolygonTransparentProxy.sol diff --git a/contracts/v2/lib/PolygonZkEVMGlobalExitRootBaseStorage.sol b/contracts/lib/PolygonZkEVMGlobalExitRootBaseStorage.sol similarity index 100% rename from contracts/v2/lib/PolygonZkEVMGlobalExitRootBaseStorage.sol rename to contracts/lib/PolygonZkEVMGlobalExitRootBaseStorage.sol diff --git a/contracts/v2/mocks/BridgeReceiverMock.sol b/contracts/mocks/BridgeReceiverMock.sol similarity index 99% rename from contracts/v2/mocks/BridgeReceiverMock.sol rename to contracts/mocks/BridgeReceiverMock.sol index 11364419b..caaa8e255 100644 --- a/contracts/v2/mocks/BridgeReceiverMock.sol +++ b/contracts/mocks/BridgeReceiverMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0 -import "../PolygonZkEVMBridgeV2.sol"; +import "../interfaces/IPolygonZkEVMBridgeV2.sol"; pragma solidity 0.8.20; /** diff --git a/contracts/v2/mocks/PolygonRollupManagerEmptyMock.sol b/contracts/mocks/PolygonRollupManagerEmptyMock.sol similarity index 98% rename from contracts/v2/mocks/PolygonRollupManagerEmptyMock.sol rename to contracts/mocks/PolygonRollupManagerEmptyMock.sol index 7663d5f86..76eec3d74 100644 --- a/contracts/v2/mocks/PolygonRollupManagerEmptyMock.sol +++ b/contracts/mocks/PolygonRollupManagerEmptyMock.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.20; import "../PolygonRollupManager.sol"; import "../interfaces/IPolygonRollupBase.sol"; -import "../../lib/EmergencyManager.sol"; +import "../lib/EmergencyManager.sol"; /** * PolygonRollupManager used only to test conensus contracts diff --git a/contracts/v2/mocks/PolygonRollupManagerMock.sol b/contracts/mocks/PolygonRollupManagerMock.sol similarity index 100% rename from contracts/v2/mocks/PolygonRollupManagerMock.sol rename to contracts/mocks/PolygonRollupManagerMock.sol diff --git a/contracts/v2/mocks/PolygonRollupManagerMockInternalTest.sol b/contracts/mocks/PolygonRollupManagerMockInternalTest.sol similarity index 100% rename from contracts/v2/mocks/PolygonRollupManagerMockInternalTest.sol rename to contracts/mocks/PolygonRollupManagerMockInternalTest.sol diff --git a/contracts/v2/newDeployments/PolygonRollupManagerNotUpgraded.sol b/contracts/newDeployments/PolygonRollupManagerNotUpgraded.sol similarity index 100% rename from contracts/v2/newDeployments/PolygonRollupManagerNotUpgraded.sol rename to contracts/newDeployments/PolygonRollupManagerNotUpgraded.sol diff --git a/contracts/PolygonZkEVM.sol b/contracts/outdated/PolygonZkEVM.sol similarity index 99% rename from contracts/PolygonZkEVM.sol rename to contracts/outdated/PolygonZkEVM.sol index 4bc4a1826..886537781 100644 --- a/contracts/PolygonZkEVM.sol +++ b/contracts/outdated/PolygonZkEVM.sol @@ -2,12 +2,12 @@ pragma solidity ^0.8.20; import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; -import "./interfaces/IVerifierRollup.sol"; -import "./interfaces/IPolygonZkEVMGlobalExitRoot.sol"; +import "../interfaces/IVerifierRollup.sol"; +import "../interfaces/IPolygonZkEVMGlobalExitRoot.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import "./interfaces/IPolygonZkEVMBridge.sol"; -import "./lib/EmergencyManager.sol"; -import "./interfaces/IPolygonZkEVMErrors.sol"; +import "../interfaces/IPolygonZkEVMBridge.sol"; +import "../lib/EmergencyManager.sol"; +import "../interfaces/IPolygonZkEVMErrors.sol"; /** * Contract responsible for managing the states and the updates of L2 network. diff --git a/contracts/PolygonZkEVMBridge.sol b/contracts/outdated/PolygonZkEVMBridge.sol similarity index 99% rename from contracts/PolygonZkEVMBridge.sol rename to contracts/outdated/PolygonZkEVMBridge.sol index 1892f6b26..796c112df 100644 --- a/contracts/PolygonZkEVMBridge.sol +++ b/contracts/outdated/PolygonZkEVMBridge.sol @@ -4,13 +4,13 @@ pragma solidity 0.8.20; import "./lib/DepositContract.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; -import "./lib/TokenWrapped.sol"; -import "./interfaces/IBasePolygonZkEVMGlobalExitRoot.sol"; -import "./interfaces/IBridgeMessageReceiver.sol"; -import "./interfaces/IPolygonZkEVMBridge.sol"; +import "../lib/TokenWrapped.sol"; +import "../interfaces/IBasePolygonZkEVMGlobalExitRoot.sol"; +import "../interfaces/IBridgeMessageReceiver.sol"; +import "../interfaces/IPolygonZkEVMBridge.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; -import "./lib/EmergencyManager.sol"; -import "./lib/GlobalExitRootLib.sol"; +import "../lib/EmergencyManager.sol"; +import "../lib/GlobalExitRootLib.sol"; /** * PolygonZkEVMBridge that will be deployed on both networks Ethereum and Polygon zkEVM diff --git a/contracts/PolygonZkEVMGlobalExitRoot.sol b/contracts/outdated/PolygonZkEVMGlobalExitRoot.sol similarity index 96% rename from contracts/PolygonZkEVMGlobalExitRoot.sol rename to contracts/outdated/PolygonZkEVMGlobalExitRoot.sol index af9e89498..1d5673613 100644 --- a/contracts/PolygonZkEVMGlobalExitRoot.sol +++ b/contracts/outdated/PolygonZkEVMGlobalExitRoot.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.20; -import "./interfaces/IPolygonZkEVMGlobalExitRoot.sol"; -import "./lib/GlobalExitRootLib.sol"; +import "../interfaces/IPolygonZkEVMGlobalExitRoot.sol"; +import "../lib/GlobalExitRootLib.sol"; /** * Contract responsible for managing the exit roots across multiple networks diff --git a/contracts/PolygonZkEVMGlobalExitRootL2.sol b/contracts/outdated/PolygonZkEVMGlobalExitRootL2.sol similarity index 95% rename from contracts/PolygonZkEVMGlobalExitRootL2.sol rename to contracts/outdated/PolygonZkEVMGlobalExitRootL2.sol index 1a7bbe33c..b4f00fac2 100644 --- a/contracts/PolygonZkEVMGlobalExitRootL2.sol +++ b/contracts/outdated/PolygonZkEVMGlobalExitRootL2.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity 0.8.20; -import "./interfaces/IBasePolygonZkEVMGlobalExitRoot.sol"; +import "../interfaces/IBasePolygonZkEVMGlobalExitRoot.sol"; /** * Contract responsible for managing the exit roots for the L2 and global exit roots diff --git a/contracts/lib/DepositContract.sol b/contracts/outdated/lib/DepositContract.sol similarity index 100% rename from contracts/lib/DepositContract.sol rename to contracts/outdated/lib/DepositContract.sol diff --git a/contracts/mainnetUpgraded/PolygonZkEVMUpgraded.sol b/contracts/outdated/mainnetUpgraded/PolygonZkEVMUpgraded.sol similarity index 100% rename from contracts/mainnetUpgraded/PolygonZkEVMUpgraded.sol rename to contracts/outdated/mainnetUpgraded/PolygonZkEVMUpgraded.sol diff --git a/contracts/outdated/mocks/DaiMock.sol.ignored b/contracts/outdated/mocks/DaiMock.sol.ignored new file mode 100644 index 000000000..4e284aa00 --- /dev/null +++ b/contracts/outdated/mocks/DaiMock.sol.ignored @@ -0,0 +1,171 @@ +/** + *Submitted for verification at Etherscan.io on 2019-11-14 + */ + +// hevm: flattened sources of /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/dai.sol +pragma solidity <=0.5.12; + +// Copeid DAi for testing porpuses. The notes are removed from this contract as it does not work correctly woith the +// coverage solidity compiler + +contract DaiMock { + // --- Auth --- + mapping(address => uint256) public wards; + + function rely(address guy) external auth { + wards[guy] = 1; + } + + function deny(address guy) external auth { + wards[guy] = 0; + } + + modifier auth() { + require(wards[msg.sender] == 1, "Dai/not-authorized"); + _; + } + + // --- ERC20 Data --- + string public constant name = "Dai Stablecoin"; + string public constant symbol = "DAI"; + string public constant version = "1"; + uint8 public constant decimals = 18; + uint256 public totalSupply; + + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + mapping(address => uint256) public nonces; + + event Approval(address indexed src, address indexed guy, uint256 wad); + event Transfer(address indexed src, address indexed dst, uint256 wad); + + // --- Math --- + function add(uint256 x, uint256 y) internal pure returns (uint256 z) { + require((z = x + y) >= x); + } + + function sub(uint256 x, uint256 y) internal pure returns (uint256 z) { + require((z = x - y) <= x); + } + + // --- EIP712 niceties --- + bytes32 public DOMAIN_SEPARATOR; + // bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)"); + bytes32 public constant PERMIT_TYPEHASH = + 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb; + + constructor(uint256 chainId_) public { + wards[msg.sender] = 1; + DOMAIN_SEPARATOR = keccak256( + abi.encode( + keccak256( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ), + keccak256(bytes(name)), + keccak256(bytes(version)), + chainId_, + address(this) + ) + ); + } + + // --- Token --- + function transfer(address dst, uint256 wad) external returns (bool) { + return transferFrom(msg.sender, dst, wad); + } + + function transferFrom( + address src, + address dst, + uint256 wad + ) public returns (bool) { + require(balanceOf[src] >= wad, "Dai/insufficient-balance"); + if (src != msg.sender && allowance[src][msg.sender] != uint256(-1)) { + require( + allowance[src][msg.sender] >= wad, + "Dai/insufficient-allowance" + ); + allowance[src][msg.sender] = sub(allowance[src][msg.sender], wad); + } + balanceOf[src] = sub(balanceOf[src], wad); + balanceOf[dst] = add(balanceOf[dst], wad); + emit Transfer(src, dst, wad); + return true; + } + + function mint(address usr, uint256 wad) external auth { + balanceOf[usr] = add(balanceOf[usr], wad); + totalSupply = add(totalSupply, wad); + emit Transfer(address(0), usr, wad); + } + + function burn(address usr, uint256 wad) external { + require(balanceOf[usr] >= wad, "Dai/insufficient-balance"); + if (usr != msg.sender && allowance[usr][msg.sender] != uint256(-1)) { + require( + allowance[usr][msg.sender] >= wad, + "Dai/insufficient-allowance" + ); + allowance[usr][msg.sender] = sub(allowance[usr][msg.sender], wad); + } + balanceOf[usr] = sub(balanceOf[usr], wad); + totalSupply = sub(totalSupply, wad); + emit Transfer(usr, address(0), wad); + } + + function approve(address usr, uint256 wad) external returns (bool) { + allowance[msg.sender][usr] = wad; + emit Approval(msg.sender, usr, wad); + return true; + } + + // --- Alias --- + function push(address usr, uint256 wad) external { + transferFrom(msg.sender, usr, wad); + } + + function pull(address usr, uint256 wad) external { + transferFrom(usr, msg.sender, wad); + } + + function move(address src, address dst, uint256 wad) external { + transferFrom(src, dst, wad); + } + + // --- Approve by signature --- + function permit( + address holder, + address spender, + uint256 nonce, + uint256 expiry, + bool allowed, + uint8 v, + bytes32 r, + bytes32 s + ) external { + bytes32 digest = keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + keccak256( + abi.encode( + PERMIT_TYPEHASH, + holder, + spender, + nonce, + expiry, + allowed + ) + ) + ) + ); + + require(holder != address(0), "Dai/invalid-address-0"); + require(holder == ecrecover(digest, v, r, s), "Dai/invalid-permit"); + require(expiry == 0 || now <= expiry, "Dai/permit-expired"); + require(nonce == nonces[holder]++, "Dai/invalid-nonce"); + uint256 wad = allowed ? uint256(-1) : 0; + allowance[holder][spender] = wad; + emit Approval(holder, spender, wad); + } +} diff --git a/contracts/mocks/DepositContractMock.sol b/contracts/outdated/mocks/DepositContractMock.sol similarity index 100% rename from contracts/mocks/DepositContractMock.sol rename to contracts/outdated/mocks/DepositContractMock.sol diff --git a/contracts/mocks/ERC20InvalidMetadata.sol b/contracts/outdated/mocks/ERC20InvalidMetadata.sol similarity index 100% rename from contracts/mocks/ERC20InvalidMetadata.sol rename to contracts/outdated/mocks/ERC20InvalidMetadata.sol diff --git a/contracts/mocks/ERC20WeirdMetadata.sol b/contracts/outdated/mocks/ERC20WeirdMetadata.sol similarity index 100% rename from contracts/mocks/ERC20WeirdMetadata.sol rename to contracts/outdated/mocks/ERC20WeirdMetadata.sol diff --git a/contracts/mocks/PolygonZkEVMBridgeMock.sol b/contracts/outdated/mocks/PolygonZkEVMBridgeMock.sol similarity index 100% rename from contracts/mocks/PolygonZkEVMBridgeMock.sol rename to contracts/outdated/mocks/PolygonZkEVMBridgeMock.sol diff --git a/contracts/mocks/PolygonZkEVMGlobalExitRootL2Mock.sol b/contracts/outdated/mocks/PolygonZkEVMGlobalExitRootL2Mock.sol similarity index 100% rename from contracts/mocks/PolygonZkEVMGlobalExitRootL2Mock.sol rename to contracts/outdated/mocks/PolygonZkEVMGlobalExitRootL2Mock.sol diff --git a/contracts/mocks/PolygonZkEVMGlobalExitRootMock.sol b/contracts/outdated/mocks/PolygonZkEVMGlobalExitRootMock.sol similarity index 100% rename from contracts/mocks/PolygonZkEVMGlobalExitRootMock.sol rename to contracts/outdated/mocks/PolygonZkEVMGlobalExitRootMock.sol diff --git a/contracts/mocks/PolygonZkEVMMock.sol b/contracts/outdated/mocks/PolygonZkEVMMock.sol similarity index 100% rename from contracts/mocks/PolygonZkEVMMock.sol rename to contracts/outdated/mocks/PolygonZkEVMMock.sol diff --git a/contracts/mocks/SequenceBatchesMock.sol b/contracts/outdated/mocks/SequenceBatchesMock.sol similarity index 100% rename from contracts/mocks/SequenceBatchesMock.sol rename to contracts/outdated/mocks/SequenceBatchesMock.sol diff --git a/contracts/outdated/mocks/Uni.sol.ignored b/contracts/outdated/mocks/Uni.sol.ignored new file mode 100644 index 000000000..052e77df6 --- /dev/null +++ b/contracts/outdated/mocks/Uni.sol.ignored @@ -0,0 +1,791 @@ +/** + *Submitted for verification at Etherscan.io on 2020-09-16 + */ + +/** + *Submitted for verification at Etherscan.io on 2020-09-15 + */ + +pragma solidity ^0.5.16; +pragma experimental ABIEncoderV2; + +// From https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/math/Math.sol +// Subject to the MIT license. + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, reverting on overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + /** + * @dev Returns the addition of two unsigned integers, reverting with custom message on overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * - Addition cannot overflow. + */ + function add( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, errorMessage); + + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on underflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * - Subtraction cannot underflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction underflow"); + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on underflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * - Subtraction cannot underflow. + */ + function sub( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * - Multiplication cannot overflow. + */ + function mul( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, errorMessage); + + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers. + * Reverts on division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + + /** + * @dev Returns the integer division of two unsigned integers. + * Reverts with custom message on division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function div( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + // Solidity only automatically asserts when dividing by 0 + require(b > 0, errorMessage); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, "SafeMath: modulo by zero"); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts with custom message when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function mod( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} + +contract Uni { + /// @notice EIP-20 token name for this token + string public constant name = "Uniswap"; + + /// @notice EIP-20 token symbol for this token + string public constant symbol = "UNI"; + + /// @notice EIP-20 token decimals for this token + uint8 public constant decimals = 18; + + /// @notice Total number of tokens in circulation + uint256 public totalSupply = 1_000_000_000e18; // 1 billion Uni + + /// @notice Address which may mint new tokens + address public minter; + + /// @notice The timestamp after which minting may occur + uint256 public mintingAllowedAfter; + + /// @notice Minimum time between mints + uint32 public constant minimumTimeBetweenMints = 1 days * 365; + + /// @notice Cap on the percentage of totalSupply that can be minted at each mint + uint8 public constant mintCap = 2; + + /// @notice Allowance amounts on behalf of others + mapping(address => mapping(address => uint96)) internal allowances; + + /// @notice Official record of token balances for each account + mapping(address => uint96) internal balances; + + /// @notice A record of each accounts delegate + mapping(address => address) public delegates; + + /// @notice A checkpoint for marking number of votes from a given block + struct Checkpoint { + uint32 fromBlock; + uint96 votes; + } + + /// @notice A record of votes checkpoints for each account, by index + mapping(address => mapping(uint32 => Checkpoint)) public checkpoints; + + /// @notice The number of checkpoints for each account + mapping(address => uint32) public numCheckpoints; + + /// @notice The EIP-712 typehash for the contract's domain + bytes32 public constant DOMAIN_TYPEHASH = + keccak256( + "EIP712Domain(string name,uint256 chainId,address verifyingContract)" + ); + + /// @notice The EIP-712 typehash for the delegation struct used by the contract + bytes32 public constant DELEGATION_TYPEHASH = + keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); + + /// @notice The EIP-712 typehash for the permit struct used by the contract + bytes32 public constant PERMIT_TYPEHASH = + keccak256( + "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" + ); + + /// @notice A record of states for signing / validating signatures + mapping(address => uint256) public nonces; + + /// @notice An event thats emitted when the minter address is changed + event MinterChanged(address minter, address newMinter); + + /// @notice An event thats emitted when an account changes its delegate + event DelegateChanged( + address indexed delegator, + address indexed fromDelegate, + address indexed toDelegate + ); + + /// @notice An event thats emitted when a delegate account's vote balance changes + event DelegateVotesChanged( + address indexed delegate, + uint256 previousBalance, + uint256 newBalance + ); + + /// @notice The standard EIP-20 transfer event + event Transfer(address indexed from, address indexed to, uint256 amount); + + /// @notice The standard EIP-20 approval event + event Approval( + address indexed owner, + address indexed spender, + uint256 amount + ); + + /** + * @notice Construct a new Uni token + * @param account The initial account to grant all the tokens + * @param minter_ The account with minting ability + * @param mintingAllowedAfter_ The timestamp after which minting may occur + */ + constructor( + address account, + address minter_, + uint256 mintingAllowedAfter_ + ) public { + require( + mintingAllowedAfter_ >= block.timestamp, + "Uni::constructor: minting can only begin after deployment" + ); + + balances[account] = uint96(totalSupply); + emit Transfer(address(0), account, totalSupply); + minter = minter_; + emit MinterChanged(address(0), minter); + mintingAllowedAfter = mintingAllowedAfter_; + } + + /** + * @notice Change the minter address + * @param minter_ The address of the new minter + */ + function setMinter(address minter_) external { + require( + msg.sender == minter, + "Uni::setMinter: only the minter can change the minter address" + ); + emit MinterChanged(minter, minter_); + minter = minter_; + } + + /** + * @notice Mint new tokens + * @param dst The address of the destination account + * @param rawAmount The number of tokens to be minted + */ + function mint(address dst, uint256 rawAmount) external { + require(msg.sender == minter, "Uni::mint: only the minter can mint"); + require( + block.timestamp >= mintingAllowedAfter, + "Uni::mint: minting not allowed yet" + ); + require( + dst != address(0), + "Uni::mint: cannot transfer to the zero address" + ); + + // record the mint + mintingAllowedAfter = SafeMath.add( + block.timestamp, + minimumTimeBetweenMints + ); + + // mint the amount + uint96 amount = safe96(rawAmount, "Uni::mint: amount exceeds 96 bits"); + require( + amount <= SafeMath.div(SafeMath.mul(totalSupply, mintCap), 100), + "Uni::mint: exceeded mint cap" + ); + totalSupply = safe96( + SafeMath.add(totalSupply, amount), + "Uni::mint: totalSupply exceeds 96 bits" + ); + + // transfer the amount to the recipient + balances[dst] = add96( + balances[dst], + amount, + "Uni::mint: transfer amount overflows" + ); + emit Transfer(address(0), dst, amount); + + // move delegates + _moveDelegates(address(0), delegates[dst], amount); + } + + /** + * @notice Get the number of tokens `spender` is approved to spend on behalf of `account` + * @param account The address of the account holding the funds + * @param spender The address of the account spending the funds + * @return The number of tokens approved + */ + function allowance( + address account, + address spender + ) external view returns (uint256) { + return allowances[account][spender]; + } + + /** + * @notice Approve `spender` to transfer up to `amount` from `src` + * @dev This will overwrite the approval amount for `spender` + * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve) + * @param spender The address of the account which may transfer tokens + * @param rawAmount The number of tokens that are approved (2^256-1 means infinite) + * @return Whether or not the approval succeeded + */ + function approve( + address spender, + uint256 rawAmount + ) external returns (bool) { + uint96 amount; + if (rawAmount == uint256(-1)) { + amount = uint96(-1); + } else { + amount = safe96(rawAmount, "Uni::approve: amount exceeds 96 bits"); + } + + allowances[msg.sender][spender] = amount; + + emit Approval(msg.sender, spender, amount); + return true; + } + + /** + * @notice Triggers an approval from owner to spends + * @param owner The address to approve from + * @param spender The address to be approved + * @param rawAmount The number of tokens that are approved (2^256-1 means infinite) + * @param deadline The time at which to expire the signature + * @param v The recovery byte of the signature + * @param r Half of the ECDSA signature pair + * @param s Half of the ECDSA signature pair + */ + function permit( + address owner, + address spender, + uint256 rawAmount, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external { + uint96 amount; + if (rawAmount == uint256(-1)) { + amount = uint96(-1); + } else { + amount = safe96(rawAmount, "Uni::permit: amount exceeds 96 bits"); + } + + bytes32 domainSeparator = keccak256( + abi.encode( + DOMAIN_TYPEHASH, + keccak256(bytes(name)), + getChainId(), + address(this) + ) + ); + bytes32 structHash = keccak256( + abi.encode( + PERMIT_TYPEHASH, + owner, + spender, + rawAmount, + nonces[owner]++, + deadline + ) + ); + bytes32 digest = keccak256( + abi.encodePacked("\x19\x01", domainSeparator, structHash) + ); + address signatory = ecrecover(digest, v, r, s); + require(signatory != address(0), "Uni::permit: invalid signature"); + require(signatory == owner, "Uni::permit: unauthorized"); + require(now <= deadline, "Uni::permit: signature expired"); + + allowances[owner][spender] = amount; + + emit Approval(owner, spender, amount); + } + + /** + * @notice Get the number of tokens held by the `account` + * @param account The address of the account to get the balance of + * @return The number of tokens held + */ + function balanceOf(address account) external view returns (uint256) { + return balances[account]; + } + + /** + * @notice Transfer `amount` tokens from `msg.sender` to `dst` + * @param dst The address of the destination account + * @param rawAmount The number of tokens to transfer + * @return Whether or not the transfer succeeded + */ + function transfer(address dst, uint256 rawAmount) external returns (bool) { + uint96 amount = safe96( + rawAmount, + "Uni::transfer: amount exceeds 96 bits" + ); + _transferTokens(msg.sender, dst, amount); + return true; + } + + /** + * @notice Transfer `amount` tokens from `src` to `dst` + * @param src The address of the source account + * @param dst The address of the destination account + * @param rawAmount The number of tokens to transfer + * @return Whether or not the transfer succeeded + */ + function transferFrom( + address src, + address dst, + uint256 rawAmount + ) external returns (bool) { + address spender = msg.sender; + uint96 spenderAllowance = allowances[src][spender]; + uint96 amount = safe96( + rawAmount, + "Uni::approve: amount exceeds 96 bits" + ); + + if (spender != src && spenderAllowance != uint96(-1)) { + uint96 newAllowance = sub96( + spenderAllowance, + amount, + "Uni::transferFrom: transfer amount exceeds spender allowance" + ); + allowances[src][spender] = newAllowance; + + emit Approval(src, spender, newAllowance); + } + + _transferTokens(src, dst, amount); + return true; + } + + /** + * @notice Delegate votes from `msg.sender` to `delegatee` + * @param delegatee The address to delegate votes to + */ + function delegate(address delegatee) public { + return _delegate(msg.sender, delegatee); + } + + /** + * @notice Delegates votes from signatory to `delegatee` + * @param delegatee The address to delegate votes to + * @param nonce The contract state required to match the signature + * @param expiry The time at which to expire the signature + * @param v The recovery byte of the signature + * @param r Half of the ECDSA signature pair + * @param s Half of the ECDSA signature pair + */ + function delegateBySig( + address delegatee, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) public { + bytes32 domainSeparator = keccak256( + abi.encode( + DOMAIN_TYPEHASH, + keccak256(bytes(name)), + getChainId(), + address(this) + ) + ); + bytes32 structHash = keccak256( + abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry) + ); + bytes32 digest = keccak256( + abi.encodePacked("\x19\x01", domainSeparator, structHash) + ); + address signatory = ecrecover(digest, v, r, s); + require( + signatory != address(0), + "Uni::delegateBySig: invalid signature" + ); + require( + nonce == nonces[signatory]++, + "Uni::delegateBySig: invalid nonce" + ); + require(now <= expiry, "Uni::delegateBySig: signature expired"); + return _delegate(signatory, delegatee); + } + + /** + * @notice Gets the current votes balance for `account` + * @param account The address to get votes balance + * @return The number of current votes for `account` + */ + function getCurrentVotes(address account) external view returns (uint96) { + uint32 nCheckpoints = numCheckpoints[account]; + return + nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0; + } + + /** + * @notice Determine the prior number of votes for an account as of a block number + * @dev Block number must be a finalized block or else this function will revert to prevent misinformation. + * @param account The address of the account to check + * @param blockNumber The block number to get the vote balance at + * @return The number of votes the account had as of the given block + */ + function getPriorVotes( + address account, + uint256 blockNumber + ) public view returns (uint96) { + require( + blockNumber < block.number, + "Uni::getPriorVotes: not yet determined" + ); + + uint32 nCheckpoints = numCheckpoints[account]; + if (nCheckpoints == 0) { + return 0; + } + + // First check most recent balance + if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) { + return checkpoints[account][nCheckpoints - 1].votes; + } + + // Next check implicit zero balance + if (checkpoints[account][0].fromBlock > blockNumber) { + return 0; + } + + uint32 lower = 0; + uint32 upper = nCheckpoints - 1; + while (upper > lower) { + uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow + Checkpoint memory cp = checkpoints[account][center]; + if (cp.fromBlock == blockNumber) { + return cp.votes; + } else if (cp.fromBlock < blockNumber) { + lower = center; + } else { + upper = center - 1; + } + } + return checkpoints[account][lower].votes; + } + + function _delegate(address delegator, address delegatee) internal { + address currentDelegate = delegates[delegator]; + uint96 delegatorBalance = balances[delegator]; + delegates[delegator] = delegatee; + + emit DelegateChanged(delegator, currentDelegate, delegatee); + + _moveDelegates(currentDelegate, delegatee, delegatorBalance); + } + + function _transferTokens(address src, address dst, uint96 amount) internal { + require( + src != address(0), + "Uni::_transferTokens: cannot transfer from the zero address" + ); + require( + dst != address(0), + "Uni::_transferTokens: cannot transfer to the zero address" + ); + + balances[src] = sub96( + balances[src], + amount, + "Uni::_transferTokens: transfer amount exceeds balance" + ); + balances[dst] = add96( + balances[dst], + amount, + "Uni::_transferTokens: transfer amount overflows" + ); + emit Transfer(src, dst, amount); + + _moveDelegates(delegates[src], delegates[dst], amount); + } + + function _moveDelegates( + address srcRep, + address dstRep, + uint96 amount + ) internal { + if (srcRep != dstRep && amount > 0) { + if (srcRep != address(0)) { + uint32 srcRepNum = numCheckpoints[srcRep]; + uint96 srcRepOld = srcRepNum > 0 + ? checkpoints[srcRep][srcRepNum - 1].votes + : 0; + uint96 srcRepNew = sub96( + srcRepOld, + amount, + "Uni::_moveVotes: vote amount underflows" + ); + _writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew); + } + + if (dstRep != address(0)) { + uint32 dstRepNum = numCheckpoints[dstRep]; + uint96 dstRepOld = dstRepNum > 0 + ? checkpoints[dstRep][dstRepNum - 1].votes + : 0; + uint96 dstRepNew = add96( + dstRepOld, + amount, + "Uni::_moveVotes: vote amount overflows" + ); + _writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew); + } + } + } + + function _writeCheckpoint( + address delegatee, + uint32 nCheckpoints, + uint96 oldVotes, + uint96 newVotes + ) internal { + uint32 blockNumber = safe32( + block.number, + "Uni::_writeCheckpoint: block number exceeds 32 bits" + ); + + if ( + nCheckpoints > 0 && + checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber + ) { + checkpoints[delegatee][nCheckpoints - 1].votes = newVotes; + } else { + checkpoints[delegatee][nCheckpoints] = Checkpoint( + blockNumber, + newVotes + ); + numCheckpoints[delegatee] = nCheckpoints + 1; + } + + emit DelegateVotesChanged(delegatee, oldVotes, newVotes); + } + + function safe32( + uint256 n, + string memory errorMessage + ) internal pure returns (uint32) { + require(n < 2 ** 32, errorMessage); + return uint32(n); + } + + function safe96( + uint256 n, + string memory errorMessage + ) internal pure returns (uint96) { + require(n < 2 ** 96, errorMessage); + return uint96(n); + } + + function add96( + uint96 a, + uint96 b, + string memory errorMessage + ) internal pure returns (uint96) { + uint96 c = a + b; + require(c >= a, errorMessage); + return c; + } + + function sub96( + uint96 a, + uint96 b, + string memory errorMessage + ) internal pure returns (uint96) { + require(b <= a, errorMessage); + return a - b; + } + + function getChainId() internal pure returns (uint256) { + uint256 chainId; + assembly { + chainId := chainid() + } + return chainId; + } +} diff --git a/contracts/testnet/PolygonZkEVMTestnetClearStorage.sol b/contracts/outdated/testnet/PolygonZkEVMTestnetClearStorage.sol similarity index 100% rename from contracts/testnet/PolygonZkEVMTestnetClearStorage.sol rename to contracts/outdated/testnet/PolygonZkEVMTestnetClearStorage.sol diff --git a/contracts/testnet/PolygonZkEVMTestnetV2.sol b/contracts/outdated/testnet/PolygonZkEVMTestnetV2.sol similarity index 100% rename from contracts/testnet/PolygonZkEVMTestnetV2.sol rename to contracts/outdated/testnet/PolygonZkEVMTestnetV2.sol diff --git a/contracts/v2/previousVersions/IPolygonRollupBasePrevious.sol b/contracts/previousVersions/IPolygonRollupBasePrevious.sol similarity index 100% rename from contracts/v2/previousVersions/IPolygonRollupBasePrevious.sol rename to contracts/previousVersions/IPolygonRollupBasePrevious.sol diff --git a/contracts/v2/previousVersions/PolygonRollupBaseEtrogPrevious.sol b/contracts/previousVersions/PolygonRollupBaseEtrogPrevious.sol similarity index 99% rename from contracts/v2/previousVersions/PolygonRollupBaseEtrogPrevious.sol rename to contracts/previousVersions/PolygonRollupBaseEtrogPrevious.sol index f9422cec1..237dd8c3e 100644 --- a/contracts/v2/previousVersions/PolygonRollupBaseEtrogPrevious.sol +++ b/contracts/previousVersions/PolygonRollupBaseEtrogPrevious.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.20; import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; import "../interfaces/IPolygonZkEVMGlobalExitRootV2.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import "../../interfaces/IPolygonZkEVMErrors.sol"; +import "../interfaces/IPolygonZkEVMErrors.sol"; import "../interfaces/IPolygonZkEVMVEtrogErrors.sol"; import "../PolygonRollupManager.sol"; import "./IPolygonRollupBasePrevious.sol"; diff --git a/contracts/v2/previousVersions/PolygonRollupManagerPrevious.sol b/contracts/previousVersions/PolygonRollupManagerPrevious.sol similarity index 99% rename from contracts/v2/previousVersions/PolygonRollupManagerPrevious.sol rename to contracts/previousVersions/PolygonRollupManagerPrevious.sol index c18c2c67b..9c3c9b0cf 100644 --- a/contracts/v2/previousVersions/PolygonRollupManagerPrevious.sol +++ b/contracts/previousVersions/PolygonRollupManagerPrevious.sol @@ -4,10 +4,10 @@ pragma solidity 0.8.20; import "../interfaces/IPolygonRollupManager.sol"; import "../interfaces/IPolygonZkEVMGlobalExitRootV2.sol"; -import "../../interfaces/IPolygonZkEVMBridge.sol"; +import "../interfaces/IPolygonZkEVMBridge.sol"; import "../interfaces/IPolygonRollupBase.sol"; -import "../../interfaces/IVerifierRollup.sol"; -import "../../lib/EmergencyManager.sol"; +import "../interfaces/IVerifierRollup.sol"; +import "../lib/EmergencyManager.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; import "../lib/PolygonTransparentProxy.sol"; import "../lib/PolygonAccessControlUpgradeable.sol"; diff --git a/contracts/v2/previousVersions/PolygonValidiumEtrogPrevious.sol b/contracts/previousVersions/PolygonValidiumEtrogPrevious.sol similarity index 100% rename from contracts/v2/previousVersions/PolygonValidiumEtrogPrevious.sol rename to contracts/previousVersions/PolygonValidiumEtrogPrevious.sol diff --git a/contracts/v2/previousVersions/PolygonZkEVMEtrogPrevious.sol b/contracts/previousVersions/PolygonZkEVMEtrogPrevious.sol similarity index 100% rename from contracts/v2/previousVersions/PolygonZkEVMEtrogPrevious.sol rename to contracts/previousVersions/PolygonZkEVMEtrogPrevious.sol diff --git a/contracts/v2/utils/ClaimCompressor.sol b/contracts/utils/ClaimCompressor.sol similarity index 99% rename from contracts/v2/utils/ClaimCompressor.sol rename to contracts/utils/ClaimCompressor.sol index 25a75ecf3..1a8b8f7be 100644 --- a/contracts/v2/utils/ClaimCompressor.sol +++ b/contracts/utils/ClaimCompressor.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0 -import "../PolygonZkEVMBridgeV2.sol"; +import "../interfaces/IPolygonZkEVMBridgeV2.sol"; import "hardhat/console.sol"; pragma solidity 0.8.20; @@ -15,10 +15,10 @@ contract ClaimCompressor { uint256 private constant _GLOBAL_INDEX_MAINNET_FLAG = 2 ** 64; bytes4 private constant _CLAIM_ASSET_SIGNATURE = - PolygonZkEVMBridgeV2.claimAsset.selector; + IPolygonZkEVMBridgeV2.claimAsset.selector; bytes4 private constant _CLAIM_MESSAGE_SIGNATURE = - PolygonZkEVMBridgeV2.claimMessage.selector; + IPolygonZkEVMBridgeV2.claimMessage.selector; // Bytes that will be added to the snark input for every rollup aggregated // 4 bytes signature diff --git a/deployment/v2/verifyContracts.js b/deployment/v2/verifyContracts.js index 32b4b6762..cb97dd949 100644 --- a/deployment/v2/verifyContracts.js +++ b/deployment/v2/verifyContracts.js @@ -141,7 +141,7 @@ async function main() { await hre.run( 'verify:verify', { - contract: 'contracts/v2/lib/PolygonTransparentProxy.sol:PolygonTransparentProxy', + contract: 'contracts/lib/PolygonTransparentProxy.sol:PolygonTransparentProxy', address: createRollupOutputParameters.rollupAddress, constructorArguments: [ await upgrades.erc1967.getImplementationAddress(createRollupOutputParameters.rollupAddress), @@ -173,7 +173,7 @@ async function main() { await hre.run( 'verify:verify', { - contract: 'contracts/v2/consensus/zkEVM/PolygonZkEVMEtrog.sol:PolygonZkEVMEtrog', + contract: 'contracts/consensus/zkEVM/PolygonZkEVMEtrog.sol:PolygonZkEVMEtrog', address: createRollupOutputParameters.rollupAddress, constructorArguments: [ deployOutputParameters.polygonZkEVMGlobalExitRootAddress, @@ -191,7 +191,7 @@ async function main() { await hre.run( 'verify:verify', { - contract: 'contracts/v2/consensus/validium/PolygonValidiumEtrog.sol:PolygonValidiumEtrog', + contract: 'contracts/consensus/validium/PolygonValidiumEtrog.sol:PolygonValidiumEtrog', address: createRollupOutputParameters.rollupAddress, constructorArguments: [ deployOutputParameters.polygonZkEVMGlobalExitRootAddress, diff --git a/docker/scripts/v1ToV2/hardhat.example.paris b/docker/scripts/v1ToV2/hardhat.example.paris index 4bb241106..448b68f5b 100644 --- a/docker/scripts/v1ToV2/hardhat.example.paris +++ b/docker/scripts/v1ToV2/hardhat.example.paris @@ -75,7 +75,7 @@ const config: HardhatUserConfig = { }, ], overrides: { - "contracts/v2/PolygonRollupManager.sol": { + "contracts/PolygonRollupManager.sol": { version: "0.8.20", settings: { optimizer: { @@ -85,7 +85,7 @@ const config: HardhatUserConfig = { evmVersion: "paris", }, // try yul optimizer }, - "contracts/v2/PolygonZkEVMBridgeV2.sol": { + "contracts/PolygonZkEVMBridgeV2.sol": { version: "0.8.20", settings: { optimizer: { @@ -105,7 +105,7 @@ const config: HardhatUserConfig = { evmVersion: "paris", }, }, - "contracts/v2/mocks/PolygonRollupManagerMock.sol": { + "contracts/mocks/PolygonRollupManagerMock.sol": { version: "0.8.20", settings: { optimizer: { @@ -116,7 +116,7 @@ const config: HardhatUserConfig = { }, // try yul optimizer }, // Should have the same optimizations than the RollupManager to verify - "contracts/v2/lib/PolygonTransparentProxy.sol": { + "contracts/lib/PolygonTransparentProxy.sol": { version: "0.8.20", settings: { optimizer: { @@ -126,7 +126,7 @@ const config: HardhatUserConfig = { evmVersion: "paris", }, // try yul optimizer }, - "contracts/v2/newDeployments/PolygonRollupManagerNotUpgraded.sol": { + "contracts/newDeployments/PolygonRollupManagerNotUpgraded.sol": { version: "0.8.20", settings: { optimizer: { diff --git a/docker/scripts/v2/hardhat.example.paris b/docker/scripts/v2/hardhat.example.paris index 4bb241106..448b68f5b 100644 --- a/docker/scripts/v2/hardhat.example.paris +++ b/docker/scripts/v2/hardhat.example.paris @@ -75,7 +75,7 @@ const config: HardhatUserConfig = { }, ], overrides: { - "contracts/v2/PolygonRollupManager.sol": { + "contracts/PolygonRollupManager.sol": { version: "0.8.20", settings: { optimizer: { @@ -85,7 +85,7 @@ const config: HardhatUserConfig = { evmVersion: "paris", }, // try yul optimizer }, - "contracts/v2/PolygonZkEVMBridgeV2.sol": { + "contracts/PolygonZkEVMBridgeV2.sol": { version: "0.8.20", settings: { optimizer: { @@ -105,7 +105,7 @@ const config: HardhatUserConfig = { evmVersion: "paris", }, }, - "contracts/v2/mocks/PolygonRollupManagerMock.sol": { + "contracts/mocks/PolygonRollupManagerMock.sol": { version: "0.8.20", settings: { optimizer: { @@ -116,7 +116,7 @@ const config: HardhatUserConfig = { }, // try yul optimizer }, // Should have the same optimizations than the RollupManager to verify - "contracts/v2/lib/PolygonTransparentProxy.sol": { + "contracts/lib/PolygonTransparentProxy.sol": { version: "0.8.20", settings: { optimizer: { @@ -126,7 +126,7 @@ const config: HardhatUserConfig = { evmVersion: "paris", }, // try yul optimizer }, - "contracts/v2/newDeployments/PolygonRollupManagerNotUpgraded.sol": { + "contracts/newDeployments/PolygonRollupManagerNotUpgraded.sol": { version: "0.8.20", settings: { optimizer: { diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 000000000..f1898e7af --- /dev/null +++ b/foundry.toml @@ -0,0 +1,48 @@ +[profile.default] +src = "contracts" +out = "out" +libs = ["lib"] +optimizer = true +optimizer_runs = 999999 +#via_ir = true +verbosity = 2 +ffi = true +fs_permissions = [{ access = "read-write", path = "./script/"}] +evm_version = "shanghai" + +remappings = [ + "@ensdomains/=node_modules/@ensdomains/", + "@openzeppelin/=node_modules/@openzeppelin/", + "eth-gas-reporter/=node_modules/eth-gas-reporter/", + "hardhat/=node_modules/hardhat/", + "forge-std/=lib/forge-std/src/", +] + +[profile.intense.fuzz] +runs = 10000 +max_test_rejects = 999999 + +[fmt] +line_length = 160 +number_underscore = "thousands" + +[rpc_endpoints] +anvil = "http://127.0.0.1:8545" +mainnet = "https://mainnet.infura.io/v3/${INFURA_KEY}" +goerli = "https://goerli.infura.io/v3/${INFURA_KEY}" +sepolia = "https://sepolia.infura.io/v3/${INFURA_KEY}" +polygon = "https://polygon-mainnet.infura.io/v3/${INFURA_KEY}" +mumbai = "https://polygon-mumbai.infura.io/v3/${INFURA_KEY}" +polygon_zkevm = "https://zkevm-rpc.com" +polygon_zkevm_testnet = "https://rpc.public.zkevm-test.net" + +[etherscan] +mainnet = { key = "${ETHERSCAN_API_KEY}" } +goerli = { key = "${ETHERSCAN_API_KEY}" } +sepolia = { key = "${ETHERSCAN_API_KEY}" } +polygon = { key = "${POLYGONSCAN_API_KEY}" } +mumbai = { key = "${POLYGONSCAN_API_KEY}" } +polygon_zkevm = { key = "${POLYGONSCAN_ZKEVM_API_KEY}" } +polygon_zkevm_testnet = { key = "${POLYGONSCAN_ZKEVM_API_KEY}" } + +# See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index 3cf06de28..90dd64692 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -5,6 +5,8 @@ import "hardhat-dependency-compiler"; import {HardhatUserConfig} from "hardhat/config"; +import "./tasks/compile"; + const DEFAULT_MNEMONIC = "test test test test test test test test test test test junk"; /* @@ -75,7 +77,7 @@ const config: HardhatUserConfig = { }, ], overrides: { - "contracts/v2/PolygonRollupManager.sol": { + "contracts/PolygonRollupManager.sol": { version: "0.8.20", settings: { optimizer: { @@ -85,7 +87,7 @@ const config: HardhatUserConfig = { evmVersion: "shanghai", }, // try yul optimizer }, - "contracts/v2/PolygonZkEVMBridgeV2.sol": { + "contracts/PolygonZkEVMBridgeV2.sol": { version: "0.8.20", settings: { optimizer: { @@ -95,7 +97,7 @@ const config: HardhatUserConfig = { evmVersion: "shanghai", }, }, - "contracts/v2/newDeployments/PolygonRollupManagerNotUpgraded.sol": { + "contracts/newDeployments/PolygonRollupManagerNotUpgraded.sol": { version: "0.8.20", settings: { optimizer: { @@ -105,7 +107,7 @@ const config: HardhatUserConfig = { evmVersion: "shanghai", }, // try yul optimizer }, - "contracts/v2/mocks/PolygonRollupManagerMock.sol": { + "contracts/mocks/PolygonRollupManagerMock.sol": { version: "0.8.20", settings: { optimizer: { @@ -116,7 +118,7 @@ const config: HardhatUserConfig = { }, // try yul optimizer }, // Should have the same optimizations than the RollupManager to verify - "contracts/v2/lib/PolygonTransparentProxy.sol": { + "contracts/lib/PolygonTransparentProxy.sol": { version: "0.8.20", settings: { optimizer: { @@ -126,7 +128,7 @@ const config: HardhatUserConfig = { evmVersion: "shanghai", }, // try yul optimizer }, - "contracts/v2/utils/ClaimCompressor.sol": { + "contracts/utils/ClaimCompressor.sol": { version: "0.8.20", settings: { optimizer: { diff --git a/lib/deployer-kit b/lib/deployer-kit new file mode 160000 index 000000000..5ed82de05 --- /dev/null +++ b/lib/deployer-kit @@ -0,0 +1 @@ +Subproject commit 5ed82de055ed1a96943d9958e9ba4301b41829d9 diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 000000000..978ac6fad --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 978ac6fadb62f5f0b723c996f64be52eddba6801 diff --git a/lib/solmate b/lib/solmate new file mode 160000 index 000000000..97bdb2003 --- /dev/null +++ b/lib/solmate @@ -0,0 +1 @@ +Subproject commit 97bdb2003b70382996a79a406813f76417b1cf90 diff --git a/script/ClaimCompressor.s.sol b/script/ClaimCompressor.s.sol new file mode 100644 index 000000000..901e9c4c8 --- /dev/null +++ b/script/ClaimCompressor.s.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Script.sol"; +import "script/deployers/ClaimCompressorDeployer.s.sol"; + +contract Deploy is Script, ClaimCompressorDeployer { + function run() public { + address _bridgeAddress = makeAddr("bridgeAddress"); + uint32 _networkID = 1; + + address implementation = deployClaimCompressorImplementation( + _bridgeAddress, + _networkID + ); + console.log("ClaimCompressor deployed at: ", implementation); + } +} diff --git a/script/CreateGenesis.s.sol b/script/CreateGenesis.s.sol new file mode 100644 index 000000000..0e5cd1545 --- /dev/null +++ b/script/CreateGenesis.s.sol @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Script.sol"; + +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import {PolygonRollupManagerNotUpgraded} from "contracts/newDeployments/PolygonRollupManagerNotUpgraded.sol"; +import {PolygonZkEVMBridgeV2} from "contracts-ignored-originals/PolygonZkEVMBridgeV2.sol"; +import {PolygonZkEVMGlobalExitRootV2} from "contracts/PolygonZkEVMGlobalExitRootV2.sol"; +import {PolygonZkEVMDeployer} from "contracts/deployment/PolygonZkEVMDeployer.sol"; +import {PolygonZkEVMTimelock} from "contracts/PolygonZkEVMTimelock.sol"; + +contract CreateGenesis is Script { + using stdJson for string; + + struct ContractInfo { + string name; + address addr; + } + + struct Slot { + bytes32 slot; + bytes32 value; + } + + string constant OUTPUT_DIR = "script/"; + string constant OUTPUT_FILENAME = "genesis.json"; + bytes32 constant ADMIN_SLOT = + 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + bytes32 constant IMPLEMENTATION_SLOT = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + bytes32 constant EMPTY_SLOT_VALUE = + 0x0000000000000000000000000000000000000000000000000000000000000000; + uint256 constant BALANCE_BRIDGE = 0xffffffffffffffffffffffffffffffff; + uint256 constant BALANCE_DEPLOYER = 0x152d02c7e14af6800000; + bytes16 private constant HEX_SYMBOLS = "0123456789abcdef"; + + address deployerAddr; + address timelockAdminAddress; + bool isTest; + + string[] internal contractNames = [ + "PolygonZkEVMDeployer", + "ProxyAdmin", + "PolygonZkEVMBridgeImplementation", + "PolygonZkEVMBridgeProxy", + "PolygonZkEVMGlobalExitRootL2Implementation", + "PolygonZkEVMGlobalExitRootL2Proxy", + "PolygonZkEVMTimelock" + ]; + + bytes32[] internal timelockRoles = [ + keccak256("TIMELOCK_ADMIN_ROLE"), + keccak256("PROPOSER_ROLE"), + keccak256("EXECUTOR_ROLE"), + keccak256("CANCELLER_ROLE") + ]; + + ContractInfo[] internal contracts; + Slot[] internal contractSlots; + + function run() public { + require(vm.isDir(OUTPUT_DIR), "Output directory does not exist"); + string memory outPath = string.concat(OUTPUT_DIR, OUTPUT_FILENAME); + + loadConfig(); + + string memory gensis = generateGenesisJson(); + bytes32 stateRoot = _calculateStateRoot(_wrapJson(gensis)); + string memory finalGenesis = _insertStateRoot(gensis, stateRoot); + + vm.writeFile(outPath, finalGenesis); + console.log("Genesis file created at path: %s\n", outPath); + } + + function loadConfig() public { + string memory inputPath = "script/inputs/genesisInput.json"; + console.log("Reading config from path: %s \n", inputPath); + + string memory input = vm.readFile(inputPath); + for (uint256 i = 0; i < contractNames.length; i++) { + ContractInfo memory currentContract = ContractInfo({ + name: contractNames[i], + addr: input.readAddress(string.concat(".", contractNames[i])) + }); + contracts.push(currentContract); + console.log( + "%s's address: %s", + currentContract.name, + currentContract.addr + ); + } + deployerAddr = input.readAddress(".Deployer"); + console.log("Deployer's address: %s", deployerAddr); + + timelockAdminAddress = input.readAddress(".timelockAdminAddress"); + console.log("Timelock Admin's address: %s", timelockAdminAddress); + + isTest = input.readBool(".isTest"); + console.log("Is test: %s", isTest); + console.log("Config loaded successfully!\n"); + } + + function generateGenesisJson() public returns (string memory) { + string memory finalOutput = '"genesis": ['; + for (uint256 i = 0; i < contracts.length; i++) { + uint256 balance = 0; + if ( + keccak256(abi.encodePacked(contracts[i].name)) == + keccak256(abi.encodePacked("PolygonZkEVMBridgeProxy")) + ) { + balance = BALANCE_BRIDGE; + } + string memory contractOutput = _generateContractGenesisInfo( + contracts[i].name, + balance, + contracts[i].addr + ); + finalOutput = string.concat(finalOutput, contractOutput); + if (i != contracts.length - 1) { + finalOutput = string.concat(finalOutput, ","); + } + } + finalOutput = string.concat(finalOutput, ","); + string memory deployerOutput = _getDeployerInfo(); + finalOutput = string.concat(finalOutput, deployerOutput); + finalOutput = string.concat(finalOutput, "]"); + return finalOutput; + } + + function _getContractSlots(address contractAddr) internal { + // try to get 200 slots + uint256 n = 200; + for (uint256 i = 0; i < n; i++) { + bytes32 currentSlot = bytes32(uint256(i)); + bytes32 currentSlotValue = vm.load(contractAddr, currentSlot); + if (currentSlotValue != EMPTY_SLOT_VALUE) { + contractSlots.push( + Slot({slot: currentSlot, value: currentSlotValue}) + ); + } + } + } + + function _generateContractGenesisInfo( + string memory contractName, + uint256 balance, + address contractAddr + ) internal returns (string memory) { + string memory contractObj = contractName; + + bytes memory runtimeCode = contractAddr.code; + require(runtimeCode.length > 0, "Contract runtime code is empty"); + vm.serializeBytes(contractObj, "bytecode", runtimeCode); + + vm.serializeString(contractObj, "contractName", contractName); + vm.serializeString(contractObj, "balance", _toHexString(balance)); + vm.serializeString( + contractObj, + "nonce", + _toHexString(vm.getNonce(contractAddr)) + ); + vm.serializeAddress(contractObj, "address", contractAddr); + + _getContractSlots(contractAddr); + + // Special handling for PolygonZkEVMTimelock + if ( + keccak256(abi.encodePacked(contractName)) != + keccak256(abi.encodePacked("PolygonZkEVMTimelock")) + ) { + bytes32 adminSlotValue = vm.load(contractAddr, ADMIN_SLOT); + if (adminSlotValue != EMPTY_SLOT_VALUE) { + contractSlots.push( + Slot({slot: ADMIN_SLOT, value: adminSlotValue}) + ); + } + bytes32 implementationSlotValue = vm.load( + contractAddr, + IMPLEMENTATION_SLOT + ); + if (implementationSlotValue != EMPTY_SLOT_VALUE) { + contractSlots.push( + Slot({ + slot: IMPLEMENTATION_SLOT, + value: implementationSlotValue + }) + ); + } + } else { + for (uint256 i = 0; i < timelockRoles.length; i++) { + uint256 rolesMappingStoragePositionStruct = 0; + bytes32 storagePosition = keccak256( + abi.encodePacked( + timelockRoles[i], + rolesMappingStoragePositionStruct + ) + ); + + address[] memory addressArray = new address[](2); + addressArray[0] = timelockAdminAddress; + addressArray[1] = contractAddr; + for (uint256 j = 0; j < addressArray.length; j++) { + bytes32 storagePositionRole = keccak256( + abi.encodePacked( + uint256(uint160(addressArray[j])), + storagePosition + ) + ); + bytes32 valueRole = vm.load( + contractAddr, + storagePositionRole + ); + if (valueRole != EMPTY_SLOT_VALUE) { + contractSlots.push( + Slot({slot: storagePositionRole, value: valueRole}) + ); + } + } + bytes32 roleAdminSlot = bytes32(uint256(storagePosition) + 1); // shift by 1 to get the next slot + bytes32 valueRoleAdminSlot = vm.load( + contractAddr, + roleAdminSlot + ); + if (valueRoleAdminSlot != EMPTY_SLOT_VALUE) { + contractSlots.push( + Slot({slot: roleAdminSlot, value: valueRoleAdminSlot}) + ); + } + } + } + + string memory slotsObj = string.concat("storage slots:", contractName); + string memory slotsOutput; + for (uint256 i = 0; i < contractSlots.length; i++) { + slotsOutput = vm.serializeBytes32( + slotsObj, + vm.toString(contractSlots[i].slot), + contractSlots[i].value + ); + } + // reset contractSlots for next contract + delete contractSlots; + return vm.serializeString(contractObj, "storage", slotsOutput); + } + + function _getDeployerInfo() internal returns (string memory) { + uint256 balance = 0; + if (isTest) { + balance = BALANCE_DEPLOYER; + } + string memory deployerObj = "deployer info"; + vm.serializeString(deployerObj, "accountName", "deployer"); + vm.serializeString( + deployerObj, + "nonce", + _toHexString(vm.getNonce(deployerAddr)) + ); + vm.serializeString(deployerObj, "balance", _toHexString(balance)); + return vm.serializeAddress(deployerObj, "address", deployerAddr); + } + + function _calculateStateRoot( + string memory genesis + ) public returns (bytes32) { + string[] memory operation = new string[](4); + operation[0] = "node"; + operation[1] = "tools/zkevm-commonjs-wrapper"; + operation[2] = "calculateRoot"; + operation[3] = genesis; + + bytes memory result = vm.ffi(operation); + return abi.decode(result, (bytes32)); + } + + function _toHexString(uint256 value) internal pure returns (string memory) { + // Determine the length of the hex string + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp >>= 4; + } + + // Handle case when value is zero + if (digits == 0) return "0x00"; + + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits--; + buffer[digits] = HEX_SYMBOLS[value & 15]; + value >>= 4; + } + + // Add '0x' prefix + return string(abi.encodePacked("0x", buffer)); + } + + function _wrapJson( + string memory json + ) internal pure returns (string memory) { + return string.concat("{", json, "}"); + } + + function _insertStateRoot( + string memory json, + bytes32 stateRoot + ) internal pure returns (string memory) { + return + string.concat( + "{", + '"root":"', + vm.toString(stateRoot), + '",', + json, + "}" + ); + } +} diff --git a/script/DeployContracts.s.sol b/script/DeployContracts.s.sol new file mode 100644 index 000000000..a7025d84e --- /dev/null +++ b/script/DeployContracts.s.sol @@ -0,0 +1,429 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Script.sol"; + +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +import {PolygonZkEVM} from "contracts/outdated/PolygonZkEVM.sol"; +import {PolygonRollupManagerNotUpgraded} from "contracts/newDeployments/PolygonRollupManagerNotUpgraded.sol"; +import {PolygonZkEVMBridgeV2} from "contracts-ignored-originals/PolygonZkEVMBridgeV2.sol"; +import {PolygonZkEVMGlobalExitRootV2} from "contracts/PolygonZkEVMGlobalExitRootV2.sol"; +import {PolygonZkEVMDeployer} from "contracts/deployment/PolygonZkEVMDeployer.sol"; +import {PolygonZkEVMTimelock} from "contracts/PolygonZkEVMTimelock.sol"; + +import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import {IPolygonZkEVMBridge} from "contracts/interfaces/IPolygonZkEVMBridge.sol"; +import {IPolygonZkEVMGlobalExitRootV2} from "contracts/interfaces/IPolygonZkEVMGlobalExitRootV2.sol"; + +contract DeployContracts is Script { + using stdJson for string; + + bytes32 constant DEFAULT_ADMIN_ROLE = bytes32(0); + bytes32 constant ADD_ROLLUP_TYPE_ROLE = keccak256("ADD_ROLLUP_TYPE_ROLE"); + bytes32 constant OBSOLETE_ROLLUP_TYPE_ROLE = + keccak256("OBSOLETE_ROLLUP_TYPE_ROLE"); + bytes32 constant CREATE_ROLLUP_ROLE = keccak256("CREATE_ROLLUP_ROLE"); + bytes32 constant ADD_EXISTING_ROLLUP_ROLE = + keccak256("ADD_EXISTING_ROLLUP_ROLE"); + bytes32 constant UPDATE_ROLLUP_ROLE = keccak256("UPDATE_ROLLUP_ROLE"); + bytes32 constant TRUSTED_AGGREGATOR_ROLE = + keccak256("TRUSTED_AGGREGATOR_ROLE"); + bytes32 constant TRUSTED_AGGREGATOR_ROLE_ADMIN = + keccak256("TRUSTED_AGGREGATOR_ROLE_ADMIN"); + bytes32 constant TWEAK_PARAMETERS_ROLE = keccak256("TWEAK_PARAMETERS_ROLE"); + bytes32 constant SET_FEE_ROLE = keccak256("SET_FEE_ROLE"); + bytes32 constant STOP_EMERGENCY_ROLE = keccak256("STOP_EMERGENCY_ROLE"); + bytes32 constant EMERGENCY_COUNCIL_ROLE = + keccak256("EMERGENCY_COUNCIL_ROLE"); + bytes32 constant EMERGENCY_COUNCIL_ADMIN = + keccak256("EMERGENCY_COUNCIL_ADMIN"); + + address constant GAS_TOKEN_ADDR_MAINNET = address(0); + uint256 constant NETWORK_ID_MAINNET = 0; + uint256 constant GAS_TOKEN_NETWORK_MAINNET = 0; + bytes constant GAS_TOKEN_METADATA = bytes(""); + + // config parameters + address admin; + address emergencyCouncilAddress; + address gasTokenAddress; + address polTokenAddress; + address timelockAdminAddress; + address trustedAggregator; + address zkEVMDeployerAddress; + uint256 deployerPvtKey; + uint256 gasTokenNetwork; + uint256 minDelayTimelock; + uint256 pendingStateTimeout; + uint256 trustedAggregatorTimeout; + bytes32 salt; + + address computedRollupManagerAddress; + address computedGlobalExitRootManagerAddress; + + PolygonZkEVMDeployer zkevmDeployer; + + function run() public { + loadConfig(); + _computeDeployAddresses(); + address proxyAdminAddr = _deployProxyAdmin(); + address bridgeImplementation = _deployBridgeImplementation(); + + _deployTimelock(proxyAdminAddr, computedRollupManagerAddress); + + address bridgeProxy = _deployBridgeProxy( + bridgeImplementation, + computedGlobalExitRootManagerAddress, + computedRollupManagerAddress, + proxyAdminAddr + ); + + address globalExitRootManager = _deployGlobalExitRootManager( + computedRollupManagerAddress, + bridgeProxy, + proxyAdminAddr + ); + address rolluplManagerAddr = _deployRollupManager( + globalExitRootManager, + bridgeProxy, + proxyAdminAddr + ); + _verifyRollupManager(rolluplManagerAddr, bridgeProxy); + } + + function loadConfig() public { + string memory inputPath = "script/inputs/deployParameters.json"; + console.log("Reading config from path: %s \n", inputPath); + + string memory input = vm.readFile(inputPath); + + admin = input.readAddress(".admin"); + emergencyCouncilAddress = input.readAddress(".emergencyCouncilAddress"); + gasTokenAddress = input.readAddress(".gasTokenAddress"); + polTokenAddress = input.readAddress(".polTokenAddress"); + timelockAdminAddress = input.readAddress(".timelockAdminAddress"); + trustedAggregator = input.readAddress(".trustedAggregator"); + zkEVMDeployerAddress = input.readAddress(".zkEVMDeployerAddress"); + deployerPvtKey = input.readUint(".deployerPvtKey"); + gasTokenNetwork = input.readUint(".gasTokenNetwork"); + minDelayTimelock = input.readUint(".minDelayTimelock"); + pendingStateTimeout = input.readUint(".pendingStateTimeout"); + trustedAggregatorTimeout = input.readUint(".trustedAggregatorTimeout"); + salt = input.readBytes32(".salt"); + + zkevmDeployer = PolygonZkEVMDeployer(zkEVMDeployerAddress); + + console.log("Admin Address: %s", address(admin)); + console.log( + "Emergency Council Address: %s", + address(emergencyCouncilAddress) + ); + console.log("Gas Token Address: %s", address(gasTokenAddress)); + console.log("Pol Token Address: %s", address(polTokenAddress)); + console.log( + "Timelock Admin Address: %s", + address(timelockAdminAddress) + ); + console.log( + "Trusted Aggregator Address: %s", + address(trustedAggregator) + ); + console.log( + "ZkEVM Deployer Address: %s", + address(zkEVMDeployerAddress) + ); + console.log("Deployer Private Key: %s", deployerPvtKey); + console.log("Gas Token Network: %s", gasTokenNetwork); + console.log("Min Delay Timelock: %s", minDelayTimelock); + console.log("Pending State Timeout: %s", pendingStateTimeout); + console.log("Trusted Aggregator Timeout: %s", trustedAggregatorTimeout); + console.log("salt: %s", vm.toString(salt)); + } + + function _deployProxyAdmin() internal returns (address) { + bytes memory bytecode = type(ProxyAdmin).creationCode; + address proxyAdminAddr = vm.computeCreate2Address( + salt, + keccak256(bytecode), + address(zkevmDeployer) + ); + + // check if there is already a ProxyAdmin deployed at the address + if (proxyAdminAddr.code.length > 0) { + console.log("\n----------------------\n"); + console.log( + "ProxyAdmin already deployed at address: %s", + proxyAdminAddr + ); + return proxyAdminAddr; + } + + // if not, deploy ProxyAdmin and transfer ownership to deployer + bytes memory callData = abi.encodeWithSelector( + ProxyAdmin(proxyAdminAddr).transferOwnership.selector, + vm.addr(deployerPvtKey) + ); + vm.startBroadcast(deployerPvtKey); + zkevmDeployer.deployDeterministicAndCall(0, salt, bytecode, callData); + vm.stopBroadcast(); + + console.log("\n----------------------\n"); + console.log("ProxyAdmin deployed and ownership transferred!"); + console.log("Proxy Admin Address: %s", proxyAdminAddr); + console.log( + "Proxy Admin Owner Address: %s", + ProxyAdmin(proxyAdminAddr).owner() + ); + return proxyAdminAddr; + } + + function _deployBridgeImplementation() internal returns (address) { + bytes memory bytecode = type(PolygonZkEVMBridgeV2).creationCode; + address bridgeAddress = zkevmDeployer.predictDeterministicAddress( + salt, + keccak256(bytecode) + ); + + // check if there is already a Bridge implementation deployed at the address + if (bridgeAddress.code.length > 0) { + console.log("\n----------------------\n"); + console.log( + "Bridge implementation already deployed at address: %s", + bridgeAddress + ); + return bridgeAddress; + } + + // deploy Bridge implementation deterministically + vm.startBroadcast(deployerPvtKey); + zkevmDeployer.deployDeterministic(0, salt, bytecode); + vm.stopBroadcast(); + + console.log("\n----------------------\n"); + console.log("Bridge implementation deployed!"); + console.log("Bridge Implementation Address: %s", bridgeAddress); + return bridgeAddress; + } + + function _deployTimelock( + address proxyAdminAddr, + address polygonRollupManagerAddr + ) internal { + vm.startBroadcast(deployerPvtKey); + address[] memory adminAddresses = new address[](1); + adminAddresses[0] = timelockAdminAddress; + PolygonZkEVMTimelock timelock = new PolygonZkEVMTimelock( + minDelayTimelock, + adminAddresses, + adminAddresses, + timelockAdminAddress, + PolygonZkEVM(polygonRollupManagerAddr) + ); + ProxyAdmin(proxyAdminAddr).transferOwnership(address(timelock)); + vm.stopBroadcast(); + + console.log("\n----------------------\n"); + console.log("Timelock deployed and ProxyAdmin ownership transferred!"); + console.log("Timelock Address: %s", address(timelock)); + console.log( + "Proxy Admin Owner Address: %s", + ProxyAdmin(proxyAdminAddr).owner() + ); + } + + function _deployBridgeProxy( + address bridgeImplementationAddr, + address globalExitRootManagerAddr, + address rollupManagerAddr, + address proxyAdminAddr + ) internal returns (address) { + bytes memory bytecode = abi.encodePacked( + type(TransparentUpgradeableProxy).creationCode, + abi.encode(bridgeImplementationAddr, proxyAdminAddr, bytes("")) + ); + address bridgeProxyAddress = zkevmDeployer.predictDeterministicAddress( + salt, + keccak256(bytecode) + ); + + // check if there is already a Bridge proxy deployed at the address + if (bridgeProxyAddress.code.length > 0) { + console.log("\n----------------------\n"); + console.log( + "Bridge proxy already deployed at address: %s", + bridgeProxyAddress + ); + return bridgeProxyAddress; + } + + bytes memory callData = abi.encodeWithSelector( + PolygonZkEVMBridgeV2(bridgeProxyAddress).initialize.selector, + NETWORK_ID_MAINNET, + GAS_TOKEN_ADDR_MAINNET, + GAS_TOKEN_NETWORK_MAINNET, + globalExitRootManagerAddr, + rollupManagerAddr, + GAS_TOKEN_METADATA + ); + + vm.startBroadcast(deployerPvtKey); + zkevmDeployer.deployDeterministicAndCall(0, salt, bytecode, callData); + vm.stopBroadcast(); + + console.log("\n----------------------\n"); + console.log("Bridge proxy deployed!"); + console.log("Bridge Proxy Address: %s", bridgeProxyAddress); + return bridgeProxyAddress; + } + + function _deployGlobalExitRootManager( + address rollupManagerAddr, + address bridgeAddr, + address proxyAdminAddr + ) internal returns (address) { + vm.startBroadcast(deployerPvtKey); + PolygonZkEVMGlobalExitRootV2 globalExitRootManager = new PolygonZkEVMGlobalExitRootV2( + rollupManagerAddr, + bridgeAddr + ); + address globalExitRootManagerProxy = _proxify( + address(globalExitRootManager), + proxyAdminAddr, + "" + ); + vm.stopBroadcast(); + + console.log("\n----------------------\n"); + console.log("Global Exit Root Manager deployed!"); + console.log( + "Global Exit Root Manager implementation address: %s", + address(globalExitRootManager) + ); + console.log( + "Global Exit Root Manager Address: %s", + globalExitRootManagerProxy + ); + return globalExitRootManagerProxy; + } + + function _deployRollupManager( + address globalExitRootManagerAddr, + address bridgeAddr, + address proxyAdminAddr + ) internal returns (address) { + vm.startBroadcast(deployerPvtKey); + PolygonRollupManagerNotUpgraded rollupManager = new PolygonRollupManagerNotUpgraded( + IPolygonZkEVMGlobalExitRootV2(globalExitRootManagerAddr), + IERC20Upgradeable(polTokenAddress), + IPolygonZkEVMBridge(bridgeAddr) + ); + address rollupManagerProxy = _proxify( + address(rollupManager), + proxyAdminAddr, + abi.encodeWithSelector( + rollupManager.initialize.selector, + trustedAggregator, + pendingStateTimeout, + trustedAggregatorTimeout, + admin, + timelockAdminAddress, + emergencyCouncilAddress, + bytes32(0), + bytes32(0), + 0, + 0 + ) + ); + vm.stopBroadcast(); + + console.log("\n----------------------\n"); + console.log("Rollup Manager deployed!"); + console.log( + "Rollup Manager implementation address: %s", + address(rollupManager) + ); + console.log("Rollup Manager Address: %s", rollupManagerProxy); + return rollupManagerProxy; + } + + function _verifyRollupManager( + address rolluplManagerAddr, + address bridgeProxyAddr + ) internal view { + PolygonRollupManagerNotUpgraded rollupManager = PolygonRollupManagerNotUpgraded( + rolluplManagerAddr + ); + assert( + address(rollupManager.globalExitRootManager()) == + computedGlobalExitRootManagerAddress + ); + assert(address(rollupManager.bridgeAddress()) == bridgeProxyAddr); + assert(address(rollupManager.pol()) == polTokenAddress); + + assert(rollupManager.hasRole(DEFAULT_ADMIN_ROLE, timelockAdminAddress)); + assert( + rollupManager.hasRole(ADD_ROLLUP_TYPE_ROLE, timelockAdminAddress) + ); + assert( + rollupManager.hasRole( + ADD_EXISTING_ROLLUP_ROLE, + timelockAdminAddress + ) + ); + assert(rollupManager.hasRole(UPDATE_ROLLUP_ROLE, timelockAdminAddress)); + assert(rollupManager.hasRole(OBSOLETE_ROLLUP_TYPE_ROLE, admin)); + assert(rollupManager.hasRole(CREATE_ROLLUP_ROLE, admin)); + assert(rollupManager.hasRole(STOP_EMERGENCY_ROLE, admin)); + assert(rollupManager.hasRole(TWEAK_PARAMETERS_ROLE, admin)); + assert( + rollupManager.hasRole(TRUSTED_AGGREGATOR_ROLE, trustedAggregator) + ); + assert(rollupManager.hasRole(TRUSTED_AGGREGATOR_ROLE_ADMIN, admin)); + assert(rollupManager.hasRole(SET_FEE_ROLE, admin)); + assert( + rollupManager.hasRole( + EMERGENCY_COUNCIL_ROLE, + emergencyCouncilAddress + ) + ); + assert( + rollupManager.hasRole( + EMERGENCY_COUNCIL_ADMIN, + emergencyCouncilAddress + ) + ); + } + + function _computeDeployAddresses() internal { + uint256 nonceProxyGlobalExitRootManager = vm.getNonce( + vm.addr(deployerPvtKey) + ) + 6; + uint256 nonceProxyRollupManager = nonceProxyGlobalExitRootManager + 2; + + computedGlobalExitRootManagerAddress = vm.computeCreateAddress( + vm.addr(deployerPvtKey), + nonceProxyGlobalExitRootManager + ); + + computedRollupManagerAddress = vm.computeCreateAddress( + vm.addr(deployerPvtKey), + nonceProxyRollupManager + ); + } + + function _proxify( + address logic, + address adminAddr, + bytes memory data + ) internal returns (address proxy) { + TransparentUpgradeableProxy proxy_ = new TransparentUpgradeableProxy( + logic, + adminAddr, + data + ); + return (address(proxy_)); + } +} diff --git a/script/DeployPolygonZkEVMDeployer.s.sol b/script/DeployPolygonZkEVMDeployer.s.sol new file mode 100644 index 000000000..de36fb551 --- /dev/null +++ b/script/DeployPolygonZkEVMDeployer.s.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Script.sol"; + +contract DeployPolygonZkEVMDeployer is Script { + using stdJson for string; + + uint256 constant GAS_PRICE = 100 gwei; + uint256 constant GAS_LIMIT = 1000000; + + address polygonZkEVMDeployerAddr = + 0x68276c6b151e998eD7997035601c0b56a9629743; + address keylessSignerAddr = 0xC5A8c8242a366B72418ef3924eE26804B3F55cc1; + + function run() public { + bytes memory fundingPrivateKey = vm.envBytes("PRIVATE_KEY"); + bytes + memory keylessDeploymentTx = hex"f90c718085174876e800830f42408080b90c55608060405234801561000f575f80fd5b50604051610c35380380610c3583398101604081905261002e91610095565b61003733610046565b61004081610046565b506100c2565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f602082840312156100a5575f80fd5b81516001600160a01b03811681146100bb575f80fd5b9392505050565b610b66806100cf5f395ff3fe60806040526004361061006e575f3560e01c8063715018a61161004c578063715018a6146100e25780638da5cb5b146100f6578063e11ae6cb1461011f578063f2fde38b14610132575f80fd5b80632b79805a146100725780634a94d487146100875780636d07dbf81461009a575b5f80fd5b610085610080366004610908565b610151565b005b6100856100953660046109a2565b6101c2565b3480156100a5575f80fd5b506100b96100b43660046109f5565b610203565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100ed575f80fd5b50610085610215565b348015610101575f80fd5b505f5473ffffffffffffffffffffffffffffffffffffffff166100b9565b61008561012d366004610a15565b610228565b34801561013d575f80fd5b5061008561014c366004610a61565b61028e565b61015961034a565b5f6101658585856103ca565b90506101718183610527565b5060405173ffffffffffffffffffffffffffffffffffffffff821681527fba82f25fed02cd2a23d9f5d11c2ef588d22af5437cbf23bfe61d87257c480e4c9060200160405180910390a15050505050565b6101ca61034a565b6101d583838361056a565b506040517f25adb19089b6a549831a273acdf7908cff8b7ee5f551f8d1d37996cf01c5df5b905f90a1505050565b5f61020e8383610598565b9392505050565b61021d61034a565b6102265f6105a4565b565b61023061034a565b5f61023c8484846103ca565b60405173ffffffffffffffffffffffffffffffffffffffff821681529091507fba82f25fed02cd2a23d9f5d11c2ef588d22af5437cbf23bfe61d87257c480e4c9060200160405180910390a150505050565b61029661034a565b73ffffffffffffffffffffffffffffffffffffffff811661033e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b610347816105a4565b50565b5f5473ffffffffffffffffffffffffffffffffffffffff163314610226576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610335565b5f83471015610435576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f437265617465323a20696e73756666696369656e742062616c616e63650000006044820152606401610335565b81515f0361049f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f437265617465323a2062797465636f6465206c656e677468206973207a65726f6044820152606401610335565b8282516020840186f5905073ffffffffffffffffffffffffffffffffffffffff811661020e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f437265617465323a204661696c6564206f6e206465706c6f79000000000000006044820152606401610335565b606061020e83835f6040518060400160405280601e81526020017f416464726573733a206c6f772d6c6576656c2063616c6c206661696c65640000815250610618565b6060610590848484604051806060016040528060298152602001610b0860299139610618565b949350505050565b5f61020e83833061072d565b5f805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6060824710156106aa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c00000000000000000000000000000000000000000000000000006064820152608401610335565b5f808673ffffffffffffffffffffffffffffffffffffffff1685876040516106d29190610a9c565b5f6040518083038185875af1925050503d805f811461070c576040519150601f19603f3d011682016040523d82523d5f602084013e610711565b606091505b509150915061072287838387610756565b979650505050505050565b5f604051836040820152846020820152828152600b8101905060ff815360559020949350505050565b606083156107eb5782515f036107e45773ffffffffffffffffffffffffffffffffffffffff85163b6107e4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610335565b5081610590565b61059083838151156108005781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103359190610ab7565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f82601f830112610870575f80fd5b813567ffffffffffffffff8082111561088b5761088b610834565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019082821181831017156108d1576108d1610834565b816040528381528660208588010111156108e9575f80fd5b836020870160208301375f602085830101528094505050505092915050565b5f805f806080858703121561091b575f80fd5b8435935060208501359250604085013567ffffffffffffffff80821115610940575f80fd5b61094c88838901610861565b93506060870135915080821115610961575f80fd5b5061096e87828801610861565b91505092959194509250565b803573ffffffffffffffffffffffffffffffffffffffff8116811461099d575f80fd5b919050565b5f805f606084860312156109b4575f80fd5b6109bd8461097a565b9250602084013567ffffffffffffffff8111156109d8575f80fd5b6109e486828701610861565b925050604084013590509250925092565b5f8060408385031215610a06575f80fd5b50508035926020909101359150565b5f805f60608486031215610a27575f80fd5b8335925060208401359150604084013567ffffffffffffffff811115610a4b575f80fd5b610a5786828701610861565b9150509250925092565b5f60208284031215610a71575f80fd5b61020e8261097a565b5f5b83811015610a94578181015183820152602001610a7c565b50505f910152565b5f8251610aad818460208701610a7a565b9190910192915050565b602081525f8251806020840152610ad5816040850160208701610a7a565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016919091016040019291505056fe416464726573733a206c6f772d6c6576656c2063616c6c20776974682076616c7565206661696c6564a2646970667358221220330b94dc698c4d290bf55c23f13b473cde6a6bae0030cb902de18af54e35839f64736f6c634300081400330000000000000000000000009ab254be10f6df45e5c58ba2ce6cb3c9a1aff9541b8505ca1ab1e0845ca1ab1e"; + + // verify if the deployer has already been deployed + require( + polygonZkEVMDeployerAddr.codehash == 0, + "PolygonZkEVMDeployer already deployed" + ); + + // fund the keyless signer account + string memory fundTxRes = _sendTransaction( + fundingPrivateKey, + keylessSignerAddr, + GAS_LIMIT * GAS_PRICE + ); + console.log("Funding transaction: %s\n", fundTxRes); + + // TODO: switch to this after the issue upstream is fixed + // vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + // (bool success, ) = keylessSignerAddr.call{value: 0}(""); + // require(success, "Failed to fund keyless signer account"); + // vm.stopBroadcast(); + + // deploy the PolygonZkEVMDeployer contract + string memory deployTxRes = _publishRawTransaction(keylessDeploymentTx); + console.log( + "PolygonZkEVMDeployer deployment transaction: %s\n", + deployTxRes + ); + + // TODO: switch to this after the issue upstream is fixed + // vm.sendRawTransaction(keylessDeploymentTx); + + console.log( + "PolygonZkEVMDeployer deployed successfully at: %s!\n", + polygonZkEVMDeployerAddr + ); + } + + // TODO: remove this + function _sendTransaction( + bytes memory from, + address to, + uint256 value + ) internal returns (string memory) { + string[] memory exe = new string[](8); + exe[0] = "cast"; + exe[1] = "send"; + exe[2] = vm.toString(to); + exe[3] = "--value"; + exe[4] = vm.toString(value); + exe[5] = "--private-key"; + exe[6] = vm.toString(from); + exe[7] = "--json"; + + string memory result = string(vm.ffi(exe)); + console.log("Transaction sent successfully!"); + // console.log(string(result)); + return string(result); + } + + // TODO: remove this + function _publishRawTransaction( + bytes memory rawTx + ) internal returns (string memory) { + string[] memory exe = new string[](3); + exe[0] = "cast"; + exe[1] = "publish"; + exe[2] = vm.toString(rawTx); + + string memory result = string(vm.ffi(exe)); + console.log("Transaction published successfully!"); + // console.log(string(result)); + return string(result); + } +} diff --git a/script/ERC20PermitMock.s.sol b/script/ERC20PermitMock.s.sol new file mode 100644 index 000000000..b49e2cca3 --- /dev/null +++ b/script/ERC20PermitMock.s.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Script.sol"; +import "script/deployers/ERC20PermitMockDeployer.s.sol"; + +contract Deploy is Script, ERC20PermitMockDeployer { + function run() public { + string memory _name = "POL Token"; + string memory _symbol = "POL"; + address _initialAccount = makeAddr("initialAccount"); + uint256 _initialBalance = 20_000_000; + + address implementation = deployERC20PermitMockImplementation( + _name, + _symbol, + _initialAccount, + _initialBalance + ); + console.log("ERC20PermitMock deployed at: ", implementation); + } +} diff --git a/script/GrantRole.sol b/script/GrantRole.sol new file mode 100644 index 000000000..cc1e357f6 --- /dev/null +++ b/script/GrantRole.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Script.sol"; + +import {PolygonZkEVMTimelock} from "contracts/PolygonZkEVMTimelock.sol"; + +contract GrantRole is Script { + using stdJson for string; + + string role; + address accountToGrantRole; + address polygonRollupManager; + uint256 timelockDelay; + PolygonZkEVMTimelock polygonZkEVMTimelock; + + bytes32 roleHash; + + bytes32[10] internal roles = [ + keccak256("ADD_ROLLUP_TYPE_ROLE"), + keccak256("OBSOLETE_ROLLUP_TYPE_ROLE"), + keccak256("CREATE_ROLLUP_ROLE"), + keccak256("ADD_EXISTING_ROLLUP_ROLE"), + keccak256("UPDATE_ROLLUP_ROLE"), + keccak256("TRUSTED_AGGREGATOR_ROLE"), + keccak256("TRUSTED_AGGREGATOR_ROLE_ADMIN"), + keccak256("SET_FEE_ROLE"), + keccak256("STOP_EMERGENCY_ROLE"), + keccak256("EMERGENCY_COUNCIL_ROLE") + ]; + + function run() public { + readInput(); + ( + bytes memory scheduleBatchPayload, + bytes memory executeBatchPayload, + bytes32 payloadId + ) = makePayload(roleHash); + + console.log("\n----------------------\n"); + + console.log("Expected ID: %s", vm.toString(payloadId)); + + console.log("\n----------------------\n"); + + console.log("Schedule payload:"); + console.logBytes(scheduleBatchPayload); + + console.log("\n----------------------\n"); + + console.log("Execute payload:"); + console.logBytes(executeBatchPayload); + } + + function readInput() public { + string memory inputPath = "script/inputs/grantRole.json"; + console.log("Reading inputs from: %s \n", inputPath); + + string memory input = vm.readFile(inputPath); + + role = input.readString(".roleName"); + accountToGrantRole = input.readAddress(".accountToGrantRole"); + polygonRollupManager = input.readAddress( + ".polygonRollupManagerAddress" + ); + timelockDelay = input.readUint(".timelockDelay"); + polygonZkEVMTimelock = PolygonZkEVMTimelock( + payable(input.readAddress(".polygonZkEVMTimelockAddress")) + ); + + _verifyRole(role); + console.log("Role name:", role); + console.log("Account to grant role to:", accountToGrantRole); + console.log("PolygonRollupManager Address:", polygonRollupManager); + console.log("Timelock delay:", timelockDelay); + console.log( + "PolygonZkEVMTimelock Address:", + address(polygonZkEVMTimelock) + ); + } + + function makePayload( + bytes32 _roleHash + ) + public + view + returns ( + bytes memory scheduleBatchPayload, + bytes memory executeBatchPayload, + bytes32 payloadId + ) + { + bytes memory payload = abi.encodeCall( + polygonZkEVMTimelock.grantRole, + (_roleHash, accountToGrantRole) + ); + + scheduleBatchPayload = abi.encodeCall( + polygonZkEVMTimelock.schedule, + (polygonRollupManager, 0, payload, "", "", timelockDelay) + ); + + executeBatchPayload = abi.encodeCall( + polygonZkEVMTimelock.execute, + (polygonRollupManager, 0, payload, "", "") + ); + + // TODO: check why the polygonZkEVMTimelock.hashOperation() function is not working + // Error: script failed: + payloadId = keccak256( + abi.encode(polygonRollupManager, 0, payload, "", "") + ); + } + + function _verifyRole(string memory roleInput) internal { + bool isValidRole = false; + roleHash = keccak256(abi.encodePacked(roleInput)); + for (uint256 i = 0; i < roles.length; i++) { + if (roleHash == roles[i]) { + isValidRole = true; + break; + } + } + require(isValidRole, "Unsupported role name provided"); + } +} diff --git a/script/PolygonDataCommittee.s.sol b/script/PolygonDataCommittee.s.sol new file mode 100644 index 000000000..430942542 --- /dev/null +++ b/script/PolygonDataCommittee.s.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Script.sol"; +import "script/deployers/PolygonDataCommitteeDeployer.s.sol"; + +contract Deploy is Script, PolygonDataCommitteeDeployer { + function run() public { + address proxyAdminOwner = makeAddr("proxyAdminOwner"); + + ( + address implementation, + address proxyAdmin, + address proxy + ) = deployPolygonDataCommitteeTransparent(proxyAdminOwner); + console.log("PolygonDataCommittee deployed at: ", proxy); + console.log( + "PolygonDataCommittee implementation deployed at: ", + implementation + ); + console.log("PolygonDataCommittee proxy admin: ", proxyAdmin); + } +} diff --git a/script/PolygonRollupManager.s.sol b/script/PolygonRollupManager.s.sol new file mode 100644 index 000000000..8205756fa --- /dev/null +++ b/script/PolygonRollupManager.s.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Script.sol"; +import "script/deployers/PolygonRollupManagerDeployer.s.sol"; + +contract Deploy is Script, PolygonRollupManagerDeployer { + function run() public { + IPolygonZkEVMGlobalExitRootV2 _globalExitRootManager = IPolygonZkEVMGlobalExitRootV2( + makeAddr("PolygonZkEVMGlobalExitRootV2") + ); + IERC20Upgradeable _pol = IERC20Upgradeable(makeAddr("POL")); + IPolygonZkEVMBridge _bridgeAddress = IPolygonZkEVMBridge( + makeAddr("PolygonZkEVMBridge") + ); + address implementation = deployPolygonRollupManagerImplementation( + _globalExitRootManager, + _pol, + _bridgeAddress + ); + console.log("PolygonRollupManager deployed at: ", implementation); + } +} diff --git a/script/PolygonRollupManagerEmptyMock.s.sol b/script/PolygonRollupManagerEmptyMock.s.sol new file mode 100644 index 000000000..84b6b293b --- /dev/null +++ b/script/PolygonRollupManagerEmptyMock.s.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Script.sol"; +import "script/deployers/PolygonRollupManagerEmptyMockDeployer.s.sol"; + +contract Deploy is Script, PolygonRollupManagerEmptyMockDeployer { + function run() public { + address proxyAdminOwner = makeAddr("proxyAdminOwner"); + + ( + address implementation, + address proxyAdmin, + address proxy + ) = deployPolygonRollupManagerEmptyMockTransparent(proxyAdminOwner); + console.log("PolygonRollupManagerEmptyMock deployed at: ", proxy); + console.log( + "PolygonRollupManagerEmptyMock implementation deployed at: ", + implementation + ); + console.log("PolygonRollupManagerEmptyMock proxy admin: ", proxyAdmin); + } +} diff --git a/script/PolygonZkEVMBridgeV2.s.sol b/script/PolygonZkEVMBridgeV2.s.sol new file mode 100644 index 000000000..e1ae44bd1 --- /dev/null +++ b/script/PolygonZkEVMBridgeV2.s.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Script.sol"; +import "script/deployers/PolygonZkEVMBridgeV2Deployer.s.sol"; + +contract Deploy is Script, PolygonZkEVMBridgeV2Deployer { + function run() public { + address proxyAdminOwner = makeAddr("proxyAdminOwner"); + uint32 _networkID = 0; + address _gasTokenAddress = makeAddr("gasTokenAddress"); + uint32 _gasTokenNetwork = 1; + IBasePolygonZkEVMGlobalExitRoot _globalExitRootManager = IBasePolygonZkEVMGlobalExitRoot( + makeAddr("PolygonZkEVMGlobalExitRootV2") + ); + + address _rollupManager = makeAddr("RollupManager"); + bytes memory _gasTokenMetadata = bytes(""); + + ( + address implementation, + address proxyAdmin, + address proxy + ) = deployPolygonZkEVMBridgeV2Transparent( + proxyAdminOwner, + _networkID, + _gasTokenAddress, + _gasTokenNetwork, + _globalExitRootManager, + _rollupManager, + _gasTokenMetadata + ); + console.log("PolygonZkEVMBridgeV2 deployed at: ", proxy); + console.log( + "PolygonZkEVMBridgeV2 implementation deployed at: ", + implementation + ); + console.log("PolygonZkEVMBridgeV2 proxy admin: ", proxyAdmin); + } +} diff --git a/script/PolygonZkEVMDeployer.s.sol b/script/PolygonZkEVMDeployer.s.sol new file mode 100644 index 000000000..a4ca9722b --- /dev/null +++ b/script/PolygonZkEVMDeployer.s.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Script.sol"; +import "script/deployers/PolygonZkEVMDeployerDeployer.s.sol"; + +contract Deploy is Script, PolygonZkEVMDeployerDeployer { + function run() public { + address _owner = vm.addr(vm.envUint("PRIVATE_KEY")); + + address implementation = deployPolygonZkEVMDeployerImplementation( + _owner + ); + console.log("PolygonZkEVMDeployer deployed at: ", implementation); + } +} diff --git a/script/PolygonZkEVMGlobalExitRootV2.s.sol b/script/PolygonZkEVMGlobalExitRootV2.s.sol new file mode 100644 index 000000000..ebd8a4db1 --- /dev/null +++ b/script/PolygonZkEVMGlobalExitRootV2.s.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Script.sol"; +import "script/deployers/PolygonZkEVMGlobalExitRootV2Deployer.s.sol"; + +contract Deploy is Script, PolygonZkEVMGlobalExitRootV2Deployer { + function run() public { + address proxyAdminOwner = makeAddr("proxyAdminOwner"); + address _rollupManager = makeAddr("RollupManager"); + address _bridgeAddress = makeAddr("PolygonZkEVMBridgeV2"); + + ( + address implementation, + address proxyAdmin, + address proxy + ) = deployPolygonZkEVMGlobalExitRootV2Transparent( + proxyAdminOwner, + _rollupManager, + _bridgeAddress + ); + console.log("PolygonZkEVMGlobalExitRootV2 deployed at: ", proxy); + console.log( + "PolygonZkEVMGlobalExitRootV2 implementation deployed at: ", + implementation + ); + console.log("PolygonZkEVMGlobalExitRootV2 proxy admin: ", proxyAdmin); + } +} diff --git a/script/PolygonZkEVMTimelock.s.sol b/script/PolygonZkEVMTimelock.s.sol new file mode 100644 index 000000000..5f1adf853 --- /dev/null +++ b/script/PolygonZkEVMTimelock.s.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Script.sol"; +import "script/deployers/PolygonZkEVMTimelockDeployer.s.sol"; + +contract Deploy is Script, PolygonZkEVMTimelockDeployer { + function run() public { + uint256 minDelay = 0; + address[] memory proposers = new address[](0); + address[] memory executors = new address[](0); + address admin = makeAddr("admin"); + PolygonZkEVM _polygonZkEVM = PolygonZkEVM(makeAddr("polygonZkEVM")); + + address implementation = deployPolygonZkEVMTimelockImplementation( + minDelay, + proposers, + executors, + admin, + _polygonZkEVM + ); + console.log("PolygonZkEVMTimelock deployed at: ", implementation); + } +} diff --git a/script/VerifierRollupHelperMock.s.sol b/script/VerifierRollupHelperMock.s.sol new file mode 100644 index 000000000..ae5202b82 --- /dev/null +++ b/script/VerifierRollupHelperMock.s.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Script.sol"; +import "script/deployers/VerifierRollupHelperMockDeployer.s.sol"; + +contract Deploy is Script, VerifierRollupHelperMockDeployer { + function run() public { + address proxyAdminOwner = makeAddr("proxyAdminOwner"); + + ( + address implementation, + address proxyAdmin, + address proxy + ) = deployVerifierRollupHelperMockTransparent(proxyAdminOwner); + console.log("VerifierRollupHelperMock deployed at: ", proxy); + console.log( + "VerifierRollupHelperMock implementation deployed at: ", + implementation + ); + console.log("VerifierRollupHelperMock proxy admin: ", proxyAdmin); + } +} diff --git a/script/deployers/ClaimCompressorDeployer.s.sol b/script/deployers/ClaimCompressorDeployer.s.sol new file mode 100644 index 000000000..14bc8fef5 --- /dev/null +++ b/script/deployers/ClaimCompressorDeployer.s.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +//////////////////////////////////////////////////// +// AUTOGENERATED - DO NOT EDIT THIS FILE DIRECTLY // +//////////////////////////////////////////////////// + +import "forge-std/Script.sol"; + +import {ClaimCompressor} from "contracts/utils/ClaimCompressor.sol"; + +abstract contract ClaimCompressorDeployer is Script { + function deployClaimCompressorImplementation( + address __bridgeAddress, + uint32 __networkID + ) internal returns (address implementation) { + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + implementation = address( + new ClaimCompressor(__bridgeAddress, __networkID) + ); + vm.stopBroadcast(); + } +} diff --git a/script/deployers/ERC20PermitMockDeployer.s.sol b/script/deployers/ERC20PermitMockDeployer.s.sol new file mode 100644 index 000000000..a913015c3 --- /dev/null +++ b/script/deployers/ERC20PermitMockDeployer.s.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +//////////////////////////////////////////////////// +// AUTOGENERATED - DO NOT EDIT THIS FILE DIRECTLY // +//////////////////////////////////////////////////// + +import "forge-std/Script.sol"; + +import "contracts/mocks/ERC20PermitMock.sol"; + +abstract contract ERC20PermitMockDeployer is Script { + function deployERC20PermitMockImplementation( + string memory name, + string memory symbol, + address initialAccount, + uint256 initialBalance + ) internal returns (address implementation) { + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + implementation = address( + new ERC20PermitMock(name, symbol, initialAccount, initialBalance) + ); + vm.stopBroadcast(); + } +} diff --git a/script/deployers/PolygonDataCommitteeDeployer.s.sol b/script/deployers/PolygonDataCommitteeDeployer.s.sol new file mode 100644 index 000000000..87db74e2a --- /dev/null +++ b/script/deployers/PolygonDataCommitteeDeployer.s.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +//////////////////////////////////////////////////// +// AUTOGENERATED - DO NOT EDIT THIS FILE DIRECTLY // +//////////////////////////////////////////////////// + +import "forge-std/Script.sol"; + +import "contracts/consensus/validium/PolygonDataCommittee.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy, ITransparentUpgradeableProxy} from "@openzeppelin/contracts5/proxy/transparent/TransparentUpgradeableProxy.sol"; + +abstract contract PolygonDataCommitteeDeployer is Script { + PolygonDataCommittee internal polygonDataCommittee; + ProxyAdmin internal polygonDataCommitteeProxyAdmin; + address internal polygonDataCommitteeImplementation; + + function deployPolygonDataCommitteeTransparent( + address proxyAdminOwner + ) + internal + returns (address implementation, address proxyAdmin, address proxy) + { + bytes memory initData = abi.encodeCall( + PolygonDataCommittee.initialize, + () + ); + + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + + polygonDataCommitteeImplementation = address( + new PolygonDataCommittee() + ); + polygonDataCommittee = PolygonDataCommittee( + address( + new TransparentUpgradeableProxy( + polygonDataCommitteeImplementation, + proxyAdminOwner, + initData + ) + ) + ); + + vm.stopBroadcast(); + + polygonDataCommitteeProxyAdmin = ProxyAdmin( + address( + uint160( + uint256( + vm.load( + address(polygonDataCommittee), + hex"b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103" + ) + ) + ) + ) + ); + + return ( + polygonDataCommitteeImplementation, + address(polygonDataCommitteeProxyAdmin), + address(polygonDataCommittee) + ); + } + + function deployPolygonDataCommitteeImplementation() + internal + returns (address implementation) + { + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + implementation = address(new PolygonDataCommittee()); + vm.stopBroadcast(); + } +} diff --git a/script/deployers/PolygonRollupManagerDeployer.s.sol b/script/deployers/PolygonRollupManagerDeployer.s.sol new file mode 100644 index 000000000..49d213a20 --- /dev/null +++ b/script/deployers/PolygonRollupManagerDeployer.s.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +//////////////////////////////////////////////////// +// AUTOGENERATED - DO NOT EDIT THIS FILE DIRECTLY // +//////////////////////////////////////////////////// + +import "forge-std/Script.sol"; + +import "contracts/PolygonRollupManager.sol"; +import {TransparentUpgradeableProxy, ITransparentUpgradeableProxy} from "@openzeppelin/contracts5/proxy/transparent/TransparentUpgradeableProxy.sol"; + +abstract contract PolygonRollupManagerDeployer is Script { + function deployPolygonRollupManagerImplementation( + IPolygonZkEVMGlobalExitRootV2 _globalExitRootManager, + IERC20Upgradeable _pol, + IPolygonZkEVMBridge _bridgeAddress + ) internal returns (address implementation) { + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + implementation = address( + new PolygonRollupManager( + _globalExitRootManager, + _pol, + _bridgeAddress + ) + ); + vm.stopBroadcast(); + } +} diff --git a/script/deployers/PolygonRollupManagerEmptyMockDeployer.s.sol b/script/deployers/PolygonRollupManagerEmptyMockDeployer.s.sol new file mode 100644 index 000000000..91188cd89 --- /dev/null +++ b/script/deployers/PolygonRollupManagerEmptyMockDeployer.s.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +//////////////////////////////////////////////////// +// AUTOGENERATED - DO NOT EDIT THIS FILE DIRECTLY // +//////////////////////////////////////////////////// + +import "forge-std/Script.sol"; + +import "contracts/mocks/PolygonRollupManagerEmptyMock.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts5/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy, ITransparentUpgradeableProxy} from "@openzeppelin/contracts5/proxy/transparent/TransparentUpgradeableProxy.sol"; + +abstract contract PolygonRollupManagerEmptyMockDeployer is Script { + PolygonRollupManagerEmptyMock internal polygonRollupManagerEmptyMock; + ProxyAdmin internal polygonRollupManagerEmptyMockProxyAdmin; + address internal polygonRollupManagerEmptyMockImplementation; + + function deployPolygonRollupManagerEmptyMockTransparent( + address proxyAdminOwner + ) + internal + returns (address implementation, address proxyAdmin, address proxy) + { + bytes memory initData = ""; + + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + + polygonRollupManagerEmptyMockImplementation = address( + new PolygonRollupManagerEmptyMock() + ); + polygonRollupManagerEmptyMock = PolygonRollupManagerEmptyMock( + address( + new TransparentUpgradeableProxy( + polygonRollupManagerEmptyMockImplementation, + proxyAdminOwner, + initData + ) + ) + ); + + vm.stopBroadcast(); + + polygonRollupManagerEmptyMockProxyAdmin = ProxyAdmin( + address( + uint160( + uint256( + vm.load( + address(polygonRollupManagerEmptyMock), + hex"b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103" + ) + ) + ) + ) + ); + + return ( + polygonRollupManagerEmptyMockImplementation, + address(polygonRollupManagerEmptyMockProxyAdmin), + address(polygonRollupManagerEmptyMock) + ); + } + + function deployPolygonRollupManagerEmptyMockImplementation() + internal + returns (address implementation) + { + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + implementation = address(new PolygonRollupManagerEmptyMock()); + vm.stopBroadcast(); + } +} diff --git a/script/deployers/PolygonValidiumEtrogDeployer.s.sol b/script/deployers/PolygonValidiumEtrogDeployer.s.sol new file mode 100644 index 000000000..2c78b715f --- /dev/null +++ b/script/deployers/PolygonValidiumEtrogDeployer.s.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +//////////////////////////////////////////////////// +// AUTOGENERATED - DO NOT EDIT THIS FILE DIRECTLY // +//////////////////////////////////////////////////// + +import "forge-std/Script.sol"; + +import "contracts/consensus/validium/PolygonValidiumEtrog.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy, ITransparentUpgradeableProxy} from "@openzeppelin/contracts5/proxy/transparent/TransparentUpgradeableProxy.sol"; + +abstract contract PolygonValidiumEtrogDeployer is Script { + PolygonValidiumEtrog internal polygonValidiumEtrog; + ProxyAdmin internal polygonValidiumEtrogProxyAdmin; + address internal polygonValidiumEtrogImplementation; + + function deployPolygonValidiumEtrogTransparent( + address proxyAdminOwner, + IPolygonZkEVMGlobalExitRootV2 _globalExitRootManager, + IERC20Upgradeable _pol, + IPolygonZkEVMBridgeV2 _bridgeAddress, + PolygonRollupManager _rollupManager, + address _admin, + address sequencer, + uint32 networkID, + address _gasTokenAddress, + string memory sequencerURL, + string memory _networkName + ) + internal + returns (address implementation, address proxyAdmin, address proxy) + { + bytes memory initData = abi.encodeWithSignature( + "initialize(address,address,uint32,address,string,string)", + _admin, + sequencer, + networkID, + _gasTokenAddress, + sequencerURL, + _networkName + ); + + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + polygonValidiumEtrogImplementation = address( + new PolygonValidiumEtrog( + _globalExitRootManager, + _pol, + _bridgeAddress, + _rollupManager + ) + ); + vm.stopBroadcast(); + + // two step deployment as rollupManager is required for initialization + vm.startBroadcast(address(_rollupManager)); + polygonValidiumEtrog = PolygonValidiumEtrog( + address( + new TransparentUpgradeableProxy( + polygonValidiumEtrogImplementation, + proxyAdminOwner, + initData + ) + ) + ); + vm.stopBroadcast(); + + polygonValidiumEtrogProxyAdmin = ProxyAdmin( + address( + uint160( + uint256( + vm.load( + address(polygonValidiumEtrog), + hex"b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103" + ) + ) + ) + ) + ); + + return ( + polygonValidiumEtrogImplementation, + address(polygonValidiumEtrogProxyAdmin), + address(polygonValidiumEtrog) + ); + } + + function deployPolygonValidiumEtrogImplementation( + IPolygonZkEVMGlobalExitRootV2 _globalExitRootManager, + IERC20Upgradeable _pol, + IPolygonZkEVMBridgeV2 _bridgeAddress, + PolygonRollupManager _rollupManager + ) internal returns (address implementation) { + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + implementation = address( + new PolygonValidiumEtrog( + _globalExitRootManager, + _pol, + _bridgeAddress, + _rollupManager + ) + ); + vm.stopBroadcast(); + } +} diff --git a/script/deployers/PolygonZkEVMBridgeV2Deployer.s.sol b/script/deployers/PolygonZkEVMBridgeV2Deployer.s.sol new file mode 100644 index 000000000..65b234aed --- /dev/null +++ b/script/deployers/PolygonZkEVMBridgeV2Deployer.s.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +//////////////////////////////////////////////////// +// AUTOGENERATED - DO NOT EDIT THIS FILE DIRECTLY // +//////////////////////////////////////////////////// + +import "forge-std/Script.sol"; + +import "contracts-ignored-originals/PolygonZkEVMBridgeV2.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy, ITransparentUpgradeableProxy} from "@openzeppelin/contracts5/proxy/transparent/TransparentUpgradeableProxy.sol"; + +abstract contract PolygonZkEVMBridgeV2Deployer is Script { + PolygonZkEVMBridgeV2 internal polygonZkEVMBridgeV2; + ProxyAdmin internal polygonZkEVMBridgeV2ProxyAdmin; + address internal polygonZkEVMBridgeV2Implementation; + + function deployPolygonZkEVMBridgeV2Transparent( + address proxyAdminOwner, + uint32 _networkID, + address _gasTokenAddress, + uint32 _gasTokenNetwork, + IBasePolygonZkEVMGlobalExitRoot _globalExitRootManager, + address _polygonRollupManager, + bytes memory _gasTokenMetadata + ) + internal + returns (address implementation, address proxyAdmin, address proxy) + { + bytes memory initData = abi.encodeCall( + PolygonZkEVMBridgeV2.initialize, + ( + _networkID, + _gasTokenAddress, + _gasTokenNetwork, + _globalExitRootManager, + _polygonRollupManager, + _gasTokenMetadata + ) + ); + + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + + polygonZkEVMBridgeV2Implementation = address( + new PolygonZkEVMBridgeV2() + ); + polygonZkEVMBridgeV2 = PolygonZkEVMBridgeV2( + address( + new TransparentUpgradeableProxy( + polygonZkEVMBridgeV2Implementation, + proxyAdminOwner, + initData + ) + ) + ); + + vm.stopBroadcast(); + + polygonZkEVMBridgeV2ProxyAdmin = ProxyAdmin( + address( + uint160( + uint256( + vm.load( + address(polygonZkEVMBridgeV2), + hex"b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103" + ) + ) + ) + ) + ); + + return ( + polygonZkEVMBridgeV2Implementation, + address(polygonZkEVMBridgeV2ProxyAdmin), + address(polygonZkEVMBridgeV2) + ); + } + + function deployPolygonZkEVMBridgeV2Implementation() + internal + returns (address implementation) + { + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + implementation = address(new PolygonZkEVMBridgeV2()); + vm.stopBroadcast(); + } +} diff --git a/script/deployers/PolygonZkEVMDeployerDeployer.s.sol b/script/deployers/PolygonZkEVMDeployerDeployer.s.sol new file mode 100644 index 000000000..7584ae7fe --- /dev/null +++ b/script/deployers/PolygonZkEVMDeployerDeployer.s.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +//////////////////////////////////////////////////// +// AUTOGENERATED - DO NOT EDIT THIS FILE DIRECTLY // +//////////////////////////////////////////////////// + +import "forge-std/Script.sol"; + +import "contracts/deployment/PolygonZkEVMDeployer.sol"; + +abstract contract PolygonZkEVMDeployerDeployer is Script { + function deployPolygonZkEVMDeployerImplementation( + address _owner + ) internal returns (address implementation) { + bytes32 salt = keccak256(abi.encode(0)); + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + implementation = address(new PolygonZkEVMDeployer{salt: salt}(_owner)); + vm.stopBroadcast(); + require( + implementation != address(0), + "Contract deployment failed with CREATE2" + ); + } +} diff --git a/script/deployers/PolygonZkEVMEtrogDeployer.s.sol b/script/deployers/PolygonZkEVMEtrogDeployer.s.sol new file mode 100644 index 000000000..6e428d214 --- /dev/null +++ b/script/deployers/PolygonZkEVMEtrogDeployer.s.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +//////////////////////////////////////////////////// +// AUTOGENERATED - DO NOT EDIT THIS FILE DIRECTLY // +//////////////////////////////////////////////////// + +import "forge-std/Script.sol"; + +import "contracts/consensus/zkEVM/PolygonZkEVMEtrog.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy, ITransparentUpgradeableProxy} from "@openzeppelin/contracts5/proxy/transparent/TransparentUpgradeableProxy.sol"; + +abstract contract PolygonZkEVMEtrogDeployer is Script { + PolygonZkEVMEtrog internal polygonZkEVMEtrog; + ProxyAdmin internal polygonZkEVMEtrogProxyAdmin; + address internal polygonZkEVMEtrogImplementation; + + function deployPolygonZkEVMEtrogTransparent( + address proxyAdminOwner, + IPolygonZkEVMGlobalExitRootV2 _globalExitRootManager, + IERC20Upgradeable _pol, + IPolygonZkEVMBridgeV2 _bridgeAddress, + PolygonRollupManager _rollupManager, + address _admin, + address sequencer, + uint32 networkID, + address _gasTokenAddress, + string memory sequencerURL, + string memory _networkName + ) + internal + returns (address implementation, address proxyAdmin, address proxy) + { + bytes memory initData = abi.encodeWithSignature( + "initialize(address,address,uint32,address,string,string)", + _admin, + sequencer, + networkID, + _gasTokenAddress, + sequencerURL, + _networkName + ); + + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + polygonZkEVMEtrogImplementation = address( + new PolygonZkEVMEtrog( + _globalExitRootManager, + _pol, + _bridgeAddress, + _rollupManager + ) + ); + vm.stopBroadcast(); + + // two step deployment as rollupManager is required for initialization + vm.startBroadcast(address(_rollupManager)); + polygonZkEVMEtrog = PolygonZkEVMEtrog( + address( + new TransparentUpgradeableProxy( + polygonZkEVMEtrogImplementation, + proxyAdminOwner, + initData + ) + ) + ); + vm.stopBroadcast(); + + polygonZkEVMEtrogProxyAdmin = ProxyAdmin( + address( + uint160( + uint256( + vm.load( + address(polygonZkEVMEtrog), + hex"b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103" + ) + ) + ) + ) + ); + + return ( + polygonZkEVMEtrogImplementation, + address(polygonZkEVMEtrogProxyAdmin), + address(polygonZkEVMEtrog) + ); + } + + function deployPolygonZkEVMEtrogImplementation( + IPolygonZkEVMGlobalExitRootV2 _globalExitRootManager, + IERC20Upgradeable _pol, + IPolygonZkEVMBridgeV2 _bridgeAddress, + PolygonRollupManager _rollupManager + ) internal returns (address implementation) { + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + implementation = address( + new PolygonZkEVMEtrog( + _globalExitRootManager, + _pol, + _bridgeAddress, + _rollupManager + ) + ); + vm.stopBroadcast(); + } +} diff --git a/script/deployers/PolygonZkEVMGlobalExitRootV2Deployer.s.sol b/script/deployers/PolygonZkEVMGlobalExitRootV2Deployer.s.sol new file mode 100644 index 000000000..46bd1578b --- /dev/null +++ b/script/deployers/PolygonZkEVMGlobalExitRootV2Deployer.s.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +//////////////////////////////////////////////////// +// AUTOGENERATED - DO NOT EDIT THIS FILE DIRECTLY // +//////////////////////////////////////////////////// + +import "forge-std/Script.sol"; + +import "contracts/PolygonZkEVMGlobalExitRootV2.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy, ITransparentUpgradeableProxy} from "@openzeppelin/contracts5/proxy/transparent/TransparentUpgradeableProxy.sol"; + +abstract contract PolygonZkEVMGlobalExitRootV2Deployer is Script { + PolygonZkEVMGlobalExitRootV2 internal polygonZkEVMGlobalExitRootV2; + ProxyAdmin internal polygonZkEVMGlobalExitRootV2ProxyAdmin; + address internal polygonZkEVMGlobalExitRootV2Implementation; + + function deployPolygonZkEVMGlobalExitRootV2Transparent( + address proxyAdminOwner, + address _rollupManager, + address _bridgeAddress + ) + internal + returns (address implementation, address proxyAdmin, address proxy) + { + bytes memory initData = abi.encodeCall( + PolygonZkEVMGlobalExitRootV2.initialize, + () + ); + + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + + polygonZkEVMGlobalExitRootV2Implementation = address( + new PolygonZkEVMGlobalExitRootV2(_rollupManager, _bridgeAddress) + ); + polygonZkEVMGlobalExitRootV2 = PolygonZkEVMGlobalExitRootV2( + address( + new TransparentUpgradeableProxy( + polygonZkEVMGlobalExitRootV2Implementation, + proxyAdminOwner, + initData + ) + ) + ); + + vm.stopBroadcast(); + + polygonZkEVMGlobalExitRootV2ProxyAdmin = ProxyAdmin( + address( + uint160( + uint256( + vm.load( + address(polygonZkEVMGlobalExitRootV2), + hex"b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103" + ) + ) + ) + ) + ); + + return ( + polygonZkEVMGlobalExitRootV2Implementation, + address(polygonZkEVMGlobalExitRootV2ProxyAdmin), + address(polygonZkEVMGlobalExitRootV2) + ); + } + + function deployPolygonZkEVMGlobalExitRootV2Implementation( + address _rollupManager, + address _bridgeAddress + ) internal returns (address implementation) { + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + implementation = address( + new PolygonZkEVMGlobalExitRootV2(_rollupManager, _bridgeAddress) + ); + vm.stopBroadcast(); + } +} diff --git a/script/deployers/PolygonZkEVMTimelockDeployer.s.sol b/script/deployers/PolygonZkEVMTimelockDeployer.s.sol new file mode 100644 index 000000000..25bc69138 --- /dev/null +++ b/script/deployers/PolygonZkEVMTimelockDeployer.s.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +//////////////////////////////////////////////////// +// AUTOGENERATED - DO NOT EDIT THIS FILE DIRECTLY // +//////////////////////////////////////////////////// + +import "forge-std/Script.sol"; + +import "contracts/PolygonZkEVMTimelock.sol"; + +abstract contract PolygonZkEVMTimelockDeployer is Script { + function deployPolygonZkEVMTimelockImplementation( + uint256 minDelay, + address[] memory proposers, + address[] memory executors, + address admin, + PolygonZkEVM _polygonZkEVM + ) internal returns (address implementation) { + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + implementation = address( + new PolygonZkEVMTimelock( + minDelay, + proposers, + executors, + admin, + _polygonZkEVM + ) + ); + vm.stopBroadcast(); + } +} diff --git a/script/deployers/VerifierRollupHelperMockDeployer.s.sol b/script/deployers/VerifierRollupHelperMockDeployer.s.sol new file mode 100644 index 000000000..ae7b623a0 --- /dev/null +++ b/script/deployers/VerifierRollupHelperMockDeployer.s.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +//////////////////////////////////////////////////// +// AUTOGENERATED - DO NOT EDIT THIS FILE DIRECTLY // +//////////////////////////////////////////////////// + +import "forge-std/Script.sol"; + +import "contracts/mocks/VerifierRollupHelperMock.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy, ITransparentUpgradeableProxy} from "@openzeppelin/contracts5/proxy/transparent/TransparentUpgradeableProxy.sol"; + +abstract contract VerifierRollupHelperMockDeployer is Script { + VerifierRollupHelperMock internal verifierRollupHelperMock; + ProxyAdmin internal verifierRollupHelperMockProxyAdmin; + address internal verifierRollupHelperMockImplementation; + + function deployVerifierRollupHelperMockTransparent( + address proxyAdminOwner + ) + internal + returns (address implementation, address proxyAdmin, address proxy) + { + bytes memory initData = ""; + + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + + verifierRollupHelperMockImplementation = address( + new VerifierRollupHelperMock() + ); + verifierRollupHelperMock = VerifierRollupHelperMock( + address( + new TransparentUpgradeableProxy( + verifierRollupHelperMockImplementation, + proxyAdminOwner, + initData + ) + ) + ); + + vm.stopBroadcast(); + + verifierRollupHelperMockProxyAdmin = ProxyAdmin( + address( + uint160( + uint256( + vm.load( + address(verifierRollupHelperMock), + hex"b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103" + ) + ) + ) + ) + ); + + return ( + verifierRollupHelperMockImplementation, + address(verifierRollupHelperMockProxyAdmin), + address(verifierRollupHelperMock) + ); + } + + function deployVerifierRollupHelperMockImplementation() + internal + returns (address implementation) + { + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + implementation = address(new VerifierRollupHelperMock()); + vm.stopBroadcast(); + } +} diff --git a/script/inputs/deployParameters.json b/script/inputs/deployParameters.json new file mode 100644 index 000000000..ab5831d49 --- /dev/null +++ b/script/inputs/deployParameters.json @@ -0,0 +1,15 @@ +{ + "admin": "0x9Ab254be10F6Df45E5c58Ba2cE6CB3c9a1aff954", + "deployerPvtKey": "0x282a9256c1f3f4effa750d85d4c5cadabc9cb1005ae6ca01e8312be683099d9a", + "emergencyCouncilAddress": "0x9Ab254be10F6Df45E5c58Ba2cE6CB3c9a1aff954", + "gasTokenAddress": "0x0000000000000000000000000000000000000000", + "gasTokenNetwork": 0, + "minDelayTimelock": 3600, + "pendingStateTimeout": 604799, + "polTokenAddress": "0x0000000000000000000000000000000000000000", + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timelockAdminAddress": "0x9Ab254be10F6Df45E5c58Ba2cE6CB3c9a1aff954", + "trustedAggregator": "0x9Ab254be10F6Df45E5c58Ba2cE6CB3c9a1aff954", + "trustedAggregatorTimeout": 604799, + "zkEVMDeployerAddress": "0x68276c6b151e998eD7997035601c0b56a9629743" +} diff --git a/script/inputs/genesisInput.json b/script/inputs/genesisInput.json new file mode 100644 index 000000000..7f3d21cc3 --- /dev/null +++ b/script/inputs/genesisInput.json @@ -0,0 +1,12 @@ +{ + "PolygonZkEVMDeployer": "0x68276c6b151e998ed7997035601c0b56a9629743", + "ProxyAdmin": "0x173d6793b2B1E432C581BD017b47fCAa6Cc3db4e", + "PolygonZkEVMBridgeImplementation": "0x6A645724F3d9c8453AFD54d935b27737eCAD06d2", + "PolygonZkEVMBridgeProxy": "0xAa1668dfB81b8b40D82C8A7a5646991b72bf912f", + "PolygonZkEVMGlobalExitRootL2Implementation": "0xE73A9D8EAc49a8B20B920122e9230fC81FB1d4b2", + "PolygonZkEVMGlobalExitRootL2Proxy": "0x6738ae51fd2124651c7eb4446ea9141E45A32F00", + "PolygonZkEVMTimelock": "0xe0b4438C77e8E71C142703B088A57d369AeEA136", + "Deployer": "0x9Ab254be10F6Df45E5c58Ba2cE6CB3c9a1aff954", + "timelockAdminAddress": "0x9Ab254be10F6Df45E5c58Ba2cE6CB3c9a1aff954", + "isTest": true +} diff --git a/script/inputs/grantRole.json b/script/inputs/grantRole.json new file mode 100644 index 000000000..1bd699974 --- /dev/null +++ b/script/inputs/grantRole.json @@ -0,0 +1,7 @@ +{ + "roleName": "TRUSTED_AGGREGATOR_ROLE", + "accountToGrantRole": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "polygonRollupManagerAddress": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", + "timelockDelay": 0, + "polygonZkEVMTimelockAddress": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" +} diff --git a/tasks/compile.ts b/tasks/compile.ts new file mode 100644 index 000000000..c3f9a3a6a --- /dev/null +++ b/tasks/compile.ts @@ -0,0 +1,52 @@ +import {task} from "hardhat/config"; +import fs from "fs/promises"; +import path from "path"; + +const IGNORED_CONTRACTS = [ + "contracts/PolygonZkEVMBridgeV2.sol.ignored", + "contracts/outdated/mocks/DaiMock.sol.ignored", + "contracts/outdated/mocks/Uni.sol.ignored", +]; + +/** + * This task overrides the original compile task to allow compilation of ignored contracts + */ +task("compile", "Compiles the entire project, building all artifacts and build ignored contracts.").setAction( + async (args, hre, runSuper) => { + // Rename the ignored contracts to the original file name to allow compilation + const renamedFiles: string[] = []; + IGNORED_CONTRACTS.forEach((contract) => { + const sourceFilePath = path.join(contract); + const renamedContract = contract.replace(".ignored", ""); + const destinationFilePath = path.join(renamedContract); + renamedFiles.push(destinationFilePath); + renameFile(sourceFilePath, destinationFilePath); + }); + + // Run the original compile task + if (runSuper.isDefined) { + await runSuper(); + } + + // Revert the renaming of the ignored contracts + // Note: Check the artifacts folder to see if the ignored contracts are compiled + renamedFiles.forEach((file) => { + const originalFilePath = file + ".ignored"; + renameFile(file, originalFilePath); + }); + } +); + +/** + * Rename a file from sourcePath to destinationPath + * @param sourcePath + * @param destinationPath + */ +async function renameFile(sourcePath: string, destinationPath: string): Promise { + try { + await fs.rename(sourcePath, destinationPath); + console.log(`Successfully renamed from ${sourcePath} to ${destinationPath}`); + } catch (error) { + console.error(`Failed to rename file: ${error}`); + } +} diff --git a/test/PolygonDataCommittee.t.sol b/test/PolygonDataCommittee.t.sol new file mode 100644 index 000000000..4b04d62e9 --- /dev/null +++ b/test/PolygonDataCommittee.t.sol @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "test/util/TestHelpers.sol"; + +import {IPolygonDataCommitteeErrors} from "contracts/interfaces/IPolygonDataCommitteeErrors.sol"; +import "script/deployers/PolygonDataCommitteeDeployer.s.sol"; + +contract PolygonDataCommitteeTest is + Test, + TestHelpers, + PolygonDataCommitteeDeployer +{ + struct CommitteeMember { + address addr; + uint256 privateKey; + } + + address proxyAdminOwner = makeAddr("proxyAdminOwner"); + address dataCommitteeOwner; + + event CommitteeUpdated(bytes32 committeeHash); + + function setUp() public { + deployPolygonDataCommitteeTransparent(proxyAdminOwner); + dataCommitteeOwner = polygonDataCommittee.owner(); + } + + function testRevert_initialize_alreadyInitialized() public { + vm.expectRevert("Initializable: contract is already initialized"); + polygonDataCommittee.initialize(); + } + + function test_initialize() public view { + assertEq( + polygonDataCommittee.getProcotolName(), + "DataAvailabilityCommittee" + ); + } + + function testRevert_setupCommittee_tooManyRequiredSignatures() public { + uint256 requiredAmountOfSignatures = 3; + ( + , + bytes memory committeeMemberAddrBytes, + string[] memory committeeMemberUrls + ) = _generateCommitteeMembers( + 2 // different from requiredAmountOfSignatures + ); + + vm.expectRevert( + IPolygonDataCommitteeErrors.TooManyRequiredSignatures.selector + ); + vm.prank(dataCommitteeOwner); + polygonDataCommittee.setupCommittee( + requiredAmountOfSignatures, + committeeMemberUrls, + committeeMemberAddrBytes + ); + } + + function testRevert_setupCommittee_UnexpectedAddrsBytesLength() public { + uint256 requiredAmountOfSignatures = 2; + (, , string[] memory committeeMemberUrls) = _generateCommitteeMembers( + requiredAmountOfSignatures + ); + + vm.expectRevert( + IPolygonDataCommitteeErrors.UnexpectedAddrsBytesLength.selector + ); + vm.prank(dataCommitteeOwner); + polygonDataCommittee.setupCommittee( + requiredAmountOfSignatures, + committeeMemberUrls, + bytes("") // empty bytes + ); + } + + function testRevert_setupCommittee_EmptyURLNotAllowed() public { + uint256 requiredAmountOfSignatures = 2; + string[] memory committeeMemberUrls = new string[](2); + committeeMemberUrls[0] = "http://committeeMember0.com"; + committeeMemberUrls[1] = ""; // empty URL + (, bytes memory committeeMemberAddrBytes, ) = _generateCommitteeMembers( + requiredAmountOfSignatures + ); + + vm.expectRevert( + IPolygonDataCommitteeErrors.EmptyURLNotAllowed.selector + ); + vm.prank(dataCommitteeOwner); + polygonDataCommittee.setupCommittee( + requiredAmountOfSignatures, + committeeMemberUrls, + committeeMemberAddrBytes + ); + } + + function testRevert_setupCommittee_WrongAddrOrder() public { + uint256 requiredAmountOfSignatures = 2; + (, , string[] memory committeeMemberUrls) = _generateCommitteeMembers( + requiredAmountOfSignatures + ); + bytes memory committeeMemberAddrBytes = abi.encodePacked( + makeAddr("committeeMember0"), + makeAddr("committeeMember1") // wrong order + ); + + vm.expectRevert(IPolygonDataCommitteeErrors.WrongAddrOrder.selector); + vm.prank(dataCommitteeOwner); + polygonDataCommittee.setupCommittee( + requiredAmountOfSignatures, + committeeMemberUrls, + committeeMemberAddrBytes + ); + } + + function test_setupCommittee() public { + uint256 requiredAmountOfSignatures = 2; + ( + , + bytes memory committeeMemberAddrBytes, + string[] memory committeeMemberUrls + ) = _generateCommitteeMembers(requiredAmountOfSignatures); + + vm.expectEmit(); + emit CommitteeUpdated(keccak256(committeeMemberAddrBytes)); + vm.prank(dataCommitteeOwner); + polygonDataCommittee.setupCommittee( + requiredAmountOfSignatures, + committeeMemberUrls, + committeeMemberAddrBytes + ); + + assertEq(polygonDataCommittee.requiredAmountOfSignatures(), 2); + assertEq( + polygonDataCommittee.committeeHash(), + keccak256(committeeMemberAddrBytes) + ); + } + + function testRevert_verifyMessage_unexpectedAddrsAndSignaturesSize() + public + { + uint256 requiredAmountOfSignatures = 2; + ( + , + bytes memory committeeMemberAddrBytes, + string[] memory committeeMemberUrls + ) = _generateCommitteeMembers(requiredAmountOfSignatures); + + vm.prank(dataCommitteeOwner); + polygonDataCommittee.setupCommittee( + requiredAmountOfSignatures, + committeeMemberUrls, + committeeMemberAddrBytes + ); + + bytes32 inputHash = keccak256("inputHash"); + bytes memory aggrSig = new bytes(65); // only 1 signature + bytes memory signaturesAndAddrs = abi.encodePacked( + aggrSig, + committeeMemberAddrBytes + ); + + vm.expectRevert( + IPolygonDataCommitteeErrors + .UnexpectedAddrsAndSignaturesSize + .selector + ); + polygonDataCommittee.verifyMessage(inputHash, signaturesAndAddrs); + } + + function testRevert_verifyMessage_unexpectedCommitteeHash() public { + uint256 requiredAmountOfSignatures = 2; + ( + CommitteeMember[] memory committeeMembers, + bytes memory committeeMemberAddrBytes, + string[] memory committeeMemberUrls + ) = _generateCommitteeMembers(requiredAmountOfSignatures); + + vm.prank(dataCommitteeOwner); + polygonDataCommittee.setupCommittee( + requiredAmountOfSignatures, + committeeMemberUrls, + committeeMemberAddrBytes + ); + + bytes32 inputHash = keccak256("inputHash"); + bytes memory aggrSig = _signAndGetAggregatedSig( + committeeMembers, + inputHash + ); + bytes memory signaturesAndAddrs = abi.encodePacked( + aggrSig, + makeAddr("committeeMember1") // just one address + ); + + vm.expectRevert( + IPolygonDataCommitteeErrors.UnexpectedCommitteeHash.selector + ); + polygonDataCommittee.verifyMessage(inputHash, signaturesAndAddrs); + } + + function testRevert_verifyMessage_committeeAddressDoesNotExist() public { + uint256 requiredAmountOfSignatures = 2; + ( + CommitteeMember[] memory committeeMembers, + bytes memory committeeMemberAddrBytes, + string[] memory committeeMemberUrls + ) = _generateCommitteeMembers(requiredAmountOfSignatures); + + vm.prank(dataCommitteeOwner); + polygonDataCommittee.setupCommittee( + requiredAmountOfSignatures, + committeeMemberUrls, + committeeMemberAddrBytes + ); + + bytes32 inputHash = keccak256("inputHash"); + bytes memory aggrSig = _signAndGetAggregatedSig( + committeeMembers, + inputHash + ); + (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign( + committeeMembers[0].privateKey, + inputHash + ); + bytes memory signature1 = abi.encodePacked(r1, s1, v1); + (uint8 v3, bytes32 r3, bytes32 s3) = vm.sign( + makeAccount("committeeMember3").key, + inputHash + ); + bytes memory signature3 = abi.encodePacked(r3, s3, v3); // signature of a non-existing committee member + aggrSig = abi.encodePacked(signature1, signature3); + bytes memory signaturesAndAddrs = abi.encodePacked( + aggrSig, + committeeMemberAddrBytes + ); + + vm.expectRevert( + IPolygonDataCommitteeErrors.CommitteeAddressDoesNotExist.selector + ); + polygonDataCommittee.verifyMessage(inputHash, signaturesAndAddrs); + } + + function test_verifyMessage() public { + uint256 requiredAmountOfSignatures = 2; + ( + CommitteeMember[] memory committeeMembers, + bytes memory committeeMemberAddrBytes, + string[] memory committeeMemberUrls + ) = _generateCommitteeMembers(requiredAmountOfSignatures); + + vm.prank(dataCommitteeOwner); + polygonDataCommittee.setupCommittee( + requiredAmountOfSignatures, + committeeMemberUrls, + committeeMemberAddrBytes + ); + assertEq(polygonDataCommittee.getAmountOfMembers(), 2); + + bytes32 inputHash = keccak256("inputHash"); + bytes memory aggrSig = _signAndGetAggregatedSig( + committeeMembers, + inputHash + ); + bytes memory signaturesAndAddrs = abi.encodePacked( + aggrSig, + committeeMemberAddrBytes + ); + + polygonDataCommittee.verifyMessage(inputHash, signaturesAndAddrs); + } + + function _generateCommitteeMembers( + uint256 numOfMembers + ) + internal + returns (CommitteeMember[] memory, bytes memory, string[] memory) + { + CommitteeMember[] memory committeeMembers = new CommitteeMember[]( + numOfMembers + ); + bytes memory committeeMemberAddrBytes = new bytes(0); + string[] memory committeeMemberUrls = new string[]( + committeeMembers.length + ); + for (uint256 i = 0; i < numOfMembers; i++) { + Account memory memberAccount = makeAccount( + string.concat("committeeMember", Strings.toString(i)) + ); + committeeMembers[i] = CommitteeMember( + memberAccount.addr, + memberAccount.key + ); + } + + committeeMembers = _sortMembersByIncrementingAddresses( + committeeMembers + ); + + for (uint256 i = 0; i < committeeMembers.length; i++) { + committeeMemberAddrBytes = abi.encodePacked( + committeeMemberAddrBytes, + committeeMembers[i].addr + ); + committeeMemberUrls[i] = string.concat( + "http://committeeMember", + Strings.toString(i), + ".com" + ); + } + + return ( + committeeMembers, + committeeMemberAddrBytes, + committeeMemberUrls + ); + } + + function _sortMembersByIncrementingAddresses( + CommitteeMember[] memory committeeMembers + ) internal pure returns (CommitteeMember[] memory) { + uint256 n = committeeMembers.length; + bool swapped; + + do { + swapped = false; + for (uint256 i = 0; i < n - 1; i++) { + if (committeeMembers[i].addr > committeeMembers[i + 1].addr) { + CommitteeMember memory temp = committeeMembers[i]; + committeeMembers[i] = committeeMembers[i + 1]; + committeeMembers[i + 1] = temp; + + swapped = true; + } + } + n--; + } while (swapped); + + return committeeMembers; + } + + function _signAndGetAggregatedSig( + CommitteeMember[] memory committeeMembers, + bytes32 inputHash + ) internal pure returns (bytes memory) { + bytes memory aggrSig = bytes(""); + for (uint256 i = 0; i < committeeMembers.length; i++) { + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + committeeMembers[i].privateKey, + inputHash + ); + bytes memory signature = abi.encodePacked(r, s, v); + aggrSig = abi.encodePacked(aggrSig, signature); + } + return aggrSig; + } +} diff --git a/test/PolygonRollupManager.t.sol b/test/PolygonRollupManager.t.sol new file mode 100644 index 000000000..7ea3ec9ac --- /dev/null +++ b/test/PolygonRollupManager.t.sol @@ -0,0 +1,752 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "test/util/TestHelpers.sol"; + +import "contracts/mocks/PolygonRollupManagerMock.sol"; +import "contracts/PolygonZkEVMGlobalExitRootV2.sol"; +import "contracts/interfaces/IPolygonZkEVMBridge.sol"; +import "contracts/interfaces/IPolygonZkEVMBridgeV2Extended.sol"; +import "contracts/interfaces/IPolygonZkEVMBridgeV2.sol"; +import "contracts/mocks/ERC20PermitMock.sol"; + +import "contracts/mocks/VerifierRollupHelperMock.sol"; +import "contracts/consensus/zkEVM/PolygonZkEVMEtrog.sol"; + +import {PolygonZkEVMBridgeV2Deployer} from "script/deployers/PolygonZkEVMBridgeV2Deployer.s.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +// note extends PolygonRollupManager.tests.ts +contract PolygonRollupManagerTest is + Test, + TestHelpers, + IPolygonRollupManager, + PolygonZkEVMBridgeV2Deployer +{ + error OnlyNotEmergencyState(); + + struct CreateNewRollupEvent { + uint32 rollupID; + CreateNewRollupEventData data; + } + + struct CreateNewRollupEventData { + uint32 rollupTypeID; + address rollupAddress; + uint64 chainID; + address gasTokenAddress; + } + + mapping(string functionName => mapping(string snapshotName => uint256 snapshotId)) + private snapshot; + + // todo change to PolygonRollupManager + PolygonRollupManagerMock internal rollupManager; + PolygonZkEVMGlobalExitRootV2 internal globalExitRoot; + IPolygonZkEVMBridgeV2Extended internal bridge; + // todo change to IERC20Upgradeable + ERC20PermitMock internal token; + + // todo change to IVerifierRollup + VerifierRollupHelperMock internal verifier; + PolygonZkEVMEtrog internal zkEvm; + + address internal trustedAggregator = makeAddr("trustedAggregator"); + address internal trustedSequencer = makeAddr("trustedAggregator"); + address internal admin = makeAddr("admin"); + address internal timelock = makeAddr("timelock"); + address internal emergencyCouncil = makeAddr("emergencyCouncil"); + address internal beneficiary = makeAddr("beneficiary"); + + event UpdateRollup( + uint32 indexed rollupID, + uint32 newRollupTypeID, + uint64 lastVerifiedBatchBeforeUpgrade + ); + + // note mimics beforeEach "Deploy contract" + function setUp() public { + // BRIDGE + bridge = IPolygonZkEVMBridgeV2Extended( + _preDeployPolygonZkEVMBridgeV2() + ); + bridge = IPolygonZkEVMBridgeV2Extended(_proxify(address(bridge))); + + // GLOBAL EXIT ROOT + address rollupManagerAddr = vm.computeCreateAddress( + address(this), + vm.getNonce(address(this)) + 4 + ); + globalExitRoot = new PolygonZkEVMGlobalExitRootV2( + rollupManagerAddr, + address(bridge) + ); + globalExitRoot = PolygonZkEVMGlobalExitRootV2( + _proxify(address(globalExitRoot)) + ); + + // ROLLUP MANAGER + token = new ERC20PermitMock( + "Polygon Ecosystem Token", + "POL", + address(this), + 20000000 ether + ); + rollupManager = new PolygonRollupManagerMock( + globalExitRoot, + IERC20Upgradeable(address(token)), + // todo change to IPolygonZkEVMBridgeV2Extended + IPolygonZkEVMBridge(address(bridge)) + ); + rollupManager = PolygonRollupManagerMock( + _proxify(address(rollupManager)) + ); + require( + address(rollupManager) == rollupManagerAddr, + "Unexpected rollupManager address. Check nonce." + ); + + // OTHER + zkEvm = new PolygonZkEVMEtrog( + globalExitRoot, + IERC20Upgradeable(address(token)), + IPolygonZkEVMBridgeV2(address(bridge)), + rollupManager + ); + verifier = new VerifierRollupHelperMock(); + + // INITIALIZATION + bridge.initialize( + 0, + address(0), + 0, + globalExitRoot, + address(rollupManager), + "" + ); + rollupManager.initializeMock( + trustedAggregator, + 100, + 100, + admin, + timelock, + emergencyCouncil + ); + } + + function testRevert_updateRollupByRollupAdmin_OnlyRollupAdmin() public { + CreateNewRollupEvent memory createNewRollupEvent = _createRollup(); + ITransparentUpgradeableProxy rollupContract = ITransparentUpgradeableProxy( + createNewRollupEvent.data.rollupAddress + ); + vm.expectRevert(OnlyRollupAdmin.selector); + rollupManager.updateRollupByRollupAdmin(rollupContract, 0); + } + + function testRevert_updateRollupByRollupAdmin_AllSequencedMustBeVerified() + public + { + CreateNewRollupEvent memory createNewRollupEvent = _createRollup(); + ITransparentUpgradeableProxy rollupContract = ITransparentUpgradeableProxy( + createNewRollupEvent.data.rollupAddress + ); + vm.prank(address(rollupContract)); + rollupManager.onSequenceBatches(1, ""); + vm.expectRevert(AllSequencedMustBeVerified.selector); + vm.prank(admin); + rollupManager.updateRollupByRollupAdmin(rollupContract, 0); + } + + function testRevert_updateRollupByRollupAdmin_UpdateToOldRollupTypeID() + public + { + CreateNewRollupEvent memory createNewRollupEvent = _createRollup(); + ITransparentUpgradeableProxy rollupContract = ITransparentUpgradeableProxy( + createNewRollupEvent.data.rollupAddress + ); + vm.expectRevert(UpdateToOldRollupTypeID.selector); + vm.prank(admin); + rollupManager.updateRollupByRollupAdmin(rollupContract, 1); + } + + function testRevert_updateRollupByRollupAdmin_RollupTypeDoesNotExist_NonZero() + public + { + CreateNewRollupEvent memory createNewRollupEvent = _createRollup(); + ITransparentUpgradeableProxy rollupContract = ITransparentUpgradeableProxy( + createNewRollupEvent.data.rollupAddress + ); + uint32 invalidNewRollupTypeID = rollupManager.rollupTypeCount() + 1; + vm.expectRevert(RollupTypeDoesNotExist.selector); + vm.prank(admin); + rollupManager.updateRollupByRollupAdmin( + rollupContract, + invalidNewRollupTypeID + ); + } + + function testRevert_updateRollupByRollupAdmin_RollupMustExist() public { + _createRollup(); + ITransparentUpgradeableProxy rollupContract = ITransparentUpgradeableProxy( + makeAddr("not rollup") + ); + vm.mockCall( + address(rollupContract), + abi.encodePacked(IPolygonRollupBase.admin.selector), + abi.encode(address(this)) + ); + vm.expectRevert(RollupMustExist.selector); + rollupManager.updateRollupByRollupAdmin(rollupContract, 1); + } + + function testRevert_updateRollupByRollupAdmin_RollupTypeObsolete() public { + CreateNewRollupEvent memory createNewRollupEvent = _createRollup(); + ITransparentUpgradeableProxy rollupContract = ITransparentUpgradeableProxy( + createNewRollupEvent.data.rollupAddress + ); + _addSecondRollupType(zkEvm, verifier, 1); + vm.prank(admin); + rollupManager.obsoleteRollupType(2); + vm.expectRevert(RollupTypeObsolete.selector); + vm.prank(admin); + rollupManager.updateRollupByRollupAdmin(rollupContract, 2); + } + + function testRevert_updateRollupByRollupAdmin_UpdateNotCompatible() public { + CreateNewRollupEvent memory createNewRollupEvent = _createRollup(); + ITransparentUpgradeableProxy rollupContract = ITransparentUpgradeableProxy( + createNewRollupEvent.data.rollupAddress + ); + _addSecondRollupType(zkEvm, verifier, 2); + vm.expectRevert(UpdateNotCompatible.selector); + vm.prank(admin); + rollupManager.updateRollupByRollupAdmin(rollupContract, 2); + } + + // @note didn't hit CannotUpdateWithUnconsolidatedPendingState from updateRollupByRollupAdmin + + function test_updateRollupByRollupAdmin() public { + CreateNewRollupEvent memory createNewRollupEvent = _createRollup(); + ITransparentUpgradeableProxy rollupContract = ITransparentUpgradeableProxy( + createNewRollupEvent.data.rollupAddress + ); + PolygonZkEVMEtrog zkEvm2 = new PolygonZkEVMEtrog( + globalExitRoot, + IERC20Upgradeable(address(token)), + IPolygonZkEVMBridgeV2(address(bridge)), + rollupManager + ); + VerifierRollupHelperMock verifier2 = new VerifierRollupHelperMock(); + _addSecondRollupType(zkEvm2, verifier2, 1); + vm.expectCall( + address(rollupContract), + abi.encodeCall( + rollupContract.upgradeToAndCall, + (address(zkEvm2), "") + ) + ); + vm.expectEmit(); + emit UpdateRollup(createNewRollupEvent.rollupID, 2, 1); + vm.prank(admin); + rollupManager.updateRollupByRollupAdmin(rollupContract, 2); + ( + , + , + IVerifierRollup verifier_, + uint64 forkID, + , + , + , + , + , + , + uint64 lastVerifiedBatchBeforeUpgrade, + uint64 rollupTypeID + ) = rollupManager.rollupIDToRollupData(createNewRollupEvent.rollupID); + assertEq(address(verifier_), address(verifier2)); + assertEq(forkID, 2); + assertEq(rollupTypeID, 1); + assertEq(lastVerifiedBatchBeforeUpgrade, 2); + } + + function testRevert_updateRollup_RollupTypeDoesNotExist_Zero() public { + CreateNewRollupEvent memory createNewRollupEvent = _createRollup(); + ITransparentUpgradeableProxy rollupContract = ITransparentUpgradeableProxy( + createNewRollupEvent.data.rollupAddress + ); + vm.expectRevert(RollupTypeDoesNotExist.selector); + vm.prank(timelock); + rollupManager.updateRollup(rollupContract, 0, ""); + } + + function testRevert_updateRollup_RollupTypeDoesNotExist_NonZero() public { + CreateNewRollupEvent memory createNewRollupEvent = _createRollup(); + ITransparentUpgradeableProxy rollupContract = ITransparentUpgradeableProxy( + createNewRollupEvent.data.rollupAddress + ); + uint32 invalidNewRollupTypeID = rollupManager.rollupTypeCount() + 1; + vm.expectRevert(RollupTypeDoesNotExist.selector); + vm.prank(timelock); + rollupManager.updateRollup(rollupContract, invalidNewRollupTypeID, ""); + } + + function testRevert_updateRollup_RollupMustExist() public { + _createRollup(); + ITransparentUpgradeableProxy rollupContract = ITransparentUpgradeableProxy( + makeAddr("not rollup") + ); + vm.mockCall( + address(rollupContract), + abi.encodePacked(IPolygonRollupBase.admin.selector), + abi.encode(address(this)) + ); + vm.expectRevert(RollupMustExist.selector); + vm.prank(timelock); + rollupManager.updateRollup(rollupContract, 1, ""); + } + + function testRevert_updateRollup_UpdateToSameRollupTypeID() public { + CreateNewRollupEvent memory createNewRollupEvent = _createRollup(); + ITransparentUpgradeableProxy rollupContract = ITransparentUpgradeableProxy( + createNewRollupEvent.data.rollupAddress + ); + vm.expectRevert(UpdateToSameRollupTypeID.selector); + vm.prank(timelock); + rollupManager.updateRollup( + rollupContract, + createNewRollupEvent.data.rollupTypeID, + "" + ); + } + + function testRevert_updateRollup_RollupTypeObsolete() public { + CreateNewRollupEvent memory createNewRollupEvent = _createRollup(); + ITransparentUpgradeableProxy rollupContract = ITransparentUpgradeableProxy( + createNewRollupEvent.data.rollupAddress + ); + _addSecondRollupType(zkEvm, verifier, 1); + vm.prank(admin); + rollupManager.obsoleteRollupType(2); + vm.expectRevert(RollupTypeObsolete.selector); + vm.prank(timelock); + rollupManager.updateRollup(rollupContract, 2, ""); + } + + function testRevert_updateRollup_UpdateNotCompatible() public { + CreateNewRollupEvent memory createNewRollupEvent = _createRollup(); + ITransparentUpgradeableProxy rollupContract = ITransparentUpgradeableProxy( + createNewRollupEvent.data.rollupAddress + ); + _addSecondRollupType(zkEvm, verifier, 2); + vm.expectRevert(UpdateNotCompatible.selector); + vm.prank(timelock); + rollupManager.updateRollup(rollupContract, 2, ""); + } + + function test_updateRollup_CannotUpdateWithUnconsolidatedPendingState() + public + { + CreateNewRollupEvent memory createNewRollupEvent = _createRollup(); + vm.revertTo( + snapshot["_createRollup"]["before verifyBatchesTrustedAggregator"] + ); + vm.warp(99999999); + bytes32[24] memory proof; + rollupManager.verifyBatches( + 1, + 0, + 0, + 1, + 0xbc02d42b4cf5e49efd5b4d51ff4d4f4981128a48d603e2f73be9338a4fb09fb4, + 0x0000000000000000000000000000000000000000000000000000000000000123, + beneficiary, + proof + ); + ITransparentUpgradeableProxy rollupContract = ITransparentUpgradeableProxy( + createNewRollupEvent.data.rollupAddress + ); + _addSecondRollupType(zkEvm, verifier, 1); + vm.expectRevert(CannotUpdateWithUnconsolidatedPendingState.selector); + vm.prank(timelock); + rollupManager.updateRollup(rollupContract, 2, ""); + } + + function test_updateRollup() public { + CreateNewRollupEvent memory createNewRollupEvent = _createRollup(); + ITransparentUpgradeableProxy rollupContract = ITransparentUpgradeableProxy( + createNewRollupEvent.data.rollupAddress + ); + PolygonZkEVMEtrog zkEvm2 = new PolygonZkEVMEtrog( + globalExitRoot, + IERC20Upgradeable(address(token)), + IPolygonZkEVMBridgeV2(address(bridge)), + rollupManager + ); + VerifierRollupHelperMock verifier2 = new VerifierRollupHelperMock(); + _addSecondRollupType(zkEvm2, verifier2, 1); + bytes memory data = abi.encodePacked( + zkEvm2.calculatePolPerForceBatch.selector + ); + vm.expectCall( + address(rollupContract), + abi.encodeCall( + rollupContract.upgradeToAndCall, + (address(zkEvm2), data) + ) + ); + vm.expectEmit(); + emit UpdateRollup(createNewRollupEvent.rollupID, 2, 1); + vm.prank(timelock); + rollupManager.updateRollup(rollupContract, 2, data); + ( + , + , + IVerifierRollup verifier_, + uint64 forkID, + , + , + , + , + , + , + uint64 lastVerifiedBatchBeforeUpgrade, + uint64 rollupTypeID + ) = rollupManager.rollupIDToRollupData(createNewRollupEvent.rollupID); + assertEq(address(verifier_), address(verifier2)); + assertEq(forkID, 2); + assertEq(rollupTypeID, 1); + assertEq(lastVerifiedBatchBeforeUpgrade, 2); + } + + function testRevert_rollbackBatches_RollupMustExist() public { + IPolygonRollupBase rollupContract = IPolygonRollupBase( + makeAddr("not rollup") + ); + vm.mockCall( + address(rollupContract), + abi.encodePacked(IPolygonRollupBase.admin.selector), + abi.encode(address(this)) + ); + vm.expectRevert(RollupMustExist.selector); + rollupManager.rollbackBatches(rollupContract, 0); + } + + function testRevert_rollbackBatches_RollbackBatchIsNotEndOfSequence() + public + { + CreateNewRollupEvent memory createNewRollupEvent = _createRollup(); + IPolygonRollupBase rollupContract = IPolygonRollupBase( + createNewRollupEvent.data.rollupAddress + ); + vm.prank(address(rollupContract)); + rollupManager.onSequenceBatches(2, ""); + vm.expectRevert(RollbackBatchIsNotEndOfSequence.selector); + vm.prank(timelock); + rollupManager.rollbackBatches(rollupContract, 2); + } + + function testRevert_onSequenceBatches_OnlyNotEmergencyState() public { + vm.prank(emergencyCouncil); + rollupManager.activateEmergencyState(); + vm.expectRevert(OnlyNotEmergencyState.selector); + rollupManager.onSequenceBatches(0, ""); + } + + // @todo verifyBatches + + function test_verifyBatchesTrustedAggregator_CleansState() public { + CreateNewRollupEvent memory createNewRollupEvent = _createRollup(); + vm.revertTo( + snapshot["_createRollup"]["before verifyBatchesTrustedAggregator"] + ); + vm.warp(99999999); + bytes32[24] memory proof; + rollupManager.verifyBatches( + 1, + 0, + 0, + 1, + 0xbc02d42b4cf5e49efd5b4d51ff4d4f4981128a48d603e2f73be9338a4fb09fb4, + 0x0000000000000000000000000000000000000000000000000000000000000123, + beneficiary, + proof + ); + ITransparentUpgradeableProxy rollupContract = ITransparentUpgradeableProxy( + createNewRollupEvent.data.rollupAddress + ); + vm.prank(address(rollupContract)); + rollupManager.onSequenceBatches(1, hex"01"); + vm.prank(address(rollupContract)); + rollupManager.onSequenceBatches(1, hex"01"); + ( + , + , + , + , + , + , + , + uint64 lastPendingState, + uint64 lastPendingStateConsolidated, + , + , + + ) = rollupManager.rollupIDToRollupData(createNewRollupEvent.rollupID); + assertTrue(lastPendingState != 0 || lastPendingStateConsolidated != 0); + vm.prank(trustedAggregator); + rollupManager.verifyBatchesTrustedAggregator( + 1, + 0, + 0, + 2, + 0xbc02d42b4cf5e49efd5b4d51ff4d4f4981128a48d603e2f73be9338a4fb09fb4, + 0x0000000000000000000000000000000000000000000000000000000000000123, + beneficiary, + proof + ); + ( + , + , + , + , + , + , + , + lastPendingState, + lastPendingStateConsolidated, + , + , + + ) = rollupManager.rollupIDToRollupData(createNewRollupEvent.rollupID); + assertEq(lastPendingState, 0); + assertEq(lastPendingStateConsolidated, 0); + } + + function testRevert_verifyBatchesTrustedAggregator_InitBatchMustMatchCurrentForkID() + public + { + CreateNewRollupEvent memory createNewRollupEvent = _createRollup(); + ITransparentUpgradeableProxy rollupContract = ITransparentUpgradeableProxy( + createNewRollupEvent.data.rollupAddress + ); + PolygonZkEVMEtrog zkEvm2 = new PolygonZkEVMEtrog( + globalExitRoot, + IERC20Upgradeable(address(token)), + IPolygonZkEVMBridgeV2(address(bridge)), + rollupManager + ); + VerifierRollupHelperMock verifier2 = new VerifierRollupHelperMock(); + _addSecondRollupType(zkEvm2, verifier2, 1); + bytes memory data = abi.encodePacked( + zkEvm2.calculatePolPerForceBatch.selector + ); + vm.prank(timelock); + rollupManager.updateRollup(rollupContract, 2, data); + rollupManager.rollupIDToRollupData(createNewRollupEvent.rollupID); + bytes32[24] memory proof; + vm.expectRevert(InitBatchMustMatchCurrentForkID.selector); + vm.prank(trustedAggregator); + rollupManager.verifyBatchesTrustedAggregator( + 1, + 0, + 0, + 1, + 0xbc02d42b4cf5e49efd5b4d51ff4d4f4981128a48d603e2f73be9338a4fb09fb4, + 0x0000000000000000000000000000000000000000000000000000000000000123, + beneficiary, + proof + ); + } + + function testRevert_verifyBatchesTrustedAggregator_PendingStateDoesNotExist() + public + { + _createRollup(); + vm.revertTo( + snapshot["_createRollup"]["before verifyBatchesTrustedAggregator"] + ); + bytes32[24] memory proof; + vm.expectRevert(PendingStateDoesNotExist.selector); + vm.prank(trustedAggregator); + rollupManager.verifyBatchesTrustedAggregator( + 1, + 1, + 0, + 1, + 0xbc02d42b4cf5e49efd5b4d51ff4d4f4981128a48d603e2f73be9338a4fb09fb4, + 0x0000000000000000000000000000000000000000000000000000000000000123, + beneficiary, + proof + ); + } + + function testRevert_verifyBatchesTrustedAggregator_InitNumBatchDoesNotMatchPendingState() + public + { + CreateNewRollupEvent memory createNewRollupEvent = _createRollup(); + vm.revertTo( + snapshot["_createRollup"]["before verifyBatchesTrustedAggregator"] + ); + vm.warp(99999999); + bytes32[24] memory proof; + rollupManager.verifyBatches( + 1, + 0, + 0, + 1, + 0xbc02d42b4cf5e49efd5b4d51ff4d4f4981128a48d603e2f73be9338a4fb09fb4, + 0x0000000000000000000000000000000000000000000000000000000000000123, + beneficiary, + proof + ); + ITransparentUpgradeableProxy rollupContract = ITransparentUpgradeableProxy( + createNewRollupEvent.data.rollupAddress + ); + vm.prank(address(rollupContract)); + rollupManager.onSequenceBatches(1, hex"01"); + vm.prank(address(rollupContract)); + rollupManager.onSequenceBatches(1, hex"01"); + rollupManager.rollupIDToRollupData(createNewRollupEvent.rollupID); + vm.expectRevert(InitNumBatchDoesNotMatchPendingState.selector); + vm.prank(trustedAggregator); + rollupManager.verifyBatchesTrustedAggregator( + 1, + 1, + 0, + 1, + 0xbc02d42b4cf5e49efd5b4d51ff4d4f4981128a48d603e2f73be9338a4fb09fb4, + 0x0000000000000000000000000000000000000000000000000000000000000123, + beneficiary, + proof + ); + } + + function testRevert_verifyBatchesTrustedAggregator_OldStateRootDoesNotExist() + public + { + _createRollup(); + bytes32[24] memory proof; + vm.expectRevert(OldStateRootDoesNotExist.selector); + vm.prank(trustedAggregator); + rollupManager.verifyBatchesTrustedAggregator( + 1, + 0, + 999999999, + 1, + 0xbc02d42b4cf5e49efd5b4d51ff4d4f4981128a48d603e2f73be9338a4fb09fb4, + 0x0000000000000000000000000000000000000000000000000000000000000123, + beneficiary, + proof + ); + } + + function testRevert_activateEmergencyState_HaltTimeoutNotExpired() public { + _createRollup(); + vm.expectRevert(HaltTimeoutNotExpired.selector); + rollupManager.activateEmergencyState(); + } + + // note mimics it "should check full flow etrog" + function _createRollup() + internal + returns (CreateNewRollupEvent memory createNewRollupEvent) + { + // ADD ROLLUP TYPE + vm.prank(timelock); + rollupManager.addNewRollupType( + address(zkEvm), + verifier, + 1, + 1, + bytes32(uint256(1)), + "zkEVM test" + ); + + // CREATE ROLLUP + vm.recordLogs(); + vm.prank(admin); + rollupManager.createNewRollup( + 1, + 1000, + admin, + trustedSequencer, + address(0), + "http://zkevm-json-rpc:8123", + "zkEVM" + ); + Vm.Log[] memory logs = vm.getRecordedLogs(); + CreateNewRollupEventData memory createNewRollupEventData = abi.decode( + logs[2].data, + (CreateNewRollupEventData) + ); + createNewRollupEvent = CreateNewRollupEvent( + uint32(uint256(logs[2].topics[1])), + createNewRollupEventData + ); + + snapshot["_createRollup"]["before verifyBatchesTrustedAggregator"] = vm + .snapshot(); + + // VERIFY BATCH + bytes32[24] memory proof; + vm.prank(trustedAggregator); + rollupManager.verifyBatchesTrustedAggregator( + 1, + 0, + 0, + 1, + 0xbc02d42b4cf5e49efd5b4d51ff4d4f4981128a48d603e2f73be9338a4fb09fb4, + 0x0000000000000000000000000000000000000000000000000000000000000123, + beneficiary, + proof + ); + } + + function _addSecondRollupType( + PolygonZkEVMEtrog zkEvm_, + VerifierRollupHelperMock verifier_, + uint8 rollupCompatibilityID + ) internal { + vm.prank(timelock); + rollupManager.addNewRollupType( + address(zkEvm_), + verifier_, + 2, + rollupCompatibilityID, + bytes32(uint256(2)), + "zkEVM test 2" + ); + } + + function _proxify(address logic) internal returns (address proxy) { + TransparentUpgradeableProxy proxy_ = new TransparentUpgradeableProxy( + logic, + msg.sender, + "" + ); + return (address(proxy_)); + } + + function _preDeployPolygonZkEVMBridgeV2() + internal + returns (address implementation) + { + string[] memory exe = new string[](5); + exe[0] = "forge"; + exe[1] = "inspect"; + exe[2] = "PolygonZkEVMBridgeV2"; + exe[3] = "bytecode"; + exe[ + 4 + ] = "--contracts=contracts-ignored-originals/PolygonZkEVMBridgeV2.sol"; + + bytes memory creationCode = vm.ffi(exe); + implementation = makeAddr("PolygonZkEVMBridgeV2"); + + vm.etch(implementation, creationCode); + (bool success, bytes memory runtimeBytecode) = implementation.call(""); + require(success, "Failed to predeploy PolygonZkEVMBridgeV2"); + vm.etch(implementation, runtimeBytecode); + } +} diff --git a/test/PolygonValidiumEtrog.t.sol b/test/PolygonValidiumEtrog.t.sol new file mode 100644 index 000000000..5f60dbbd0 --- /dev/null +++ b/test/PolygonValidiumEtrog.t.sol @@ -0,0 +1,1349 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "test/util/TestHelpers.sol"; + +import {PolygonRollupManager} from "contracts/PolygonRollupManager.sol"; + +import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import {IPolygonValidium} from "contracts/interfaces/IPolygonValidium.sol"; +import {IPolygonZkEVMBridgeV2Extended} from "contracts/interfaces/IPolygonZkEVMBridgeV2Extended.sol"; +import {IPolygonZkEVMBridgeV2} from "contracts/interfaces/IPolygonZkEVMBridgeV2.sol"; + +import {ERC20PermitMockDeployer} from "script/deployers/ERC20PermitMockDeployer.s.sol"; +import {PolygonDataCommitteeDeployer} from "script/deployers/PolygonDataCommitteeDeployer.s.sol"; +import {PolygonRollupManagerEmptyMockDeployer} from "script/deployers/PolygonRollupManagerEmptyMockDeployer.s.sol"; +import {PolygonZkEVMGlobalExitRootV2Deployer} from "script/deployers/PolygonZkEVMGlobalExitRootV2Deployer.s.sol"; + +import "@openzeppelin/contracts/utils/Strings.sol"; +import "script/deployers/PolygonValidiumEtrogDeployer.s.sol"; + +contract PolygonValidiumEtrogTest is + Test, + TestHelpers, + ERC20PermitMockDeployer, + PolygonDataCommitteeDeployer, + PolygonRollupManagerEmptyMockDeployer, + PolygonValidiumEtrogDeployer, + PolygonZkEVMGlobalExitRootV2Deployer +{ + address admin = makeAddr("admin"); + address destinationAddress = makeAddr("destinationAddress"); + address proxyAdminOwner = makeAddr("proxyAdminOwner"); + address polTokenOwner = makeAddr("polTokenOwner"); + address trustedSequencer = makeAddr("trustedSequencer"); + address trustedAggregator = makeAddr("trustedAggregator"); + + IPolygonZkEVMBridgeV2Extended polygonZkEVMBridge; + IERC20Upgradeable pol; + PolygonRollupManager polygonRollupManager; + + string constant tokenName = "Polygon"; + string constant tokenSymbol = "POL"; + uint256 constant tokenInitialBalance = 20_000_000 ether; + + string constant networkName = "zkevm"; + string constant sequencerURL = "http://zkevm-json-rpc:8123"; + + uint256 constant tokenTransferAmount = 10 ether; + + uint256 public constant TIMESTAMP_RANGE = 36; + + bytes tokenMetaData = abi.encode(tokenName, tokenSymbol, tokenDecimals); + + uint64 constant FORCE_BATCH_TIMEOUT = 60 * 60 * 24 * 5; // 5 days + uint64 constant SIGNATURE_BYTES = 32 + 32 + 1; + uint64 constant EFFECTIVE_PERCENTAGE_BYTES = 1; + uint64 internal constant MAX_VERIFY_BATCHES = 1000; + uint64 internal constant HALT_AGGREGATION_TIMEOUT = 1 weeks; + + uint32 networkIDMainnet = 0; + uint32 networkIDRollup = 1; + uint32 l1InfoRootIndex = 1; + + uint8 constant tokenDecimals = 18; + uint8 constant LEAF_TYPE_MESSAGE = 1; + bytes l2TxData = "0x123456"; + + struct CommitteeMember { + address addr; + uint256 privateKey; + } + + event SequenceBatches(uint64 indexed numBatch, bytes32 l1InfoRoot); + event ForceBatch( + uint64 indexed forceBatchNum, + bytes32 lastGlobalExitRoot, + address sequencer, + bytes transactions + ); + event SequenceForceBatches(uint64 indexed numBatch); + event InitialSequenceBatches( + bytes transactions, + bytes32 lastGlobalExitRoot, + address sequencer + ); + event VerifyBatches( + uint64 indexed numBatch, + bytes32 stateRoot, + address indexed aggregator + ); + event RollbackBatches( + uint64 indexed targetBatch, + bytes32 accInputHashToRollback + ); + event SetDataAvailabilityProtocol(address newDataAvailabilityProtocol); + event SwitchSequenceWithDataAvailability(); + event SetTrustedSequencer(address newTrustedSequencer); + event SetTrustedSequencerURL(string newTrustedSequencerURL); + event SetForceBatchTimeout(uint64 newforceBatchTimeout); + event SetForceBatchAddress(address newForceBatchAddress); + event TransferAdminRole(address newPendingAdmin); + event AcceptAdminRole(address newAdmin); + event CommitteeUpdated(bytes32 committeeHash); + + function setUp() public { + pol = IERC20Upgradeable( + deployERC20PermitMockImplementation( + tokenName, + tokenSymbol, + polTokenOwner, + tokenInitialBalance + ) + ); + + IPolygonZkEVMBridgeV2Extended polygonZkEVMBridgeImplementation = IPolygonZkEVMBridgeV2Extended( + _preDeployPolygonZkEVMBridgeV2() + ); + polygonZkEVMBridge = IPolygonZkEVMBridgeV2Extended( + _proxify(address(polygonZkEVMBridgeImplementation)) + ); + + polygonRollupManager = PolygonRollupManager( + deployPolygonRollupManagerEmptyMockImplementation() + ); + + deployPolygonZkEVMGlobalExitRootV2Transparent( + proxyAdminOwner, + address(polygonRollupManager), + address(polygonZkEVMBridge) + ); + + polygonZkEVMBridge.initialize( + networkIDMainnet, + address(0), + 0, + polygonZkEVMGlobalExitRootV2, + address(polygonRollupManager), + bytes("") + ); + + vm.prank(polTokenOwner); + pol.transfer(trustedSequencer, 1_000 ether); + + polygonValidiumEtrog = PolygonValidiumEtrog( + deployPolygonValidiumEtrogImplementation( + polygonZkEVMGlobalExitRootV2, + pol, + IPolygonZkEVMBridgeV2(address(polygonZkEVMBridge)), + polygonRollupManager + ) + ); + + deployPolygonDataCommitteeTransparent(proxyAdminOwner); + } + + function testRevert_initialize_onlyRollupManager() public { + vm.expectRevert(IPolygonZkEVMVEtrogErrors.OnlyRollupManager.selector); + _initializePolygonValidiumEtrog(); + } + + function test_initialize() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + assertEq(polygonValidiumEtrog.admin(), admin); + assertEq(polygonValidiumEtrog.trustedSequencer(), trustedSequencer); + assertEq(polygonValidiumEtrog.trustedSequencerURL(), sequencerURL); + assertEq(polygonValidiumEtrog.networkName(), networkName); + assertEq(polygonValidiumEtrog.forceBatchTimeout(), FORCE_BATCH_TIMEOUT); + } + + function testRevert_initialize_alreadyInitialized() public { + vm.startPrank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + vm.expectRevert("Initializable: contract is already initialized"); + _initializePolygonValidiumEtrog(); + vm.stopPrank(); + } + + function testRevert_adminFunctions() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + bytes4 selector = IPolygonZkEVMErrors.OnlyAdmin.selector; + + vm.expectRevert(selector); + polygonValidiumEtrog.switchSequenceWithDataAvailability(true); + + vm.expectRevert(selector); + polygonValidiumEtrog.setDataAvailabilityProtocol(polygonDataCommittee); + + vm.expectRevert(selector); + polygonValidiumEtrog.setForceBatchTimeout(1); + + vm.expectRevert(selector); + polygonValidiumEtrog.setTrustedSequencer(address(0)); + + vm.expectRevert(selector); + polygonValidiumEtrog.setTrustedSequencerURL(""); + + vm.expectRevert(selector); + polygonValidiumEtrog.transferAdminRole(address(0)); + + vm.expectRevert(selector); + polygonValidiumEtrog.setForceBatchAddress(address(0)); + + vm.expectRevert(IPolygonZkEVMErrors.OnlyPendingAdmin.selector); + polygonValidiumEtrog.acceptAdminRole(); + } + + function test_adminFunctions() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + vm.startPrank(admin); + + vm.expectEmit(); + emit SwitchSequenceWithDataAvailability(); + polygonValidiumEtrog.switchSequenceWithDataAvailability(true); + assertEq( + polygonValidiumEtrog.isSequenceWithDataAvailabilityAllowed(), + true + ); + + vm.expectEmit(); + emit SetDataAvailabilityProtocol(address(polygonDataCommittee)); + polygonValidiumEtrog.setDataAvailabilityProtocol(polygonDataCommittee); + assertEq( + address(polygonValidiumEtrog.dataAvailabilityProtocol()), + address(polygonDataCommittee) + ); + + vm.expectEmit(); + emit SetForceBatchTimeout(0); + polygonValidiumEtrog.setForceBatchTimeout(0); + assertEq(polygonValidiumEtrog.forceBatchTimeout(), 0); + + vm.expectEmit(); + emit SetTrustedSequencer(makeAddr("newTrustedSequencer")); + polygonValidiumEtrog.setTrustedSequencer( + makeAddr("newTrustedSequencer") + ); + assertEq( + polygonValidiumEtrog.trustedSequencer(), + makeAddr("newTrustedSequencer") + ); + + vm.expectEmit(); + emit SetTrustedSequencerURL("http://zkevm-json-rpc:8145"); + polygonValidiumEtrog.setTrustedSequencerURL( + "http://zkevm-json-rpc:8145" + ); + assertEq( + polygonValidiumEtrog.trustedSequencerURL(), + "http://zkevm-json-rpc:8145" + ); + + vm.expectEmit(); + emit SetForceBatchAddress(makeAddr("newForceBatchAddress")); + polygonValidiumEtrog.setForceBatchAddress( + makeAddr("newForceBatchAddress") + ); + assertEq( + polygonValidiumEtrog.forceBatchAddress(), + makeAddr("newForceBatchAddress") + ); + + address newAdmin = makeAddr("newAdmin"); + + vm.expectEmit(); + emit TransferAdminRole(newAdmin); + polygonValidiumEtrog.transferAdminRole(newAdmin); + assertEq(polygonValidiumEtrog.pendingAdmin(), newAdmin); + + vm.stopPrank(); + + vm.prank(newAdmin); + vm.expectEmit(); + emit AcceptAdminRole(makeAddr("newAdmin")); + polygonValidiumEtrog.acceptAdminRole(); + assertEq(polygonValidiumEtrog.admin(), newAdmin); + } + + function testRevert_setForceBatch() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + bytes4 forceBatchNotAllowedSelector = IPolygonZkEVMErrors + .ForceBatchNotAllowed + .selector; + + vm.expectRevert(forceBatchNotAllowedSelector); + polygonValidiumEtrog.forceBatch(bytes(""), 0); + + vm.expectRevert(forceBatchNotAllowedSelector); + polygonValidiumEtrog.sequenceForceBatches( + new PolygonRollupBaseEtrog.BatchData[](0) + ); + + vm.startPrank(admin); + + polygonValidiumEtrog.setForceBatchAddress(address(0)); + + vm.expectRevert( + IPolygonZkEVMVEtrogErrors.ForceBatchesDecentralized.selector + ); + polygonValidiumEtrog.setForceBatchAddress(address(0)); + + vm.stopPrank(); + } + + function testRevert_setForceBatchTimeout_invalidRangeForceBatchTimeout() + public + { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + vm.startPrank(admin); + vm.expectRevert( + IPolygonZkEVMErrors.InvalidRangeForceBatchTimeout.selector + ); + polygonValidiumEtrog.setForceBatchTimeout(HALT_AGGREGATION_TIMEOUT + 1); + + vm.expectRevert( + IPolygonZkEVMErrors.InvalidRangeForceBatchTimeout.selector + ); + polygonValidiumEtrog.setForceBatchTimeout(HALT_AGGREGATION_TIMEOUT); + vm.stopPrank(); + } + + function testRevert_generateInitializeTransaction_hugeTokenMetadataNotSupported() + public + { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + bytes memory hugeTokenMetaData = new bytes(1_000_000); // huge data + + vm.expectRevert( + IPolygonZkEVMVEtrogErrors.HugeTokenMetadataNotSupported.selector + ); + polygonValidiumEtrog.generateInitializeTransaction( + networkIDRollup, + address(0), + networkIDMainnet, + hugeTokenMetaData + ); + } + + function test_generateInitializeTransaction() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + uint64 timestamp = uint64(block.timestamp); + bytes32 blockParentHash = blockhash(block.number - 1); + bytes memory initialTx = polygonValidiumEtrog + .generateInitializeTransaction( + networkIDRollup, + address(0), + networkIDMainnet, + bytes("") + ); + + bytes32 initExpectedAccInputHash = _calculateAccInputHash( + bytes32(0), + keccak256(initialTx), + polygonZkEVMGlobalExitRootV2.getLastGlobalExitRoot(), + timestamp, + trustedSequencer, + blockParentHash + ); + assertEq( + polygonValidiumEtrog.lastAccInputHash(), + initExpectedAccInputHash + ); + } + + function testRevert_sequenceBatches_sequenceWithDataAvailabilityNotAllowed() + public + { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[](1); + + vm.expectRevert( + IPolygonValidium.SequenceWithDataAvailabilityNotAllowed.selector + ); + polygonValidiumEtrog.sequenceBatches( + batchData, + l1InfoRootIndex, + uint64(block.timestamp), + bytes32(0), + trustedAggregator + ); + } + + function testRevert_sequenceBatches_onlyTrustedSequencer() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + vm.prank(admin); + polygonValidiumEtrog.switchSequenceWithDataAvailability(true); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[](1); + + vm.expectRevert(IPolygonZkEVMErrors.OnlyTrustedSequencer.selector); + polygonValidiumEtrog.sequenceBatches( + batchData, + l1InfoRootIndex, + uint64(block.timestamp), + bytes32(0), + trustedAggregator + ); + } + + function testRevert_sequenceBatches_sequenceZeroBatches() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + vm.prank(admin); + polygonValidiumEtrog.switchSequenceWithDataAvailability(true); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[](0); // Empty batchData + + vm.prank(trustedSequencer); + vm.expectRevert(IPolygonZkEVMErrors.SequenceZeroBatches.selector); + polygonValidiumEtrog.sequenceBatches( + batchData, + l1InfoRootIndex, + uint64(block.timestamp), + bytes32(0), + trustedAggregator + ); + } + + function testRevert_sequenceBatches_exceedMaxVerifyBatches() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + vm.prank(admin); + polygonValidiumEtrog.switchSequenceWithDataAvailability(true); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[]( + MAX_VERIFY_BATCHES + 1 + ); // Exceed max verify batches + + vm.prank(trustedSequencer); + vm.expectRevert(IPolygonZkEVMErrors.ExceedMaxVerifyBatches.selector); + polygonValidiumEtrog.sequenceBatches( + batchData, + l1InfoRootIndex, + uint64(block.timestamp), + bytes32(0), + trustedAggregator + ); + } + + function testRevert_sequenceBatches_maxTimestampSequenceInvalid() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + vm.prank(admin); + polygonValidiumEtrog.switchSequenceWithDataAvailability(true); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[](1); + + vm.prank(trustedSequencer); + vm.expectRevert( + IPolygonZkEVMVEtrogErrors.MaxTimestampSequenceInvalid.selector + ); + polygonValidiumEtrog.sequenceBatches( + batchData, + l1InfoRootIndex, + uint64(block.timestamp + TIMESTAMP_RANGE + 1), // Exceed max timestamp + bytes32(0), + trustedAggregator + ); + } + + function testRevert_sequenceBatches_l1InfoRootIndexInvalid() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + vm.prank(admin); + polygonValidiumEtrog.switchSequenceWithDataAvailability(true); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[](1); + + vm.prank(trustedSequencer); + vm.expectRevert( + IPolygonZkEVMVEtrogErrors.L1InfoTreeLeafCountInvalid.selector + ); + polygonValidiumEtrog.sequenceBatches( + batchData, + 10, // Invalid l1InfoRootIndex + uint64(block.timestamp), + bytes32(0), + trustedAggregator + ); + } + + function testRevert_sequenceBatches_forcedDataDoesNotMatch() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + vm.prank(admin); + polygonValidiumEtrog.switchSequenceWithDataAvailability(true); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[](1); + batchData[0] = PolygonRollupBaseEtrog.BatchData( + l2TxData, + bytes32(0), + uint64(block.timestamp + 10), // forcedBatch timestamp provided but PolygonRollupBaseEtrog.forcedBatch mapping is empty + bytes32(0) + ); + + polygonZkEVMBridge.bridgeMessage( + networkIDRollup, + destinationAddress, + true, + tokenMetaData + ); + + bytes32 expectedAccInputHash = _calculateAccInputHash( + polygonValidiumEtrog.lastAccInputHash(), + keccak256(l2TxData), + polygonZkEVMGlobalExitRootV2.getLastGlobalExitRoot(), + uint64(block.timestamp + 10), + trustedSequencer, + bytes32(0) + ); + + vm.prank(trustedSequencer); + vm.expectRevert(IPolygonZkEVMErrors.ForcedDataDoesNotMatch.selector); + polygonValidiumEtrog.sequenceBatches( + batchData, + l1InfoRootIndex, + uint64(block.timestamp + 10), + expectedAccInputHash, + trustedAggregator + ); + } + + function testRevert_sequenceBatches_transactionsLengthAboveMax() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + vm.prank(admin); + polygonValidiumEtrog.switchSequenceWithDataAvailability(true); + + bytes memory hugeData = new bytes(1_000_000); // huge data + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[](1); + batchData[0] = PolygonRollupBaseEtrog.BatchData( + hugeData, + bytes32(0), + 0, + bytes32(0) + ); + + polygonZkEVMBridge.bridgeMessage( + networkIDRollup, + destinationAddress, + true, + tokenMetaData + ); + + bytes32 expectedAccInputHash = _calculateAccInputHash( + polygonValidiumEtrog.lastAccInputHash(), + keccak256(hugeData), + polygonZkEVMGlobalExitRootV2.getLastGlobalExitRoot(), + uint64(block.timestamp + 10), + trustedSequencer, + bytes32(0) + ); + + vm.prank(trustedSequencer); + vm.expectRevert( + IPolygonZkEVMErrors.TransactionsLengthAboveMax.selector + ); + polygonValidiumEtrog.sequenceBatches( + batchData, + l1InfoRootIndex, + uint64(block.timestamp + 10), + expectedAccInputHash, + trustedAggregator + ); + } + + function test_sequenceBatches() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + vm.prank(admin); + polygonValidiumEtrog.switchSequenceWithDataAvailability(true); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[](1); + batchData[0] = PolygonRollupBaseEtrog.BatchData( + l2TxData, + bytes32(0), + 0, + bytes32(0) + ); + + polygonZkEVMBridge.bridgeMessage( + networkIDRollup, + destinationAddress, + true, + tokenMetaData + ); + + uint256 currentTime = block.timestamp; + bytes32 l1InfoRootHash = polygonZkEVMGlobalExitRootV2.l1InfoRootMap( + l1InfoRootIndex + ); + bytes32 expectedAccInputHash = _calculateAccInputHash( + polygonValidiumEtrog.lastAccInputHash(), + keccak256(l2TxData), + l1InfoRootHash, + uint64(currentTime), + trustedSequencer, + bytes32(0) + ); + + vm.startPrank(trustedSequencer); + pol.approve(address(polygonValidiumEtrog), 100); + + vm.expectEmit(); + emit SequenceBatches(2, l1InfoRootHash); + polygonValidiumEtrog.sequenceBatches( + batchData, + l1InfoRootIndex, + uint64(currentTime), + expectedAccInputHash, + trustedSequencer + ); + vm.stopPrank(); + } + + function testRevert_forceBatch_forceBatchNotAllowed() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + vm.expectRevert(IPolygonZkEVMErrors.ForceBatchNotAllowed.selector); + polygonValidiumEtrog.forceBatch(bytes(""), 0); + } + + function testRevert_forceBatch_forceBatchesNotAllowedOnEmergencyState() + public + { + vm.startPrank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + polygonRollupManager.activateEmergencyState(); + vm.stopPrank(); + + vm.prank(admin); + vm.expectRevert( + IPolygonZkEVMVEtrogErrors + .ForceBatchesNotAllowedOnEmergencyState + .selector + ); + polygonValidiumEtrog.forceBatch(bytes(""), 0); + } + + function testRevert_forceBatch_notEnoughPOLAmount() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + vm.prank(admin); + vm.expectRevert(IPolygonZkEVMVEtrogErrors.NotEnoughPOLAmount.selector); + polygonValidiumEtrog.forceBatch(bytes(""), 0); + } + + function testRevert_forceBatch_transactionsLengthAboveMax() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + bytes memory hugeData = new bytes(1_000_000); // huge data + + vm.prank(admin); + vm.expectRevert( + IPolygonZkEVMErrors.TransactionsLengthAboveMax.selector + ); + polygonValidiumEtrog.forceBatch(hugeData, tokenTransferAmount); + } + + function test_forceBatch() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + uint64 lastForcedBatch = 1; + + vm.prank(polTokenOwner); + pol.transfer(admin, 1_000); + + vm.startPrank(admin); + pol.approve(address(polygonValidiumEtrog), tokenTransferAmount); + + vm.expectEmit(); + emit ForceBatch( + lastForcedBatch, + polygonZkEVMGlobalExitRootV2.getLastGlobalExitRoot(), + admin, + l2TxData + ); + polygonValidiumEtrog.forceBatch(l2TxData, tokenTransferAmount); + vm.stopPrank(); + + assertEq( + polygonValidiumEtrog.calculatePolPerForceBatch(), + polygonRollupManager.getForcedBatchFee() + ); + } + + function test_forceBatch_sendFromContract() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + SendData sendData = new SendData(); + vm.prank(polTokenOwner); + pol.transfer(address(sendData), 1_000); + + bytes memory approveData = abi.encodeWithSelector( + pol.approve.selector, + address(polygonValidiumEtrog), + tokenTransferAmount + ); + sendData.sendData(address(pol), approveData); + + vm.expectEmit(); + emit SetForceBatchAddress(address(sendData)); + vm.prank(admin); + polygonValidiumEtrog.setForceBatchAddress(address(sendData)); + + uint64 lastForcedBatch = polygonValidiumEtrog.lastForceBatch() + 1; // checks increment + bytes32 globalExitRoot = polygonZkEVMGlobalExitRootV2 + .getLastGlobalExitRoot(); + + bytes memory forceBatchData = abi.encodeWithSelector( + polygonValidiumEtrog.forceBatch.selector, + l2TxData, + tokenTransferAmount + ); + vm.expectEmit(); + emit ForceBatch( + lastForcedBatch, + globalExitRoot, + address(sendData), + l2TxData + ); + sendData.sendData(address(polygonValidiumEtrog), forceBatchData); + + assertEq( + polygonValidiumEtrog.calculatePolPerForceBatch(), + polygonRollupManager.getForcedBatchFee() + ); + } + + function testRevert_sequenceForceBatches_haltTimeoutNotExpiredAfterEmergencyState() + public + { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[](1); + + vm.prank(admin); + vm.expectRevert( + IPolygonZkEVMVEtrogErrors + .HaltTimeoutNotExpiredAfterEmergencyState + .selector + ); + polygonValidiumEtrog.sequenceForceBatches(batchData); + } + + function testRevert_sequenceForceBatches_sequenceZeroBatches() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + skip(1 weeks); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[](0); // Empty batchData + + vm.prank(admin); + vm.expectRevert(IPolygonZkEVMErrors.SequenceZeroBatches.selector); + polygonValidiumEtrog.sequenceForceBatches(batchData); + } + + function testRevert_sequenceForceBatches_exceedMaxVerifyBatches() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + skip(1 weeks); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[]( + MAX_VERIFY_BATCHES + 1 + ); // Exceed max verify batches + + vm.prank(admin); + vm.expectRevert(IPolygonZkEVMErrors.ExceedMaxVerifyBatches.selector); + polygonValidiumEtrog.sequenceForceBatches(batchData); + } + + function testRevert_sequenceForceBatches_forceBatchesOverflow() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + skip(1 weeks); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[](1); + + vm.prank(admin); + vm.expectRevert(IPolygonZkEVMErrors.ForceBatchesOverflow.selector); + polygonValidiumEtrog.sequenceForceBatches(batchData); + } + + function testRevert_sequenceForceBatches_forcedDataDoesNotMatch() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + skip(1 weeks); + + vm.prank(polTokenOwner); + pol.transfer(admin, 1_000); + + vm.startPrank(admin); + pol.approve(address(polygonValidiumEtrog), 100); + polygonValidiumEtrog.forceBatch(l2TxData, tokenTransferAmount); + + PolygonRollupBaseEtrog.BatchData[] + memory batchDataArray = new PolygonRollupBaseEtrog.BatchData[](1); + batchDataArray[0] = PolygonRollupBaseEtrog.BatchData( + bytes(""), + bytes32("Random"), + 1000, + bytes32(0) + ); + + vm.expectRevert(IPolygonZkEVMErrors.ForcedDataDoesNotMatch.selector); + polygonValidiumEtrog.sequenceForceBatches(batchDataArray); + vm.stopPrank(); + } + + function testRevert_sequenceForceBatches_forceBatchTimeoutNotExpired() + public + { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + skip(1 weeks); + + vm.prank(polTokenOwner); + pol.transfer(admin, 1_000); + + bytes32 lastGlobalExitRoot = polygonZkEVMGlobalExitRootV2 + .getLastGlobalExitRoot(); + + vm.startPrank(admin); + pol.approve(address(polygonValidiumEtrog), 100); + polygonValidiumEtrog.forceBatch(l2TxData, tokenTransferAmount); + + uint64 currentTime = uint64(block.timestamp); + bytes32 parentHash = blockhash(block.number - 1); + + PolygonRollupBaseEtrog.BatchData[] + memory batchDataArray = new PolygonRollupBaseEtrog.BatchData[](1); + batchDataArray[0] = PolygonRollupBaseEtrog.BatchData( + l2TxData, + lastGlobalExitRoot, + currentTime, + parentHash + ); + + vm.expectRevert( + IPolygonZkEVMErrors.ForceBatchTimeoutNotExpired.selector + ); + polygonValidiumEtrog.sequenceForceBatches(batchDataArray); + vm.stopPrank(); + } + + function test_sequenceForceBatches() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + skip(1 weeks); + + vm.prank(polTokenOwner); + pol.transfer(admin, 1_000); + + bytes32 lastGlobalExitRoot = polygonZkEVMGlobalExitRootV2 + .getLastGlobalExitRoot(); + + vm.startPrank(admin); + pol.approve(address(polygonValidiumEtrog), 100); + polygonValidiumEtrog.forceBatch(l2TxData, tokenTransferAmount); + + uint64 currentTime = uint64(block.timestamp); + bytes32 parentHash = blockhash(block.number - 1); + + PolygonRollupBaseEtrog.BatchData[] + memory batchDataArray = new PolygonRollupBaseEtrog.BatchData[](1); + batchDataArray[0] = PolygonRollupBaseEtrog.BatchData( + l2TxData, + lastGlobalExitRoot, + currentTime, + parentHash + ); + + skip(FORCE_BATCH_TIMEOUT); + uint64 expectedBatchNum = polygonValidiumEtrog.lastForceBatch() + 1; + bytes32 lastAccInputHash = polygonValidiumEtrog.lastAccInputHash(); + + vm.expectEmit(); + emit SequenceForceBatches(expectedBatchNum); + polygonValidiumEtrog.sequenceForceBatches(batchDataArray); + + bytes32 expectedAccInputHash = _calculateAccInputHash( + lastAccInputHash, + keccak256(l2TxData), + lastGlobalExitRoot, + currentTime, + admin, + parentHash + ); + assertEq(polygonValidiumEtrog.lastAccInputHash(), expectedAccInputHash); + vm.stopPrank(); + } + + function testRevert_onVerifyBatches_onlyRollupManager() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + vm.expectRevert(IPolygonZkEVMVEtrogErrors.OnlyRollupManager.selector); + polygonValidiumEtrog.onVerifyBatches(0, bytes32(0), trustedAggregator); + } + + function testRevert_switchSequenceWithDataAvailability_switchToSameValue() + public + { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + vm.prank(admin); + vm.expectRevert(IPolygonValidium.SwitchToSameValue.selector); + polygonValidiumEtrog.switchSequenceWithDataAvailability(false); + } + + function testRevert_sequenceBatchesValidium_onlyTrustedSequencer() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + PolygonValidiumEtrog.ValidiumBatchData[] + memory batchData = new PolygonValidiumEtrog.ValidiumBatchData[](1); + + vm.expectRevert(IPolygonZkEVMErrors.OnlyTrustedSequencer.selector); + polygonValidiumEtrog.sequenceBatchesValidium( + batchData, + l1InfoRootIndex, + uint64(block.timestamp), + bytes32(0), + trustedAggregator, + bytes("") + ); + } + + function testRevert_sequenceBatchesValidium_sequenceZeroBatches() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + vm.prank(admin); + polygonValidiumEtrog.switchSequenceWithDataAvailability(true); + + PolygonValidiumEtrog.ValidiumBatchData[] + memory batchData = new PolygonValidiumEtrog.ValidiumBatchData[](0); // Empty batchData + + vm.prank(trustedSequencer); + vm.expectRevert(IPolygonZkEVMErrors.SequenceZeroBatches.selector); + polygonValidiumEtrog.sequenceBatchesValidium( + batchData, + l1InfoRootIndex, + uint64(block.timestamp), + bytes32(0), + trustedAggregator, + bytes("") + ); + } + + function testRevert_sequenceBatchesValidium_exceedMaxVerifyBatches() + public + { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + vm.prank(admin); + polygonValidiumEtrog.switchSequenceWithDataAvailability(true); + + PolygonValidiumEtrog.ValidiumBatchData[] + memory batchData = new PolygonValidiumEtrog.ValidiumBatchData[]( + MAX_VERIFY_BATCHES + 1 + ); // Exceed max verify batches + + vm.prank(trustedSequencer); + vm.expectRevert(IPolygonZkEVMErrors.ExceedMaxVerifyBatches.selector); + polygonValidiumEtrog.sequenceBatchesValidium( + batchData, + l1InfoRootIndex, + uint64(block.timestamp), + bytes32(0), + trustedAggregator, + bytes("") + ); + } + + function testRevert_sequenceBatchesValidium_maxTimestampSequenceInvalid() + public + { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + vm.prank(admin); + polygonValidiumEtrog.switchSequenceWithDataAvailability(true); + + PolygonValidiumEtrog.ValidiumBatchData[] + memory batchData = new PolygonValidiumEtrog.ValidiumBatchData[](1); + + vm.prank(trustedSequencer); + vm.expectRevert( + IPolygonZkEVMVEtrogErrors.MaxTimestampSequenceInvalid.selector + ); + polygonValidiumEtrog.sequenceBatchesValidium( + batchData, + l1InfoRootIndex, + uint64(block.timestamp + TIMESTAMP_RANGE + 1), // Exceed max timestamp + bytes32(0), + trustedAggregator, + bytes("") + ); + } + + function testRevert_sequenceBatchesValidium_l1InfoRootIndexInvalid() + public + { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + vm.prank(admin); + polygonValidiumEtrog.switchSequenceWithDataAvailability(true); + + PolygonValidiumEtrog.ValidiumBatchData[] + memory batchData = new PolygonValidiumEtrog.ValidiumBatchData[](1); + + vm.prank(trustedSequencer); + vm.expectRevert( + IPolygonZkEVMVEtrogErrors.L1InfoTreeLeafCountInvalid.selector + ); + polygonValidiumEtrog.sequenceBatchesValidium( + batchData, + 10, // Invalid l1InfoRootIndex + uint64(block.timestamp), + bytes32(0), + trustedAggregator, + bytes("") + ); + } + + function testRevert_sequenceBatchesValidium_forcedDataDoesNotMatch() + public + { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + vm.prank(admin); + polygonValidiumEtrog.switchSequenceWithDataAvailability(true); + + PolygonValidiumEtrog.ValidiumBatchData[] + memory batchData = new PolygonValidiumEtrog.ValidiumBatchData[](1); + batchData[0] = PolygonValidiumEtrog.ValidiumBatchData( + keccak256(l2TxData), + bytes32(0), + uint64(block.timestamp + 10), // forcedBatch timestamp provided but PolygonRollupBaseEtrog.forcedBatch mapping is empty + bytes32(0) + ); + + polygonZkEVMBridge.bridgeMessage( + networkIDRollup, + destinationAddress, + true, + tokenMetaData + ); + + bytes32 expectedAccInputHash = _calculateAccInputHash( + polygonValidiumEtrog.lastAccInputHash(), + keccak256(l2TxData), + polygonZkEVMGlobalExitRootV2.getLastGlobalExitRoot(), + uint64(block.timestamp + 10), + trustedSequencer, + bytes32(0) + ); + + vm.prank(trustedSequencer); + vm.expectRevert(IPolygonZkEVMErrors.ForcedDataDoesNotMatch.selector); + polygonValidiumEtrog.sequenceBatchesValidium( + batchData, + l1InfoRootIndex, + uint64(block.timestamp + 10), + expectedAccInputHash, + trustedAggregator, + bytes("") + ); + } + + function testRevert_sequenceBatchesValidium() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonValidiumEtrog(); + + vm.startPrank(admin); + polygonValidiumEtrog.switchSequenceWithDataAvailability(true); + polygonValidiumEtrog.setDataAvailabilityProtocol(polygonDataCommittee); + vm.stopPrank(); + + PolygonValidiumEtrog.ValidiumBatchData[] + memory batchData = new PolygonValidiumEtrog.ValidiumBatchData[](1); + batchData[0] = PolygonValidiumEtrog.ValidiumBatchData( + keccak256(l2TxData), + bytes32(0), + 0, + bytes32(0) + ); + + polygonZkEVMBridge.bridgeMessage( + networkIDRollup, + destinationAddress, + true, + tokenMetaData + ); + + uint64 currentTime = uint64(block.timestamp); + + uint256 numberOfCommitteeMembers = 3; + ( + CommitteeMember[] memory committeeMembers, + bytes memory committeeMemberAddrBytes, + string[] memory committeeMemberUrls + ) = _generateCommitteeMembers(numberOfCommitteeMembers); + + bytes32 expectedAccInputHash = _calculateAccInputHash( + polygonValidiumEtrog.lastAccInputHash(), + keccak256(l2TxData), + polygonZkEVMGlobalExitRootV2.getRoot(), + currentTime, + trustedSequencer, + bytes32(0) + ); + + bytes memory aggrSig = _signAndGetAggregatedSig( + committeeMembers, + expectedAccInputHash + ); + + address committeeOwner = polygonDataCommittee.owner(); + vm.expectEmit(); + emit CommitteeUpdated(keccak256(committeeMemberAddrBytes)); + vm.prank(committeeOwner); + polygonDataCommittee.setupCommittee( + numberOfCommitteeMembers, + committeeMemberUrls, + committeeMemberAddrBytes + ); + + bytes memory dataAvailabilityMessage = abi.encodePacked( + aggrSig, + committeeMemberAddrBytes + ); + + vm.startPrank(trustedSequencer); + pol.approve(address(polygonValidiumEtrog), 100); + polygonValidiumEtrog.sequenceBatchesValidium( + batchData, + l1InfoRootIndex, + currentTime, + expectedAccInputHash, + trustedSequencer, + dataAvailabilityMessage + ); + vm.stopPrank(); + assertEq(polygonValidiumEtrog.lastAccInputHash(), expectedAccInputHash); + } + + function _initializePolygonValidiumEtrog() internal { + polygonValidiumEtrog.initialize( + admin, + trustedSequencer, + networkIDRollup, + address(0), + sequencerURL, + networkName + ); + } + + function _proxify(address logic) internal returns (address proxy) { + TransparentUpgradeableProxy proxy_ = new TransparentUpgradeableProxy( + logic, + msg.sender, + "" + ); + return (address(proxy_)); + } + + function _preDeployPolygonZkEVMBridgeV2() + internal + returns (address implementation) + { + string[] memory exe = new string[](5); + exe[0] = "forge"; + exe[1] = "inspect"; + exe[2] = "PolygonZkEVMBridgeV2"; + exe[3] = "bytecode"; + exe[ + 4 + ] = "--contracts=contracts-ignored-originals/PolygonZkEVMBridgeV2.sol"; + + bytes memory creationCode = vm.ffi(exe); + implementation = makeAddr("PolygonZkEVMBridgeV2"); + + vm.etch(implementation, creationCode); + (bool success, bytes memory runtimeBytecode) = implementation.call(""); + require(success, "Failed to predeploy PolygonZkEVMBridgeV2"); + vm.etch(implementation, runtimeBytecode); + } + + function _calculateAccInputHash( + bytes32 oldAccInputHash, + bytes32 batchHashData, + bytes32 globalExitRoot, + uint64 timestamp, + address sequencerAddress, + bytes32 forcedBlockHash + ) internal pure returns (bytes32) { + return + keccak256( + abi.encodePacked( + oldAccInputHash, + batchHashData, + globalExitRoot, + timestamp, + sequencerAddress, + forcedBlockHash + ) + ); + } + + function _generateCommitteeMembers( + uint256 numOfMembers + ) + internal + returns (CommitteeMember[] memory, bytes memory, string[] memory) + { + CommitteeMember[] memory committeeMembers = new CommitteeMember[]( + numOfMembers + ); + bytes memory committeeMemberAddrBytes = new bytes(0); + string[] memory committeeMemberUrls = new string[]( + committeeMembers.length + ); + for (uint256 i = 0; i < numOfMembers; i++) { + Account memory memberAccount = makeAccount( + string.concat("committeeMember", Strings.toString(i)) + ); + committeeMembers[i] = CommitteeMember( + memberAccount.addr, + memberAccount.key + ); + } + + committeeMembers = _sortMembersByIncrementingAddresses( + committeeMembers + ); + + for (uint256 i = 0; i < committeeMembers.length; i++) { + committeeMemberAddrBytes = abi.encodePacked( + committeeMemberAddrBytes, + committeeMembers[i].addr + ); + committeeMemberUrls[i] = string.concat( + "http://committeeMember", + Strings.toString(i), + ".com" + ); + } + + return ( + committeeMembers, + committeeMemberAddrBytes, + committeeMemberUrls + ); + } + + function _sortMembersByIncrementingAddresses( + CommitteeMember[] memory committeeMembers + ) internal pure returns (CommitteeMember[] memory) { + uint256 n = committeeMembers.length; + bool swapped; + + do { + swapped = false; + for (uint256 i = 0; i < n - 1; i++) { + if (committeeMembers[i].addr > committeeMembers[i + 1].addr) { + CommitteeMember memory temp = committeeMembers[i]; + committeeMembers[i] = committeeMembers[i + 1]; + committeeMembers[i + 1] = temp; + + swapped = true; + } + } + n--; + } while (swapped); + + return committeeMembers; + } + + function _signAndGetAggregatedSig( + CommitteeMember[] memory committeeMembers, + bytes32 inputHash + ) internal pure returns (bytes memory) { + bytes memory aggrSig = bytes(""); + for (uint256 i = 0; i < committeeMembers.length; i++) { + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + committeeMembers[i].privateKey, + inputHash + ); + bytes memory signature = abi.encodePacked(r, s, v); + aggrSig = abi.encodePacked(aggrSig, signature); + } + return aggrSig; + } +} + +contract SendData { + function sendData(address destination, bytes memory data) public { + (bool success, ) = destination.call(data); + require(success, "SendData: failed to send data"); + } +} diff --git a/test/PolygonZkEVMBridgeV2.t.sol b/test/PolygonZkEVMBridgeV2.t.sol new file mode 100644 index 000000000..3366b790a --- /dev/null +++ b/test/PolygonZkEVMBridgeV2.t.sol @@ -0,0 +1,1735 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "test/util/TestHelpers.sol"; + +import {ZkEVMCommon} from "test/util/ZkEVMCommon.sol"; + +import "contracts/interfaces/IPolygonZkEVMBridgeV2Extended.sol"; +import "contracts/lib/TokenWrapped.sol"; +import "contracts/mocks/ERC20PermitMock.sol"; +import "contracts/PolygonZkEVMGlobalExitRootV2.sol"; + +import "script/deployers/PolygonZkEVMGlobalExitRootV2Deployer.s.sol"; + +contract PolygonZkEVMBridgeV2Test is + Test, + TestHelpers, + ZkEVMCommon, + PolygonZkEVMGlobalExitRootV2Deployer +{ + struct ClaimPayload { + bytes32[32] proofMainnet; + bytes32[32] proofRollup; + uint256 globalIndex; + bytes32 mainnetExitRoot; + bytes32 rollupExitRoot; + uint32 originNetwork; + address originAddress; + uint32 destinationNetwork; + address destinationAddress; + uint256 amount; + bytes metadata; + } + + IPolygonZkEVMBridgeV2Extended polygonZkEVMBridge; + ERC20PermitMock pol; + + address deployer = makeAddr("deployer"); + address rollupManager = makeAddr("rollupManager"); + address user = makeAddr("user"); + address polygonZkEVMGlobalExitRootV2ProxyOwner = + makeAddr("polygonZkEVMGlobalExitRootV2ProxyOwner"); + address polOwner = makeAddr("polOwner"); + address destinationAddress = makeAddr("destinationAddress"); + address wethCalculated; + address dummyTokenAddress = makeAddr("dummyTokenAddress"); + + string constant tokenName = "Polygon"; + string constant tokenSymbol = "POL"; + uint256 constant tokenInitialBalance = 20_000_000 ether; + uint256 constant tokenTransferAmount = 10 ether; + + string constant WETH_NAME = "Wrapped Ether"; + string constant WETH_SYMBOL = "WETH"; + + uint256 constant _GLOBAL_INDEX_MAINNET_FLAG = 2 ** 64; + + uint32 constant networkIDMainnet = 0; + uint32 constant networkIDRollup = 1; + uint8 constant tokenDecimals = 18; + uint8 constant WETH_DECIMALS = 18; + uint8 constant LEAF_TYPE_ASSET = 0; + uint8 constant LEAF_TYPE_MESSAGE = 1; + + bytes tokenMetaData = abi.encode(tokenName, tokenSymbol, tokenDecimals); + bytes wethMetaData = abi.encode(WETH_NAME, WETH_SYMBOL, WETH_DECIMALS); + + event BridgeEvent( + uint8 leafType, + uint32 originNetwork, + address originAddress, + uint32 destinationNetwork, + address destinationAddress, + uint256 amount, + bytes metadata, + uint32 depositCount + ); + + event ClaimEvent( + uint256 globalIndex, + uint32 originNetwork, + address originAddress, + address destinationAddress, + uint256 amount + ); + + function setUp() public virtual { + IPolygonZkEVMBridgeV2Extended polygonZkEVMBridgeImplementation = IPolygonZkEVMBridgeV2Extended( + _preDeployPolygonZkEVMBridgeV2() + ); + + polygonZkEVMBridge = IPolygonZkEVMBridgeV2Extended( + _proxify(address(polygonZkEVMBridgeImplementation)) + ); + + deployPolygonZkEVMGlobalExitRootV2Transparent( + polygonZkEVMGlobalExitRootV2ProxyOwner, + rollupManager, + address(polygonZkEVMBridge) + ); + + polygonZkEVMGlobalExitRootV2 = PolygonZkEVMGlobalExitRootV2( + polygonZkEVMGlobalExitRootV2 + ); + + pol = new ERC20PermitMock( + tokenName, + tokenSymbol, + polOwner, + tokenInitialBalance + ); + + bytes memory bytecode = polygonZkEVMBridge + .BASE_INIT_BYTECODE_WRAPPED_TOKEN(); + bytes memory creationCode = abi.encodePacked( + bytecode, + abi.encode(WETH_NAME, WETH_SYMBOL, WETH_DECIMALS) + ); + wethCalculated = vm.computeCreate2Address( + 0, // salt + keccak256(creationCode), + address(polygonZkEVMBridge) + ); + } + + function test_initialize_gasTokenNotPresent() public { + _initializePolygonZkEVMBridge(address(0), networkIDMainnet, bytes("")); + assertEq(polygonZkEVMBridge.networkID(), networkIDMainnet); + assertEq(polygonZkEVMBridge.gasTokenAddress(), address(0)); + assertEq(polygonZkEVMBridge.gasTokenNetwork(), networkIDMainnet); + assertEq( + address(polygonZkEVMBridge.globalExitRootManager()), + address(polygonZkEVMGlobalExitRootV2) + ); + assertEq(polygonZkEVMBridge.polygonRollupManager(), rollupManager); + } + + function test_initialize_gasTokenPresent() public { + _initializePolygonZkEVMBridge( + address(pol), + networkIDMainnet, + tokenMetaData + ); + assertEq(polygonZkEVMBridge.networkID(), networkIDMainnet); + assertEq(polygonZkEVMBridge.WETHToken(), wethCalculated); + assertEq(polygonZkEVMBridge.gasTokenAddress(), address(pol)); + assertEq(polygonZkEVMBridge.gasTokenNetwork(), networkIDMainnet); + assertEq(polygonZkEVMBridge.gasTokenMetadata(), tokenMetaData); + + assertEq( + address(polygonZkEVMBridge.globalExitRootManager()), + address(polygonZkEVMGlobalExitRootV2) + ); + assertEq(polygonZkEVMBridge.polygonRollupManager(), rollupManager); + } + + function testRevert_initialize_gasTokenNetworkMustBeZeroOnEther() public { + vm.expectRevert( + IPolygonZkEVMBridgeV2Extended + .GasTokenNetworkMustBeZeroOnEther + .selector + ); + _initializePolygonZkEVMBridge(address(0), networkIDRollup, bytes("")); + } + + function testRevert_initialize_reinitialize() public { + _initializePolygonZkEVMBridge(address(0), networkIDMainnet, bytes("")); + vm.expectRevert("Initializable: contract is already initialized"); + polygonZkEVMBridge.initialize( + networkIDMainnet, + address(0), + networkIDMainnet, + polygonZkEVMGlobalExitRootV2, + rollupManager, + bytes("") + ); + } + + function testRevert_bridgeAsset_destinationNetworkInvalid() public { + _initializePolygonZkEVMBridge(address(0), networkIDMainnet, bytes("")); + vm.expectRevert( + IPolygonZkEVMBridgeV2Extended.DestinationNetworkInvalid.selector + ); + polygonZkEVMBridge.bridgeAsset( + networkIDMainnet, // same network as bridge + destinationAddress, + tokenTransferAmount, + address(pol), + true, + bytes("") + ); + } + + function testRevert_bridgeAsset_amountDoesNotMatchMsgValue() public { + _initializePolygonZkEVMBridge(address(0), networkIDMainnet, bytes("")); + // sending native asset + vm.expectRevert( + IPolygonZkEVMBridgeV2Extended.AmountDoesNotMatchMsgValue.selector + ); + polygonZkEVMBridge.bridgeAsset( // msg.value = 0 && msg.value != tokenTransferAmount + networkIDRollup, + destinationAddress, + tokenTransferAmount, + address(0), + true, + bytes("") + ); + } + + function testRevert_bridgeAsset_msgValueNotZero() public { + _initializePolygonZkEVMBridge(address(0), networkIDMainnet, bytes("")); + vm.expectRevert(IPolygonZkEVMBridgeV2Extended.MsgValueNotZero.selector); + polygonZkEVMBridge.bridgeAsset{value: tokenTransferAmount}( + networkIDRollup, + destinationAddress, + tokenTransferAmount, + address(pol), + true, + bytes("") + ); + } + + function test_bridgeAsset_assetIsTheGasToken() public { + _initializePolygonZkEVMBridge( + address(pol), + networkIDMainnet, + tokenMetaData + ); + + vm.prank(address(polygonZkEVMBridge)); + TokenWrapped(wethCalculated).mint(user, tokenTransferAmount); + assertEq( + TokenWrapped(wethCalculated).balanceOf(user), + tokenTransferAmount + ); + + vm.prank(user); + vm.expectEmit(); + emit BridgeEvent( + LEAF_TYPE_ASSET, + networkIDMainnet, + address(0), + networkIDRollup, + destinationAddress, + tokenTransferAmount, + bytes(""), + 0 + ); + polygonZkEVMBridge.bridgeAsset( + networkIDRollup, + destinationAddress, + tokenTransferAmount, + wethCalculated, + true, + bytes("") + ); + assertEq(TokenWrapped(wethCalculated).balanceOf(user), 0); + assertEq(polygonZkEVMBridge.lastUpdatedDepositCount(), 1); + } + + function test_bridgeAsset_assetIsTokenOnTheBridgeNetwork() public { + _initializePolygonZkEVMBridge( + address(pol), + networkIDMainnet, + tokenMetaData + ); + + vm.prank(polOwner); + pol.mint(user, tokenTransferAmount); + assertEq(pol.balanceOf(user), tokenTransferAmount); + + vm.startPrank(user); + pol.approve(address(polygonZkEVMBridge), tokenTransferAmount); + + vm.expectEmit(); + emit BridgeEvent( + LEAF_TYPE_ASSET, + networkIDMainnet, + address(pol), + networkIDRollup, + destinationAddress, + tokenTransferAmount, + tokenMetaData, + 0 + ); + polygonZkEVMBridge.bridgeAsset( + networkIDRollup, + destinationAddress, + tokenTransferAmount, + address(pol), + true, + bytes("") + ); + vm.stopPrank(); + assertEq( + pol.balanceOf(address(polygonZkEVMBridge)), + tokenTransferAmount + ); + assertEq(pol.balanceOf(user), 0); + assertEq(polygonZkEVMBridge.lastUpdatedDepositCount(), 1); + } + + function test_bridgeAsset_verifyProof() public { + _initializePolygonZkEVMBridge( + address(pol), + networkIDMainnet, + tokenMetaData + ); + + bytes32 leaf = polygonZkEVMBridge.getLeafValue( + LEAF_TYPE_ASSET, + networkIDMainnet, + address(pol), + networkIDRollup, + destinationAddress, + tokenTransferAmount, + keccak256(abi.encodePacked(tokenMetaData)) + ); + + bytes32[] memory leaves = new bytes32[](1); + leaves[0] = leaf; + string memory encodedLeaves = _encodeLeaves(leaves); + + vm.prank(polOwner); + pol.mint(user, tokenTransferAmount); + + vm.startPrank(user); + pol.approve(address(polygonZkEVMBridge), tokenTransferAmount); + polygonZkEVMBridge.bridgeAsset( + networkIDRollup, + destinationAddress, + tokenTransferAmount, + address(pol), + true, + bytes("") + ); + vm.stopPrank(); + + bytes32 calculatedMainnetExitRoot = _getMerkleTreeRoot(encodedLeaves); + assertEq(polygonZkEVMBridge.getRoot(), calculatedMainnetExitRoot); + + bytes32[32] memory proof = _getProofByIndex(encodedLeaves, "0"); + assertEq( + polygonZkEVMBridge.verifyMerkleProof( + leaf, + proof, + 0, + polygonZkEVMBridge.getRoot() + ), + true + ); + } + + function testRevert_bridgeMessage_noValueInMessagesOnGasTokenNetworks() + public + { + _initializePolygonZkEVMBridge( + address(pol), + networkIDMainnet, + tokenMetaData + ); + + vm.expectRevert( + IPolygonZkEVMBridgeV2Extended + .NoValueInMessagesOnGasTokenNetworks + .selector + ); + polygonZkEVMBridge.bridgeMessage{value: tokenTransferAmount}( + networkIDRollup, + destinationAddress, + true, + bytes("") + ); + } + + function testRevert_bridgeMessage_destinationNetworkInvalid() public { + _initializePolygonZkEVMBridge( + address(pol), + networkIDMainnet, + tokenMetaData + ); + + vm.expectRevert( + IPolygonZkEVMBridgeV2Extended.DestinationNetworkInvalid.selector + ); + polygonZkEVMBridge.bridgeMessage( + networkIDMainnet, // same network as bridge + destinationAddress, + true, + bytes("") + ); + } + + function test_bridgeMessage() public { + _initializePolygonZkEVMBridge(address(0), networkIDMainnet, bytes("")); + + vm.expectEmit(); + emit BridgeEvent( + LEAF_TYPE_MESSAGE, + networkIDMainnet, + address(this), + networkIDRollup, + destinationAddress, + 0, + bytes(""), + 0 + ); + polygonZkEVMBridge.bridgeMessage( + networkIDRollup, + destinationAddress, + true, + bytes("") + ); + } + + function test_bridgeMessage_verifyProof() public { + _initializePolygonZkEVMBridge(address(0), networkIDMainnet, bytes("")); + + bytes32 leaf = polygonZkEVMBridge.getLeafValue( + LEAF_TYPE_MESSAGE, + networkIDMainnet, + address(this), + networkIDRollup, + destinationAddress, + 0, + keccak256(abi.encodePacked(bytes(""))) + ); + + bytes32[] memory leaves = new bytes32[](1); + leaves[0] = leaf; + string memory encodedLeaves = _encodeLeaves(leaves); + + polygonZkEVMBridge.bridgeMessage( + networkIDRollup, + destinationAddress, + true, + bytes("") + ); + + bytes32 calculatedMainnetExitRoot = _getMerkleTreeRoot(encodedLeaves); + assertEq(polygonZkEVMBridge.getRoot(), calculatedMainnetExitRoot); + + bytes32[32] memory proof = _getProofByIndex(encodedLeaves, "0"); + assertEq( + polygonZkEVMBridge.verifyMerkleProof( + leaf, + proof, + 0, + polygonZkEVMBridge.getRoot() + ), + true + ); + } + + function testRevert_bridgeMessageWETH_nativeTokenIsEther() public { + _initializePolygonZkEVMBridge(address(0), networkIDMainnet, bytes("")); + + vm.expectRevert( + IPolygonZkEVMBridgeV2Extended.NativeTokenIsEther.selector + ); + polygonZkEVMBridge.bridgeMessageWETH( + networkIDRollup, + destinationAddress, + tokenTransferAmount, + true, + wethMetaData + ); + } + + function test_bridgeMessageWETH() public { + _initializePolygonZkEVMBridge( + address(pol), + networkIDMainnet, + tokenMetaData + ); + + vm.prank(address(polygonZkEVMBridge)); + TokenWrapped(wethCalculated).mint(user, tokenTransferAmount); + assertEq( + TokenWrapped(wethCalculated).balanceOf(user), + tokenTransferAmount + ); + + vm.prank(user); + vm.expectEmit(); + emit BridgeEvent( + LEAF_TYPE_MESSAGE, + networkIDMainnet, + user, + networkIDRollup, + destinationAddress, + tokenTransferAmount, + wethMetaData, + 0 + ); + polygonZkEVMBridge.bridgeMessageWETH( + networkIDRollup, + destinationAddress, + tokenTransferAmount, + true, + wethMetaData + ); + assertEq(TokenWrapped(wethCalculated).balanceOf(user), 0); + } + + function test_bridgeMessageWETH_verifyProof() public { + _initializePolygonZkEVMBridge( + address(pol), + networkIDMainnet, + tokenMetaData + ); + + bytes32 leaf = polygonZkEVMBridge.getLeafValue( + LEAF_TYPE_MESSAGE, + networkIDMainnet, + user, + networkIDRollup, + destinationAddress, + tokenTransferAmount, + keccak256(abi.encodePacked(bytes(""))) + ); + + bytes32[] memory leaves = new bytes32[](1); + leaves[0] = leaf; + string memory encodedLeaves = _encodeLeaves(leaves); + + vm.prank(address(polygonZkEVMBridge)); + TokenWrapped(wethCalculated).mint(user, tokenTransferAmount); + + vm.startPrank(user); + polygonZkEVMBridge.bridgeMessageWETH( + networkIDRollup, + destinationAddress, + tokenTransferAmount, + true, + bytes("") + ); + vm.stopPrank(); + + bytes32 calculatedMainnetExitRoot = _getMerkleTreeRoot(encodedLeaves); + assertEq(polygonZkEVMBridge.getRoot(), calculatedMainnetExitRoot); + + bytes32[32] memory proof = _getProofByIndex(encodedLeaves, "0"); + assertEq( + polygonZkEVMBridge.verifyMerkleProof( + leaf, + proof, + 0, + polygonZkEVMBridge.getRoot() + ), + true + ); + } + + function testRevert_claimAsset_destinationNetworkInvalid() public { + _initializePolygonZkEVMBridge( + address(pol), + networkIDMainnet, + tokenMetaData + ); + + bytes32[32] memory smtEmptyProof; + ClaimPayload memory payload = ClaimPayload({ + proofMainnet: smtEmptyProof, + proofRollup: smtEmptyProof, + globalIndex: 0, + mainnetExitRoot: bytes32(0), + rollupExitRoot: bytes32(0), + originNetwork: networkIDMainnet, + originAddress: address(pol), + destinationNetwork: networkIDRollup, // invalid destination network + destinationAddress: destinationAddress, + amount: tokenTransferAmount, + metadata: tokenMetaData + }); + + vm.expectRevert( + IPolygonZkEVMBridgeV2Extended.DestinationNetworkInvalid.selector + ); + polygonZkEVMBridge.claimAsset( + payload.proofMainnet, + payload.proofRollup, + payload.globalIndex, + payload.mainnetExitRoot, + payload.rollupExitRoot, + payload.originNetwork, + payload.originAddress, + payload.destinationNetwork, + payload.destinationAddress, + payload.amount, + payload.metadata + ); + } + + function testRevert_claimAsset_globalExitRootInvalid() public { + _initializePolygonZkEVMBridge( + address(pol), + networkIDMainnet, + tokenMetaData + ); + + bytes32[32] memory smtEmptyProof; + ClaimPayload memory payload = ClaimPayload({ + proofMainnet: smtEmptyProof, + proofRollup: smtEmptyProof, + globalIndex: 0, + mainnetExitRoot: bytes32(0), // invalid mainnetExitRoot + rollupExitRoot: bytes32(0), // invalid rollupExitRoot + originNetwork: networkIDMainnet, + originAddress: address(pol), + destinationNetwork: networkIDMainnet, + destinationAddress: destinationAddress, + amount: tokenTransferAmount, + metadata: tokenMetaData + }); + + vm.expectRevert( + IPolygonZkEVMBridgeV2Extended.GlobalExitRootInvalid.selector + ); + polygonZkEVMBridge.claimAsset( + payload.proofMainnet, + payload.proofRollup, + payload.globalIndex, + payload.mainnetExitRoot, + payload.rollupExitRoot, + payload.originNetwork, + payload.originAddress, + payload.destinationNetwork, + payload.destinationAddress, + payload.amount, + payload.metadata + ); + } + + function testRevert_claimAsset_onSameNetwork_invalidSmtProof() public { + _initializePolygonZkEVMBridge( + address(pol), + networkIDMainnet, + tokenMetaData + ); + + bytes32 leaf = polygonZkEVMBridge.getLeafValue( + LEAF_TYPE_ASSET, + networkIDMainnet, + address(pol), + networkIDMainnet, + destinationAddress, + tokenTransferAmount, + keccak256(abi.encodePacked(tokenMetaData)) + ); + + bytes32[] memory leaves = new bytes32[](1); + leaves[0] = leaf; + string memory encodedLeaves = _encodeLeaves(leaves); + + ClaimPayload memory payload; + payload.mainnetExitRoot = _getMerkleTreeRoot(encodedLeaves); + + vm.prank(address(polygonZkEVMBridge)); + polygonZkEVMGlobalExitRootV2.updateExitRoot(payload.mainnetExitRoot); + + assertEq( + polygonZkEVMGlobalExitRootV2.lastMainnetExitRoot(), + payload.mainnetExitRoot + ); + + payload.proofMainnet = _getProofByIndex(encodedLeaves, "0"); + payload.globalIndex = _computeGlobalIndex(0, 0, true); + payload.rollupExitRoot = polygonZkEVMGlobalExitRootV2 + .lastRollupExitRoot(); + payload.originNetwork = networkIDMainnet; + payload.originAddress = address(pol); + payload.destinationNetwork = networkIDMainnet; + payload.destinationAddress = destinationAddress; + payload.amount = tokenTransferAmount + 1; // invalidate proof by changing leaf value + payload.metadata = tokenMetaData; + + vm.expectRevert(IPolygonZkEVMBridgeV2Extended.InvalidSmtProof.selector); + polygonZkEVMBridge.claimAsset( + payload.proofMainnet, + payload.proofMainnet, + payload.globalIndex, + payload.mainnetExitRoot, + payload.rollupExitRoot, + payload.originNetwork, + payload.originAddress, + payload.destinationNetwork, + payload.destinationAddress, + payload.amount, + payload.metadata + ); + } + + function testRevert_claimAsset_onDiffNetwork_invalidSmtProof() public { + _initializePolygonZkEVMBridge( + address(pol), + networkIDMainnet, + tokenMetaData + ); + + bytes32 leaf = polygonZkEVMBridge.getLeafValue( + LEAF_TYPE_ASSET, + networkIDRollup, + address(pol), + networkIDMainnet, + destinationAddress, + tokenTransferAmount, + keccak256(abi.encodePacked(tokenMetaData)) + ); + + bytes32[] memory mainnetLeaves = new bytes32[](2); + mainnetLeaves[0] = leaf; + mainnetLeaves[1] = leaf; + string memory encodedLeaves = _encodeLeaves(mainnetLeaves); + + ClaimPayload memory payload; + bytes32 calculatedMainnetExitRoot = _getMerkleTreeRoot(encodedLeaves); + + bytes32[] memory rollupLeaves = new bytes32[](10); + for (uint256 i = 0; i < 10; i++) { + rollupLeaves[i] = calculatedMainnetExitRoot; + } + string memory encodedRollupLeaves = _encodeLeaves(rollupLeaves); + payload.rollupExitRoot = _getMerkleTreeRoot(encodedRollupLeaves); + + vm.prank(rollupManager); + polygonZkEVMGlobalExitRootV2.updateExitRoot(payload.rollupExitRoot); + + assertEq( + polygonZkEVMGlobalExitRootV2.lastRollupExitRoot(), + payload.rollupExitRoot + ); + + payload.mainnetExitRoot = polygonZkEVMGlobalExitRootV2 + .lastMainnetExitRoot(); + + assertEq( + polygonZkEVMGlobalExitRootV2.getLastGlobalExitRoot(), + keccak256( + abi.encodePacked( + payload.mainnetExitRoot, + payload.rollupExitRoot + ) + ) + ); + + payload.proofMainnet = _getProofByIndex(encodedLeaves, "0"); + payload.proofRollup = _getProofByIndex(encodedRollupLeaves, "5"); + payload.globalIndex = _computeGlobalIndex(0, 5, false); + payload.originNetwork = networkIDRollup; + payload.originAddress = address(pol); + payload.destinationNetwork = networkIDMainnet; + payload.destinationAddress = destinationAddress; + payload.amount = tokenTransferAmount + 1; // invalidate proof by changing leaf value + payload.metadata = tokenMetaData; + + vm.expectRevert(IPolygonZkEVMBridgeV2Extended.InvalidSmtProof.selector); + polygonZkEVMBridge.claimAsset( + payload.proofMainnet, + payload.proofRollup, + payload.globalIndex, + payload.mainnetExitRoot, + payload.rollupExitRoot, + payload.originNetwork, + payload.originAddress, + payload.destinationNetwork, + payload.destinationAddress, + payload.amount, + payload.metadata + ); + } + + function testRevert_claimAsset_alreadyClaimed() public { + _initializePolygonZkEVMBridge( + address(pol), + networkIDMainnet, + tokenMetaData + ); + + bytes32 leaf = polygonZkEVMBridge.getLeafValue( + LEAF_TYPE_ASSET, + networkIDMainnet, + address(pol), + networkIDMainnet, + destinationAddress, + tokenTransferAmount, + keccak256(abi.encodePacked(tokenMetaData)) + ); + + bytes32[] memory leaves = new bytes32[](1); + leaves[0] = leaf; + string memory encodedLeaves = _encodeLeaves(leaves); + + ClaimPayload memory payload; + payload.mainnetExitRoot = _getMerkleTreeRoot(encodedLeaves); + + vm.prank(address(polygonZkEVMBridge)); + polygonZkEVMGlobalExitRootV2.updateExitRoot(payload.mainnetExitRoot); + + assertEq( + polygonZkEVMGlobalExitRootV2.lastMainnetExitRoot(), + payload.mainnetExitRoot + ); + + payload.proofMainnet = _getProofByIndex(encodedLeaves, "0"); + payload.globalIndex = _computeGlobalIndex(0, 0, true); + payload.rollupExitRoot = polygonZkEVMGlobalExitRootV2 + .lastRollupExitRoot(); + payload.originNetwork = networkIDMainnet; + payload.originAddress = address(pol); + payload.destinationNetwork = networkIDMainnet; + payload.destinationAddress = destinationAddress; + payload.amount = tokenTransferAmount; + payload.metadata = tokenMetaData; + + vm.deal(address(polygonZkEVMBridge), tokenTransferAmount); + + polygonZkEVMBridge.claimAsset( + payload.proofMainnet, + payload.proofMainnet, + payload.globalIndex, + payload.mainnetExitRoot, + payload.rollupExitRoot, + payload.originNetwork, + payload.originAddress, + payload.destinationNetwork, + payload.destinationAddress, + payload.amount, + payload.metadata + ); + assertEq(destinationAddress.balance, tokenTransferAmount); + assertEq(address(polygonZkEVMBridge).balance, 0); + + vm.expectRevert(IPolygonZkEVMBridgeV2Extended.AlreadyClaimed.selector); + polygonZkEVMBridge.claimAsset( + payload.proofMainnet, + payload.proofMainnet, + payload.globalIndex, + payload.mainnetExitRoot, + payload.rollupExitRoot, + payload.originNetwork, + payload.originAddress, + payload.destinationNetwork, + payload.destinationAddress, + payload.amount, + payload.metadata + ); + } + + function test_claimAsset_assetIsNativeToken() public { + _initializePolygonZkEVMBridge(address(0), networkIDMainnet, bytes("")); + + bytes32 leaf = polygonZkEVMBridge.getLeafValue( + LEAF_TYPE_ASSET, + networkIDMainnet, + address(0), + networkIDMainnet, + destinationAddress, + tokenTransferAmount, + keccak256(abi.encodePacked(bytes(""))) + ); + + bytes32[] memory leaves = new bytes32[](1); + leaves[0] = leaf; + string memory encodedLeaves = _encodeLeaves(leaves); + + ClaimPayload memory payload; + payload.mainnetExitRoot = _getMerkleTreeRoot(encodedLeaves); + + vm.prank(address(polygonZkEVMBridge)); + polygonZkEVMGlobalExitRootV2.updateExitRoot(payload.mainnetExitRoot); + + assertEq( + polygonZkEVMGlobalExitRootV2.lastMainnetExitRoot(), + payload.mainnetExitRoot + ); + + payload.proofMainnet = _getProofByIndex(encodedLeaves, "0"); + payload.globalIndex = _computeGlobalIndex(0, 0, true); + payload.rollupExitRoot = polygonZkEVMGlobalExitRootV2 + .lastRollupExitRoot(); + payload.originNetwork = networkIDMainnet; + payload.originAddress = address(0); + payload.destinationNetwork = networkIDMainnet; + payload.destinationAddress = destinationAddress; + payload.amount = tokenTransferAmount; + payload.metadata = bytes(""); + + vm.deal(address(polygonZkEVMBridge), tokenTransferAmount); + + vm.expectEmit(); + emit ClaimEvent( + payload.globalIndex, + payload.originNetwork, + payload.originAddress, + payload.destinationAddress, + payload.amount + ); + polygonZkEVMBridge.claimAsset( + payload.proofMainnet, + payload.proofMainnet, + payload.globalIndex, + payload.mainnetExitRoot, + payload.rollupExitRoot, + payload.originNetwork, + payload.originAddress, + payload.destinationNetwork, + payload.destinationAddress, + payload.amount, + payload.metadata + ); + assertEq(destinationAddress.balance, tokenTransferAmount); + assertEq(address(polygonZkEVMBridge).balance, 0); + } + + function test_claimAsset_assetIsNativeTokenWETH() public { + _initializePolygonZkEVMBridge( + address(pol), + networkIDMainnet, + tokenMetaData + ); + + bytes32 leaf = polygonZkEVMBridge.getLeafValue( + LEAF_TYPE_ASSET, + networkIDMainnet, + address(0), + networkIDMainnet, + destinationAddress, + tokenTransferAmount, + keccak256(abi.encodePacked(bytes(""))) + ); + + bytes32[] memory leaves = new bytes32[](1); + leaves[0] = leaf; + string memory encodedLeaves = _encodeLeaves(leaves); + + ClaimPayload memory payload; + payload.mainnetExitRoot = _getMerkleTreeRoot(encodedLeaves); + + vm.prank(address(polygonZkEVMBridge)); + polygonZkEVMGlobalExitRootV2.updateExitRoot(payload.mainnetExitRoot); + + assertEq( + polygonZkEVMGlobalExitRootV2.lastMainnetExitRoot(), + payload.mainnetExitRoot + ); + + payload.proofMainnet = _getProofByIndex(encodedLeaves, "0"); + payload.globalIndex = _computeGlobalIndex(0, 0, true); + payload.rollupExitRoot = polygonZkEVMGlobalExitRootV2 + .lastRollupExitRoot(); + payload.originNetwork = networkIDMainnet; + payload.originAddress = address(0); + payload.destinationNetwork = networkIDMainnet; + payload.destinationAddress = destinationAddress; + payload.amount = tokenTransferAmount; + payload.metadata = bytes(""); + + vm.expectEmit(); + emit ClaimEvent( + payload.globalIndex, + payload.originNetwork, + payload.originAddress, + payload.destinationAddress, + payload.amount + ); + polygonZkEVMBridge.claimAsset( + payload.proofMainnet, + payload.proofMainnet, + payload.globalIndex, + payload.mainnetExitRoot, + payload.rollupExitRoot, + payload.originNetwork, + payload.originAddress, + payload.destinationNetwork, + payload.destinationAddress, + payload.amount, + payload.metadata + ); + assertEq( + TokenWrapped(wethCalculated).balanceOf(destinationAddress), + tokenTransferAmount + ); + } + + function test_claimAsset_assetIsGasToken() public { + _initializePolygonZkEVMBridge( + address(pol), + networkIDMainnet, + tokenMetaData + ); + + bytes32 leaf = polygonZkEVMBridge.getLeafValue( + LEAF_TYPE_ASSET, + networkIDMainnet, + address(pol), + networkIDMainnet, + destinationAddress, + tokenTransferAmount, + keccak256(abi.encodePacked(tokenMetaData)) + ); + + bytes32[] memory leaves = new bytes32[](1); + leaves[0] = leaf; + string memory encodedLeaves = _encodeLeaves(leaves); + + ClaimPayload memory payload; + payload.mainnetExitRoot = _getMerkleTreeRoot(encodedLeaves); + + vm.prank(address(polygonZkEVMBridge)); + polygonZkEVMGlobalExitRootV2.updateExitRoot(payload.mainnetExitRoot); + + assertEq( + polygonZkEVMGlobalExitRootV2.lastMainnetExitRoot(), + payload.mainnetExitRoot + ); + + payload.proofMainnet = _getProofByIndex(encodedLeaves, "0"); + payload.globalIndex = _computeGlobalIndex(0, 0, true); + payload.rollupExitRoot = polygonZkEVMGlobalExitRootV2 + .lastRollupExitRoot(); + payload.originNetwork = networkIDMainnet; + payload.originAddress = address(pol); + payload.destinationNetwork = networkIDMainnet; + payload.destinationAddress = destinationAddress; + payload.amount = tokenTransferAmount; + payload.metadata = tokenMetaData; + + vm.deal(address(polygonZkEVMBridge), tokenTransferAmount); + + vm.expectEmit(); + emit ClaimEvent( + payload.globalIndex, + payload.originNetwork, + payload.originAddress, + payload.destinationAddress, + payload.amount + ); + polygonZkEVMBridge.claimAsset( + payload.proofMainnet, + payload.proofMainnet, + payload.globalIndex, + payload.mainnetExitRoot, + payload.rollupExitRoot, + payload.originNetwork, + payload.originAddress, + payload.destinationNetwork, + payload.destinationAddress, + payload.amount, + payload.metadata + ); + assertEq(destinationAddress.balance, tokenTransferAmount); + assertEq(address(polygonZkEVMBridge).balance, 0); + } + + function test_claimAsset_assetIsTokenOnSameNetwork() public { + _initializePolygonZkEVMBridge( + dummyTokenAddress, + networkIDMainnet, + bytes("") + ); + + bytes32 leaf = polygonZkEVMBridge.getLeafValue( + LEAF_TYPE_ASSET, + networkIDMainnet, + address(pol), + networkIDMainnet, + destinationAddress, + tokenTransferAmount, + keccak256(abi.encodePacked(tokenMetaData)) + ); + + bytes32[] memory leaves = new bytes32[](1); + leaves[0] = leaf; + string memory encodedLeaves = _encodeLeaves(leaves); + + ClaimPayload memory payload; + payload.mainnetExitRoot = _getMerkleTreeRoot(encodedLeaves); + + vm.prank(address(polygonZkEVMBridge)); + polygonZkEVMGlobalExitRootV2.updateExitRoot(payload.mainnetExitRoot); + + assertEq( + polygonZkEVMGlobalExitRootV2.lastMainnetExitRoot(), + payload.mainnetExitRoot + ); + + payload.proofMainnet = _getProofByIndex(encodedLeaves, "0"); + payload.globalIndex = _computeGlobalIndex(0, 0, true); + payload.rollupExitRoot = polygonZkEVMGlobalExitRootV2 + .lastRollupExitRoot(); + payload.originNetwork = networkIDMainnet; + payload.originAddress = address(pol); + payload.destinationNetwork = networkIDMainnet; + payload.destinationAddress = destinationAddress; + payload.amount = tokenTransferAmount; + payload.metadata = tokenMetaData; + + vm.prank(polOwner); + pol.mint(address(polygonZkEVMBridge), tokenTransferAmount); + + vm.expectEmit(); + emit ClaimEvent( + payload.globalIndex, + payload.originNetwork, + payload.originAddress, + payload.destinationAddress, + payload.amount + ); + polygonZkEVMBridge.claimAsset( + payload.proofMainnet, + payload.proofMainnet, + payload.globalIndex, + payload.mainnetExitRoot, + payload.rollupExitRoot, + payload.originNetwork, + payload.originAddress, + payload.destinationNetwork, + payload.destinationAddress, + payload.amount, + payload.metadata + ); + assertEq(polygonZkEVMBridge.isClaimed(0, 0), true); + assertEq(pol.balanceOf(destinationAddress), tokenTransferAmount); + assertEq(pol.balanceOf(address(polygonZkEVMBridge)), 0); + } + + function test_claimAsset_assetIsNewTokenOnDiffNetwork() public { + _initializePolygonZkEVMBridge( + dummyTokenAddress, + networkIDMainnet, + bytes("") + ); + + bytes32 leaf = polygonZkEVMBridge.getLeafValue( + LEAF_TYPE_ASSET, + networkIDRollup, + address(pol), + networkIDMainnet, + destinationAddress, + tokenTransferAmount, + keccak256(abi.encodePacked(tokenMetaData)) + ); + + bytes32[] memory mainnetLeaves = new bytes32[](2); + mainnetLeaves[0] = leaf; + mainnetLeaves[1] = leaf; + string memory encodedLeaves = _encodeLeaves(mainnetLeaves); + + ClaimPayload memory payload; + bytes32 calculatedMainnetExitRoot = _getMerkleTreeRoot(encodedLeaves); + + bytes32[] memory rollupLeaves = new bytes32[](10); + for (uint256 i = 0; i < 10; i++) { + rollupLeaves[i] = calculatedMainnetExitRoot; + } + string memory encodedRollupLeaves = _encodeLeaves(rollupLeaves); + payload.rollupExitRoot = _getMerkleTreeRoot(encodedRollupLeaves); + + vm.prank(rollupManager); + polygonZkEVMGlobalExitRootV2.updateExitRoot(payload.rollupExitRoot); + + assertEq( + polygonZkEVMGlobalExitRootV2.lastRollupExitRoot(), + payload.rollupExitRoot + ); + + payload.mainnetExitRoot = polygonZkEVMGlobalExitRootV2 + .lastMainnetExitRoot(); + + assertEq( + polygonZkEVMGlobalExitRootV2.getLastGlobalExitRoot(), + keccak256( + abi.encodePacked( + payload.mainnetExitRoot, + payload.rollupExitRoot + ) + ) + ); + + payload.proofMainnet = _getProofByIndex(encodedLeaves, "0"); + payload.proofRollup = _getProofByIndex(encodedRollupLeaves, "5"); + payload.globalIndex = _computeGlobalIndex(0, 5, false); + payload.originNetwork = networkIDRollup; + payload.originAddress = address(pol); + payload.destinationNetwork = networkIDMainnet; + payload.destinationAddress = destinationAddress; + payload.amount = tokenTransferAmount; + payload.metadata = tokenMetaData; + + vm.expectEmit(); + emit ClaimEvent( + payload.globalIndex, + payload.originNetwork, + payload.originAddress, + payload.destinationAddress, + payload.amount + ); + polygonZkEVMBridge.claimAsset( + payload.proofMainnet, + payload.proofRollup, + payload.globalIndex, + payload.mainnetExitRoot, + payload.rollupExitRoot, + payload.originNetwork, + payload.originAddress, + payload.destinationNetwork, + payload.destinationAddress, + payload.amount, + payload.metadata + ); + assertEq(polygonZkEVMBridge.isClaimed(0, 5 + 1), true); + + address wrappedTokenAddress = polygonZkEVMBridge + .precalculatedWrapperAddress( + payload.originNetwork, + address(pol), + tokenName, + tokenSymbol, + tokenDecimals + ); + assertEq( + TokenWrapped(wrappedTokenAddress).balanceOf(destinationAddress), + tokenTransferAmount + ); + } + + function test_claimAsset_assetIsExistingTokenOnDiffNetwork() public { + _initializePolygonZkEVMBridge( + dummyTokenAddress, + networkIDMainnet, + bytes("") + ); + + bytes32 leaf = polygonZkEVMBridge.getLeafValue( + LEAF_TYPE_ASSET, + networkIDRollup, + address(pol), + networkIDMainnet, + destinationAddress, + tokenTransferAmount, + keccak256(abi.encodePacked(tokenMetaData)) + ); + + bytes32[] memory mainnetLeaves = new bytes32[](2); + mainnetLeaves[0] = leaf; + mainnetLeaves[1] = leaf; + string memory encodedLeaves = _encodeLeaves(mainnetLeaves); + + ClaimPayload memory payload; + bytes32 calculatedMainnetExitRoot = _getMerkleTreeRoot(encodedLeaves); + + bytes32[] memory rollupLeaves = new bytes32[](10); + for (uint256 i = 0; i < 10; i++) { + rollupLeaves[i] = calculatedMainnetExitRoot; + } + string memory encodedRollupLeaves = _encodeLeaves(rollupLeaves); + payload.rollupExitRoot = _getMerkleTreeRoot(encodedRollupLeaves); + + vm.prank(rollupManager); + polygonZkEVMGlobalExitRootV2.updateExitRoot(payload.rollupExitRoot); + + assertEq( + polygonZkEVMGlobalExitRootV2.lastRollupExitRoot(), + payload.rollupExitRoot + ); + + payload.mainnetExitRoot = polygonZkEVMGlobalExitRootV2 + .lastMainnetExitRoot(); + + assertEq( + polygonZkEVMGlobalExitRootV2.getLastGlobalExitRoot(), + keccak256( + abi.encodePacked( + payload.mainnetExitRoot, + payload.rollupExitRoot + ) + ) + ); + + payload.proofMainnet = _getProofByIndex(encodedLeaves, "0"); + payload.proofRollup = _getProofByIndex(encodedRollupLeaves, "5"); + payload.globalIndex = _computeGlobalIndex(0, 5, false); + payload.originNetwork = networkIDRollup; + payload.originAddress = address(pol); + payload.destinationNetwork = networkIDMainnet; + payload.destinationAddress = destinationAddress; + payload.amount = tokenTransferAmount; + payload.metadata = tokenMetaData; + + polygonZkEVMBridge.claimAsset( + payload.proofMainnet, + payload.proofRollup, + payload.globalIndex, + payload.mainnetExitRoot, + payload.rollupExitRoot, + payload.originNetwork, + payload.originAddress, + payload.destinationNetwork, + payload.destinationAddress, + payload.amount, + payload.metadata + ); + + payload.proofMainnet = _getProofByIndex(encodedLeaves, "1"); + payload.proofRollup = _getProofByIndex(encodedRollupLeaves, "5"); + payload.globalIndex = _computeGlobalIndex(1, 5, false); + polygonZkEVMBridge.claimAsset( + payload.proofMainnet, + payload.proofRollup, + payload.globalIndex, + payload.mainnetExitRoot, + payload.rollupExitRoot, + payload.originNetwork, + payload.originAddress, + payload.destinationNetwork, + payload.destinationAddress, + payload.amount, + payload.metadata + ); + address wrappedTokenAddress = polygonZkEVMBridge + .precalculatedWrapperAddress( + payload.originNetwork, + address(pol), + tokenName, + tokenSymbol, + tokenDecimals + ); + assertEq( + TokenWrapped(wrappedTokenAddress).balanceOf(destinationAddress), + tokenTransferAmount * 2 + ); + } + + function testRevert_claimMessage_destinationNetworkInvalid() public { + _initializePolygonZkEVMBridge(address(0), networkIDMainnet, bytes("")); + + bytes32[32] memory smtEmptyProof; + ClaimPayload memory payload = ClaimPayload({ + proofMainnet: smtEmptyProof, + proofRollup: smtEmptyProof, + globalIndex: 0, + mainnetExitRoot: bytes32(0), + rollupExitRoot: bytes32(0), + originNetwork: networkIDMainnet, + originAddress: address(this), + destinationNetwork: networkIDRollup, // invalid destination network + destinationAddress: destinationAddress, + amount: 0, + metadata: abi.encode("Test message") + }); + + vm.expectRevert( + IPolygonZkEVMBridgeV2Extended.DestinationNetworkInvalid.selector + ); + polygonZkEVMBridge.claimMessage( + payload.proofMainnet, + payload.proofRollup, + payload.globalIndex, + payload.mainnetExitRoot, + payload.rollupExitRoot, + payload.originNetwork, + payload.originAddress, + payload.destinationNetwork, + payload.destinationAddress, + payload.amount, + payload.metadata + ); + } + + function testRevert_claimMessage_globalExitRootInvalid() public { + _initializePolygonZkEVMBridge(address(0), networkIDMainnet, bytes("")); + + bytes32[32] memory smtEmptyProof; + ClaimPayload memory payload = ClaimPayload({ + proofMainnet: smtEmptyProof, + proofRollup: smtEmptyProof, + globalIndex: 0, + mainnetExitRoot: bytes32(0), // invalid mainnetExitRoot + rollupExitRoot: bytes32(0), // invalid rollupExitRoot + originNetwork: networkIDMainnet, + originAddress: address(this), + destinationNetwork: networkIDMainnet, + destinationAddress: destinationAddress, + amount: 0, + metadata: abi.encode("Test message") + }); + + vm.expectRevert( + IPolygonZkEVMBridgeV2Extended.GlobalExitRootInvalid.selector + ); + polygonZkEVMBridge.claimMessage( + payload.proofMainnet, + payload.proofRollup, + payload.globalIndex, + payload.mainnetExitRoot, + payload.rollupExitRoot, + payload.originNetwork, + payload.originAddress, + payload.destinationNetwork, + payload.destinationAddress, + payload.amount, + payload.metadata + ); + } + + function testRevert_claimMessage_onSameNetwork_invalidSmtProof() public { + _initializePolygonZkEVMBridge(address(0), networkIDMainnet, bytes("")); + + bytes32 leaf = polygonZkEVMBridge.getLeafValue( + LEAF_TYPE_MESSAGE, + networkIDMainnet, + address(this), + networkIDMainnet, + destinationAddress, + 0, + keccak256(abi.encode("Test message")) + ); + + bytes32[] memory leaves = new bytes32[](1); + leaves[0] = leaf; + string memory encodedLeaves = _encodeLeaves(leaves); + + ClaimPayload memory payload; + payload.mainnetExitRoot = _getMerkleTreeRoot(encodedLeaves); + + vm.prank(address(polygonZkEVMBridge)); + polygonZkEVMGlobalExitRootV2.updateExitRoot(payload.mainnetExitRoot); + + assertEq( + polygonZkEVMGlobalExitRootV2.lastMainnetExitRoot(), + payload.mainnetExitRoot + ); + + payload.proofMainnet = _getProofByIndex(encodedLeaves, "0"); + payload.globalIndex = _computeGlobalIndex(0, 0, true); + payload.rollupExitRoot = polygonZkEVMGlobalExitRootV2 + .lastRollupExitRoot(); + payload.originNetwork = networkIDMainnet; + payload.originAddress = address(this); + payload.destinationNetwork = networkIDMainnet; + payload.destinationAddress = destinationAddress; + payload.amount = 0; + payload.metadata = abi.encode("Test message: invalid"); // invalidate proof by changing leaf value + + vm.expectRevert(IPolygonZkEVMBridgeV2Extended.InvalidSmtProof.selector); + polygonZkEVMBridge.claimMessage( + payload.proofMainnet, + payload.proofMainnet, + payload.globalIndex, + payload.mainnetExitRoot, + payload.rollupExitRoot, + payload.originNetwork, + payload.originAddress, + payload.destinationNetwork, + payload.destinationAddress, + payload.amount, + payload.metadata + ); + } + + function testRevert_claimMessage_onDiffNetwork_invalidSmtProof() public { + _initializePolygonZkEVMBridge(address(0), networkIDMainnet, bytes("")); + + bytes32 leaf = polygonZkEVMBridge.getLeafValue( + LEAF_TYPE_MESSAGE, + networkIDRollup, + address(this), + networkIDMainnet, + destinationAddress, + 0, + keccak256(abi.encode("Test message")) + ); + + bytes32[] memory mainnetLeaves = new bytes32[](2); + mainnetLeaves[0] = leaf; + mainnetLeaves[1] = leaf; + string memory encodedLeaves = _encodeLeaves(mainnetLeaves); + + ClaimPayload memory payload; + bytes32 calculatedMainnetExitRoot = _getMerkleTreeRoot(encodedLeaves); + + bytes32[] memory rollupLeaves = new bytes32[](10); + for (uint256 i = 0; i < 10; i++) { + rollupLeaves[i] = calculatedMainnetExitRoot; + } + string memory encodedRollupLeaves = _encodeLeaves(rollupLeaves); + payload.rollupExitRoot = _getMerkleTreeRoot(encodedRollupLeaves); + + vm.prank(rollupManager); + polygonZkEVMGlobalExitRootV2.updateExitRoot(payload.rollupExitRoot); + + assertEq( + polygonZkEVMGlobalExitRootV2.lastRollupExitRoot(), + payload.rollupExitRoot + ); + + payload.mainnetExitRoot = polygonZkEVMGlobalExitRootV2 + .lastMainnetExitRoot(); + + assertEq( + polygonZkEVMGlobalExitRootV2.getLastGlobalExitRoot(), + keccak256( + abi.encodePacked( + payload.mainnetExitRoot, + payload.rollupExitRoot + ) + ) + ); + + payload.proofMainnet = _getProofByIndex(encodedLeaves, "0"); + payload.proofRollup = _getProofByIndex(encodedRollupLeaves, "5"); + payload.globalIndex = _computeGlobalIndex(0, 5, false); + payload.originNetwork = networkIDRollup; + payload.originAddress = address(this); + payload.destinationNetwork = networkIDMainnet; + payload.destinationAddress = destinationAddress; + payload.amount = 0; + payload.metadata = abi.encode("Test message: invalid"); // invalidate proof by changing leaf value + + vm.expectRevert(IPolygonZkEVMBridgeV2Extended.InvalidSmtProof.selector); + polygonZkEVMBridge.claimMessage( + payload.proofMainnet, + payload.proofRollup, + payload.globalIndex, + payload.mainnetExitRoot, + payload.rollupExitRoot, + payload.originNetwork, + payload.originAddress, + payload.destinationNetwork, + payload.destinationAddress, + payload.amount, + payload.metadata + ); + } + + function test_claimMessage() public { + _initializePolygonZkEVMBridge(address(0), networkIDMainnet, bytes("")); + + bytes32 leaf = polygonZkEVMBridge.getLeafValue( + LEAF_TYPE_MESSAGE, + networkIDMainnet, + address(this), + networkIDMainnet, + destinationAddress, + tokenTransferAmount, + keccak256(abi.encode("Test message")) + ); + + bytes32[] memory leaves = new bytes32[](1); + leaves[0] = leaf; + string memory encodedLeaves = _encodeLeaves(leaves); + + ClaimPayload memory payload; + payload.mainnetExitRoot = _getMerkleTreeRoot(encodedLeaves); + + vm.prank(address(polygonZkEVMBridge)); + polygonZkEVMGlobalExitRootV2.updateExitRoot(payload.mainnetExitRoot); + + assertEq( + polygonZkEVMGlobalExitRootV2.lastMainnetExitRoot(), + payload.mainnetExitRoot + ); + + payload.proofMainnet = _getProofByIndex(encodedLeaves, "0"); + payload.globalIndex = _computeGlobalIndex(0, 0, true); + payload.rollupExitRoot = polygonZkEVMGlobalExitRootV2 + .lastRollupExitRoot(); + payload.originNetwork = networkIDMainnet; + payload.originAddress = address(this); + payload.destinationNetwork = networkIDMainnet; + payload.destinationAddress = destinationAddress; + payload.amount = tokenTransferAmount; + payload.metadata = abi.encode("Test message"); + + vm.deal(address(polygonZkEVMBridge), tokenTransferAmount); + + vm.expectEmit(); + emit ClaimEvent( + payload.globalIndex, + payload.originNetwork, + payload.originAddress, + payload.destinationAddress, + payload.amount + ); + polygonZkEVMBridge.claimMessage( + payload.proofMainnet, + payload.proofMainnet, + payload.globalIndex, + payload.mainnetExitRoot, + payload.rollupExitRoot, + payload.originNetwork, + payload.originAddress, + payload.destinationNetwork, + payload.destinationAddress, + payload.amount, + payload.metadata + ); + assertEq(polygonZkEVMBridge.isClaimed(0, 0), true); + assertEq(destinationAddress.balance, tokenTransferAmount); + assertEq(address(polygonZkEVMBridge).balance, 0); + } + + function test_claimMessage_WETH() public { + _initializePolygonZkEVMBridge( + address(pol), + networkIDMainnet, + tokenMetaData + ); + + bytes32 leaf = polygonZkEVMBridge.getLeafValue( + LEAF_TYPE_MESSAGE, + networkIDMainnet, + address(this), + networkIDMainnet, + destinationAddress, + tokenTransferAmount, + keccak256(abi.encode("Test message")) + ); + + bytes32[] memory leaves = new bytes32[](1); + leaves[0] = leaf; + string memory encodedLeaves = _encodeLeaves(leaves); + + ClaimPayload memory payload; + payload.mainnetExitRoot = _getMerkleTreeRoot(encodedLeaves); + + vm.prank(address(polygonZkEVMBridge)); + polygonZkEVMGlobalExitRootV2.updateExitRoot(payload.mainnetExitRoot); + + assertEq( + polygonZkEVMGlobalExitRootV2.lastMainnetExitRoot(), + payload.mainnetExitRoot + ); + + payload.proofMainnet = _getProofByIndex(encodedLeaves, "0"); + payload.globalIndex = _computeGlobalIndex(0, 0, true); + payload.rollupExitRoot = polygonZkEVMGlobalExitRootV2 + .lastRollupExitRoot(); + payload.originNetwork = networkIDMainnet; + payload.originAddress = address(this); + payload.destinationNetwork = networkIDMainnet; + payload.destinationAddress = destinationAddress; + payload.amount = tokenTransferAmount; + payload.metadata = abi.encode("Test message"); + + vm.expectEmit(); + emit ClaimEvent( + payload.globalIndex, + payload.originNetwork, + payload.originAddress, + payload.destinationAddress, + payload.amount + ); + polygonZkEVMBridge.claimMessage( + payload.proofMainnet, + payload.proofMainnet, + payload.globalIndex, + payload.mainnetExitRoot, + payload.rollupExitRoot, + payload.originNetwork, + payload.originAddress, + payload.destinationNetwork, + payload.destinationAddress, + payload.amount, + payload.metadata + ); + assertEq(polygonZkEVMBridge.isClaimed(0, 0), true); + assertEq( + TokenWrapped(wethCalculated).balanceOf(destinationAddress), + tokenTransferAmount + ); + } + + //TODO: add more tests + + function _initializePolygonZkEVMBridge( + address gasTokenAddress, + uint32 gasTokenNetwork, + bytes memory gasTokenMetadata + ) internal { + polygonZkEVMBridge.initialize( + networkIDMainnet, //_networkID + gasTokenAddress, //_gasTokenAddress + gasTokenNetwork, //_gasTokenNetwork + polygonZkEVMGlobalExitRootV2, //_globalExitRootManager + rollupManager, //_polygonRollupManager + gasTokenMetadata //_gasTokenMetadata + ); + } + + function _proxify(address logic) internal returns (address proxy) { + TransparentUpgradeableProxy proxy_ = new TransparentUpgradeableProxy( + logic, + msg.sender, + "" + ); + return (address(proxy_)); + } + + function _preDeployPolygonZkEVMBridgeV2() + internal + returns (address implementation) + { + string[] memory exe = new string[](5); + exe[0] = "forge"; + exe[1] = "inspect"; + exe[2] = "PolygonZkEVMBridgeV2"; + exe[3] = "bytecode"; + exe[ + 4 + ] = "--contracts=contracts-ignored-originals/PolygonZkEVMBridgeV2.sol"; + + bytes memory creationCode = vm.ffi(exe); + implementation = makeAddr("PolygonZkEVMBridgeV2"); + + vm.etch(implementation, creationCode); + (bool success, bytes memory runtimeBytecode) = implementation.call(""); + require(success, "Failed to predeploy PolygonZkEVMBridgeV2"); + vm.etch(implementation, runtimeBytecode); + } + + function _computeGlobalIndex( + uint256 indexMainnet, + uint256 indexRollup, + bool isMainnet + ) internal pure returns (uint256) { + if (isMainnet) { + return indexMainnet + _GLOBAL_INDEX_MAINNET_FLAG; + } else { + return indexMainnet + indexRollup * 2 ** 32; + } + } +} diff --git a/test/PolygonZkEVMDeployer.t.sol b/test/PolygonZkEVMDeployer.t.sol new file mode 100644 index 000000000..3f1c7f26b --- /dev/null +++ b/test/PolygonZkEVMDeployer.t.sol @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "test/util/TestHelpers.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/presets/ERC20PresetFixedSupplyUpgradeable.sol"; + +import "script/deployers/PolygonZkEVMDeployerDeployer.s.sol"; + +abstract contract Common is Test, TestHelpers { + address internal polygonZkEVMDeployerOwner = makeAddr("owner"); + + string internal tokenName = "Polygon"; + string internal tokenSymbol = "POL"; + bytes32 internal tokenSalt = bytes32(0); + uint256 internal tokenSupply = 1000 ether; + address internal tokenOwner = makeAddr("tokenOwner"); + address internal tokenDeterministicAddr; + + uint256 internal amount = 100 ether; + + address internal notOwner = makeAddr("notOwner"); + address internal receipient = makeAddr("receipient"); + + bytes32 internal creationCodeHash; + bytes internal creationCode; + + PolygonZkEVMDeployer internal polygonZkEVMDeployer; + + bytes transferCallData = + abi.encodeWithSignature( + "transfer(address,uint256)", + receipient, + amount + ); + + bytes transferCallDataFailure = + abi.encodeWithSignature( + "transfer(address,uint256)", + address(0), // zero address + amount + ); + + bytes initializeCallData = + abi.encodeWithSignature( + "initialize(string,string,uint256,address)", + tokenName, + tokenSymbol, + tokenSupply, + tokenOwner + ); + + event NewDeterministicDeployment(address newContractAddress); + event FunctionCall(); + event FunctionalCall(); + + constructor() { + creationCode = abi.encodePacked( + vm.getCode("ERC20PresetFixedSupplyUpgradeable"), + "" + ); + creationCodeHash = keccak256(abi.encodePacked(creationCode)); + } +} + +abstract contract Predeployment is Common, PolygonZkEVMDeployerDeployer { + function setUp() public virtual { + polygonZkEVMDeployer = PolygonZkEVMDeployer( + deployPolygonZkEVMDeployerImplementation(polygonZkEVMDeployerOwner) + ); + } +} + +contract PolygonZkEVMDeployerTestPredeployment is Predeployment { + function test_owner() public view { + assertEq(polygonZkEVMDeployer.owner(), polygonZkEVMDeployerOwner); + } + + function test_predictDeterministicAddress() public view { + address precalculatedTokenAddress = vm.computeCreate2Address( + tokenSalt, + creationCodeHash, + address(polygonZkEVMDeployer) + ); + + address deterministicTokenAddress = polygonZkEVMDeployer + .predictDeterministicAddress(tokenSalt, creationCodeHash); + + assertEq(precalculatedTokenAddress, deterministicTokenAddress); + } + + function testRevert_deployDeterministic_notOwner() public { + vm.expectRevert("Ownable: caller is not the owner"); + vm.prank(notOwner); + polygonZkEVMDeployer.deployDeterministic(0, tokenSalt, creationCode); + } + + function test_deployDeterministic() public { + tokenDeterministicAddr = polygonZkEVMDeployer + .predictDeterministicAddress(tokenSalt, creationCodeHash); + + vm.prank(polygonZkEVMDeployerOwner); + vm.expectEmit(); + emit NewDeterministicDeployment(tokenDeterministicAddr); + polygonZkEVMDeployer.deployDeterministic(0, tokenSalt, creationCode); + } + + function test_deployDeterministicAndCall() public { + tokenDeterministicAddr = polygonZkEVMDeployer + .predictDeterministicAddress(tokenSalt, creationCodeHash); + + vm.prank(polygonZkEVMDeployerOwner); + vm.expectEmit(); + emit NewDeterministicDeployment(tokenDeterministicAddr); + polygonZkEVMDeployer.deployDeterministicAndCall( + 0, + tokenSalt, + creationCode, + initializeCallData + ); + ERC20PresetFixedSupplyUpgradeable token = ERC20PresetFixedSupplyUpgradeable( + tokenDeterministicAddr + ); + assertEq(token.name(), tokenName); + assertEq(token.symbol(), tokenSymbol); + assertEq(token.totalSupply(), tokenSupply); + } +} + +abstract contract Postdeployment is Common, PolygonZkEVMDeployerDeployer { + function setUp() public virtual { + polygonZkEVMDeployer = PolygonZkEVMDeployer( + deployPolygonZkEVMDeployerImplementation(polygonZkEVMDeployerOwner) + ); + + tokenDeterministicAddr = polygonZkEVMDeployer + .predictDeterministicAddress(tokenSalt, creationCodeHash); + + vm.startPrank(polygonZkEVMDeployerOwner); + polygonZkEVMDeployer.deployDeterministic(0, tokenSalt, creationCode); + + polygonZkEVMDeployer.functionCall( + tokenDeterministicAddr, + initializeCallData, + 0 + ); + vm.stopPrank(); + } +} + +contract PolygonZkEVMDeployerTestPostdeployment is Postdeployment { + function testRevert_deployOnSameAddress() public { + vm.prank(polygonZkEVMDeployerOwner); + vm.expectRevert("Create2: Failed on deploy"); + polygonZkEVMDeployer.deployDeterministic(0, tokenSalt, creationCode); + } + + function testRevert_functionCall_callToNonContract() public { + vm.prank(polygonZkEVMDeployerOwner); + vm.expectRevert("Address: call to non-contract"); + polygonZkEVMDeployer.functionCall( + notOwner, // points to a non-contract address with no code + transferCallData, + 0 + ); + } + + function testRevert_functionCall_lowLevelCallNotFound() public { + vm.prank(polygonZkEVMDeployerOwner); + vm.expectRevert("Address: low-level call with value failed"); + polygonZkEVMDeployer.functionCall( + address(this), // points to a contract address with no 'transfer' function + transferCallData, + 0 + ); + } + + function testRevert_functionCall_lowLevelCallInternalRevert() public { + vm.prank(polygonZkEVMDeployerOwner); + vm.expectRevert("ERC20: transfer to the zero address"); + polygonZkEVMDeployer.functionCall( + tokenDeterministicAddr, + transferCallDataFailure, + 0 + ); + } + + function testRevert_functionCall_notOwner() public { + vm.expectRevert("Ownable: caller is not the owner"); + polygonZkEVMDeployer.functionCall( + tokenDeterministicAddr, + transferCallData, + 0 + ); + } + + function test_functionCall() public { + // give some tokens to the polygonZkEVMDeployer contract since it will be the one calling the function + vm.prank(tokenOwner); // msg.sender + ERC20PresetFixedSupplyUpgradeable(tokenDeterministicAddr).transfer( + address(polygonZkEVMDeployer), + amount + ); + + // only the polygonZkEVMDeployerOwner can initiate the function call + vm.prank(polygonZkEVMDeployerOwner); + vm.expectEmit(); + emit FunctionCall(); + polygonZkEVMDeployer.functionCall( + tokenDeterministicAddr, + transferCallData, + 0 + ); + + uint256 receipientBalance = ERC20PresetFixedSupplyUpgradeable( + tokenDeterministicAddr + ).balanceOf(receipient); + assertEq(receipientBalance, amount); + } +} diff --git a/test/PolygonZkEVMEtrog.t.sol b/test/PolygonZkEVMEtrog.t.sol new file mode 100644 index 000000000..8505cb6be --- /dev/null +++ b/test/PolygonZkEVMEtrog.t.sol @@ -0,0 +1,927 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "test/util/TestHelpers.sol"; + +import {PolygonRollupManager} from "contracts/PolygonRollupManager.sol"; + +import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import {IPolygonZkEVMBridgeV2Extended} from "contracts/interfaces/IPolygonZkEVMBridgeV2Extended.sol"; +import {IPolygonZkEVMBridgeV2} from "contracts/interfaces/IPolygonZkEVMBridgeV2.sol"; + +import {ERC20PermitMockDeployer} from "script/deployers/ERC20PermitMockDeployer.s.sol"; +import {PolygonRollupManagerEmptyMockDeployer} from "script/deployers/PolygonRollupManagerEmptyMockDeployer.s.sol"; +import {PolygonZkEVMGlobalExitRootV2Deployer} from "script/deployers/PolygonZkEVMGlobalExitRootV2Deployer.s.sol"; + +import "script/deployers/PolygonZkEVMEtrogDeployer.s.sol"; + +contract PolygonZkEVMEtrogTest is + Test, + TestHelpers, + ERC20PermitMockDeployer, + PolygonRollupManagerEmptyMockDeployer, + PolygonZkEVMEtrogDeployer, + PolygonZkEVMGlobalExitRootV2Deployer +{ + address admin = makeAddr("admin"); + address destinationAddress = makeAddr("destinationAddress"); + address proxyAdminOwner = makeAddr("proxyAdminOwner"); + address polTokenOwner = makeAddr("polTokenOwner"); + address trustedSequencer = makeAddr("trustedSequencer"); + address trustedAggregator = makeAddr("trustedAggregator"); + + IPolygonZkEVMBridgeV2Extended polygonZkEVMBridge; + IERC20Upgradeable pol; + PolygonRollupManager polygonRollupManager; + + string constant tokenName = "Polygon"; + string constant tokenSymbol = "POL"; + uint256 constant tokenInitialBalance = 20_000_000 ether; + + string constant networkName = "zkevm"; + string constant sequencerURL = "http://zkevm-json-rpc:8123"; + + uint256 constant tokenTransferAmount = 10 ether; + + uint256 public constant TIMESTAMP_RANGE = 36; + + bytes tokenMetaData = abi.encode(tokenName, tokenSymbol, tokenDecimals); + + uint64 constant FORCE_BATCH_TIMEOUT = 60 * 60 * 24 * 5; // 5 days + uint64 constant SIGNATURE_BYTES = 32 + 32 + 1; + uint64 constant EFFECTIVE_PERCENTAGE_BYTES = 1; + uint64 internal constant HALT_AGGREGATION_TIMEOUT = 1 weeks; + uint64 internal constant MAX_VERIFY_BATCHES = 1000; + + uint32 networkIDMainnet = 0; + uint32 networkIDRollup = 1; + uint32 l1InfoRootIndex = 1; + + uint8 constant tokenDecimals = 18; + uint8 constant LEAF_TYPE_MESSAGE = 1; + bytes l2TxData = "0x123456"; + + event SequenceBatches(uint64 indexed numBatch, bytes32 l1InfoRoot); + event ForceBatch( + uint64 indexed forceBatchNum, + bytes32 lastGlobalExitRoot, + address sequencer, + bytes transactions + ); + event SequenceForceBatches(uint64 indexed numBatch); + event InitialSequenceBatches( + bytes transactions, + bytes32 lastGlobalExitRoot, + address sequencer + ); + event VerifyBatches( + uint64 indexed numBatch, + bytes32 stateRoot, + address indexed aggregator + ); + event RollbackBatches( + uint64 indexed targetBatch, + bytes32 accInputHashToRollback + ); + event SetTrustedSequencer(address newTrustedSequencer); + event SetTrustedSequencerURL(string newTrustedSequencerURL); + event SetForceBatchTimeout(uint64 newforceBatchTimeout); + event SetForceBatchAddress(address newForceBatchAddress); + event TransferAdminRole(address newPendingAdmin); + event AcceptAdminRole(address newAdmin); + + function setUp() public { + pol = IERC20Upgradeable( + deployERC20PermitMockImplementation( + tokenName, + tokenSymbol, + polTokenOwner, + tokenInitialBalance + ) + ); + + IPolygonZkEVMBridgeV2Extended polygonZkEVMBridgeImplementation = IPolygonZkEVMBridgeV2Extended( + _preDeployPolygonZkEVMBridgeV2() + ); + polygonZkEVMBridge = IPolygonZkEVMBridgeV2Extended( + _proxify(address(polygonZkEVMBridgeImplementation)) + ); + + polygonRollupManager = PolygonRollupManager( + deployPolygonRollupManagerEmptyMockImplementation() + ); + + deployPolygonZkEVMGlobalExitRootV2Transparent( + proxyAdminOwner, + address(polygonRollupManager), + address(polygonZkEVMBridge) + ); + + polygonZkEVMBridge.initialize( + networkIDMainnet, + address(0), + 0, + polygonZkEVMGlobalExitRootV2, + address(polygonRollupManager), + bytes("") + ); + + vm.prank(polTokenOwner); + pol.transfer(trustedSequencer, 1_000 ether); + + polygonZkEVMEtrog = PolygonZkEVMEtrog( + deployPolygonZkEVMEtrogImplementation( + polygonZkEVMGlobalExitRootV2, + pol, + IPolygonZkEVMBridgeV2(address(polygonZkEVMBridge)), + polygonRollupManager + ) + ); + } + + function testRevert_initialize_onlyRollupManager() public { + vm.expectRevert(IPolygonZkEVMVEtrogErrors.OnlyRollupManager.selector); + _initializePolygonZkEVMEtrog(); + } + + function test_initialize() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + assertEq(polygonZkEVMEtrog.admin(), admin); + assertEq(polygonZkEVMEtrog.trustedSequencer(), trustedSequencer); + assertEq(polygonZkEVMEtrog.trustedSequencerURL(), sequencerURL); + assertEq(polygonZkEVMEtrog.networkName(), networkName); + assertEq(polygonZkEVMEtrog.forceBatchTimeout(), FORCE_BATCH_TIMEOUT); + } + + function testRevert_initialize_alreadyInitialized() public { + vm.startPrank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + vm.expectRevert("Initializable: contract is already initialized"); + _initializePolygonZkEVMEtrog(); + vm.stopPrank(); + } + + function testRevert_adminFunctions() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + bytes4 selector = IPolygonZkEVMErrors.OnlyAdmin.selector; + + vm.expectRevert(selector); + polygonZkEVMEtrog.setForceBatchTimeout(1); + + vm.expectRevert(selector); + polygonZkEVMEtrog.setTrustedSequencer(address(0)); + + vm.expectRevert(selector); + polygonZkEVMEtrog.setTrustedSequencerURL(""); + + vm.expectRevert(selector); + polygonZkEVMEtrog.transferAdminRole(address(0)); + + vm.expectRevert(selector); + polygonZkEVMEtrog.setForceBatchAddress(address(0)); + + vm.expectRevert(IPolygonZkEVMErrors.OnlyPendingAdmin.selector); + polygonZkEVMEtrog.acceptAdminRole(); + } + + function test_adminFunctions() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + vm.startPrank(admin); + + vm.expectEmit(); + emit SetForceBatchTimeout(0); + polygonZkEVMEtrog.setForceBatchTimeout(0); + assertEq(polygonZkEVMEtrog.forceBatchTimeout(), 0); + + vm.expectEmit(); + emit SetTrustedSequencer(makeAddr("newTrustedSequencer")); + polygonZkEVMEtrog.setTrustedSequencer(makeAddr("newTrustedSequencer")); + assertEq( + polygonZkEVMEtrog.trustedSequencer(), + makeAddr("newTrustedSequencer") + ); + + vm.expectEmit(); + emit SetTrustedSequencerURL("http://zkevm-json-rpc:8145"); + polygonZkEVMEtrog.setTrustedSequencerURL("http://zkevm-json-rpc:8145"); + assertEq( + polygonZkEVMEtrog.trustedSequencerURL(), + "http://zkevm-json-rpc:8145" + ); + + vm.expectEmit(); + emit SetForceBatchAddress(makeAddr("newForceBatchAddress")); + polygonZkEVMEtrog.setForceBatchAddress( + makeAddr("newForceBatchAddress") + ); + assertEq( + polygonZkEVMEtrog.forceBatchAddress(), + makeAddr("newForceBatchAddress") + ); + + address newAdmin = makeAddr("newAdmin"); + + vm.expectEmit(); + emit TransferAdminRole(newAdmin); + polygonZkEVMEtrog.transferAdminRole(newAdmin); + assertEq(polygonZkEVMEtrog.pendingAdmin(), newAdmin); + + vm.stopPrank(); + + vm.prank(newAdmin); + vm.expectEmit(); + emit AcceptAdminRole(makeAddr("newAdmin")); + polygonZkEVMEtrog.acceptAdminRole(); + assertEq(polygonZkEVMEtrog.admin(), newAdmin); + } + + function testRevert_setForceBatch() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + + bytes4 forceBatchNotAllowedSelector = IPolygonZkEVMErrors + .ForceBatchNotAllowed + .selector; + + vm.expectRevert(forceBatchNotAllowedSelector); + polygonZkEVMEtrog.forceBatch(bytes(""), 0); + + vm.expectRevert(forceBatchNotAllowedSelector); + polygonZkEVMEtrog.sequenceForceBatches( + new PolygonRollupBaseEtrog.BatchData[](0) + ); + + vm.startPrank(admin); + + polygonZkEVMEtrog.setForceBatchAddress(address(0)); + + vm.expectRevert( + IPolygonZkEVMVEtrogErrors.ForceBatchesDecentralized.selector + ); + polygonZkEVMEtrog.setForceBatchAddress(address(0)); + + vm.stopPrank(); + } + + function testRevert_setForceBatchTimeout_invalidRangeForceBatchTimeout() + public + { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + + vm.startPrank(admin); + vm.expectRevert( + IPolygonZkEVMErrors.InvalidRangeForceBatchTimeout.selector + ); + polygonZkEVMEtrog.setForceBatchTimeout(HALT_AGGREGATION_TIMEOUT + 1); + + vm.expectRevert( + IPolygonZkEVMErrors.InvalidRangeForceBatchTimeout.selector + ); + polygonZkEVMEtrog.setForceBatchTimeout(HALT_AGGREGATION_TIMEOUT); + vm.stopPrank(); + } + + function testRevert_generateInitializeTransaction_hugeTokenMetadataNotSupported() + public + { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + + bytes memory hugeTokenMetaData = new bytes(1_000_000); // huge data + + vm.expectRevert( + IPolygonZkEVMVEtrogErrors.HugeTokenMetadataNotSupported.selector + ); + polygonZkEVMEtrog.generateInitializeTransaction( + networkIDRollup, + address(0), + networkIDMainnet, + hugeTokenMetaData + ); + } + + function test_generateInitializeTransaction() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + + uint64 timestamp = uint64(block.timestamp); + bytes32 blockParentHash = blockhash(block.number - 1); + bytes memory initialTx = polygonZkEVMEtrog + .generateInitializeTransaction( + networkIDRollup, + address(0), + networkIDMainnet, + bytes("") + ); + + bytes32 initExpectedAccInputHash = _calculateAccInputHash( + bytes32(0), + keccak256(initialTx), + polygonZkEVMGlobalExitRootV2.getLastGlobalExitRoot(), + timestamp, + trustedSequencer, + blockParentHash + ); + assertEq( + polygonZkEVMEtrog.lastAccInputHash(), + initExpectedAccInputHash + ); + } + + function testRevert_sequenceBatches_onlyTrustedSequencer() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[](1); + + vm.expectRevert(IPolygonZkEVMErrors.OnlyTrustedSequencer.selector); + polygonZkEVMEtrog.sequenceBatches( + batchData, + l1InfoRootIndex, + uint64(block.timestamp), + bytes32(0), + trustedAggregator + ); + } + + function testRevert_sequenceBatches_sequenceZeroBatches() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[](0); // Empty batchData + + vm.prank(trustedSequencer); + vm.expectRevert(IPolygonZkEVMErrors.SequenceZeroBatches.selector); + polygonZkEVMEtrog.sequenceBatches( + batchData, + l1InfoRootIndex, + uint64(block.timestamp), + bytes32(0), + trustedAggregator + ); + } + + function testRevert_sequenceBatches_exceedMaxVerifyBatches() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[]( + MAX_VERIFY_BATCHES + 1 + ); // Exceed max verify batches + + vm.prank(trustedSequencer); + vm.expectRevert(IPolygonZkEVMErrors.ExceedMaxVerifyBatches.selector); + polygonZkEVMEtrog.sequenceBatches( + batchData, + l1InfoRootIndex, + uint64(block.timestamp), + bytes32(0), + trustedAggregator + ); + } + + function testRevert_sequenceBatches_maxTimestampSequenceInvalid() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[](1); + + vm.prank(trustedSequencer); + vm.expectRevert( + IPolygonZkEVMVEtrogErrors.MaxTimestampSequenceInvalid.selector + ); + polygonZkEVMEtrog.sequenceBatches( + batchData, + l1InfoRootIndex, + uint64(block.timestamp + TIMESTAMP_RANGE + 1), // Exceed max timestamp + bytes32(0), + trustedAggregator + ); + } + + function testRevert_sequenceBatches_l1InfoRootIndexInvalid() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[](1); + + vm.prank(trustedSequencer); + vm.expectRevert( + IPolygonZkEVMVEtrogErrors.L1InfoTreeLeafCountInvalid.selector + ); + polygonZkEVMEtrog.sequenceBatches( + batchData, + 10, // Invalid l1InfoRootIndex + uint64(block.timestamp), + bytes32(0), + trustedAggregator + ); + } + + function testRevert_sequenceBatches_forcedDataDoesNotMatch() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[](1); + batchData[0] = PolygonRollupBaseEtrog.BatchData( + l2TxData, + bytes32(0), + uint64(block.timestamp + 10), // forcedBatch timestamp provided but PolygonRollupBaseEtrog.forcedBatch mapping is empty + bytes32(0) + ); + + polygonZkEVMBridge.bridgeMessage( + networkIDRollup, + destinationAddress, + true, + tokenMetaData + ); + + bytes32 expectedAccInputHash = _calculateAccInputHash( + polygonZkEVMEtrog.lastAccInputHash(), + keccak256(l2TxData), + polygonZkEVMGlobalExitRootV2.getLastGlobalExitRoot(), + uint64(block.timestamp + 10), + trustedSequencer, + bytes32(0) + ); + + vm.prank(trustedSequencer); + vm.expectRevert(IPolygonZkEVMErrors.ForcedDataDoesNotMatch.selector); + polygonZkEVMEtrog.sequenceBatches( + batchData, + l1InfoRootIndex, + uint64(block.timestamp + 10), + expectedAccInputHash, + trustedAggregator + ); + } + + function testRevert_sequenceBatches_transactionsLengthAboveMax() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + + bytes memory hugeData = new bytes(1_000_000); // huge data + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[](1); + batchData[0] = PolygonRollupBaseEtrog.BatchData( + hugeData, + bytes32(0), + 0, + bytes32(0) + ); + + polygonZkEVMBridge.bridgeMessage( + networkIDRollup, + destinationAddress, + true, + tokenMetaData + ); + + bytes32 expectedAccInputHash = _calculateAccInputHash( + polygonZkEVMEtrog.lastAccInputHash(), + keccak256(hugeData), + polygonZkEVMGlobalExitRootV2.getLastGlobalExitRoot(), + uint64(block.timestamp + 10), + trustedSequencer, + bytes32(0) + ); + + vm.prank(trustedSequencer); + vm.expectRevert( + IPolygonZkEVMErrors.TransactionsLengthAboveMax.selector + ); + polygonZkEVMEtrog.sequenceBatches( + batchData, + l1InfoRootIndex, + uint64(block.timestamp + 10), + expectedAccInputHash, + trustedAggregator + ); + } + + function test_sequenceBatches() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[](1); + batchData[0] = PolygonRollupBaseEtrog.BatchData( + l2TxData, + bytes32(0), + 0, + bytes32(0) + ); + + polygonZkEVMBridge.bridgeMessage( + networkIDRollup, + destinationAddress, + true, + tokenMetaData + ); + + uint256 currentTime = block.timestamp; + bytes32 l1InfoRootHash = polygonZkEVMGlobalExitRootV2.l1InfoRootMap( + l1InfoRootIndex + ); + bytes32 expectedAccInputHash = _calculateAccInputHash( + polygonZkEVMEtrog.lastAccInputHash(), + keccak256(l2TxData), + l1InfoRootHash, + uint64(currentTime), + trustedSequencer, + bytes32(0) + ); + + vm.startPrank(trustedSequencer); + pol.approve(address(polygonZkEVMEtrog), 100); + + vm.expectEmit(); + emit SequenceBatches(2, l1InfoRootHash); + polygonZkEVMEtrog.sequenceBatches( + batchData, + l1InfoRootIndex, + uint64(currentTime), + expectedAccInputHash, + trustedSequencer + ); + vm.stopPrank(); + } + + function testRevert_forceBatch_forceBatchNotAllowed() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + + vm.expectRevert(IPolygonZkEVMErrors.ForceBatchNotAllowed.selector); + polygonZkEVMEtrog.forceBatch(bytes(""), 0); + } + + function testRevert_forceBatch_forceBatchesNotAllowedOnEmergencyState() + public + { + vm.startPrank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + polygonRollupManager.activateEmergencyState(); + vm.stopPrank(); + + vm.prank(admin); + vm.expectRevert( + IPolygonZkEVMVEtrogErrors + .ForceBatchesNotAllowedOnEmergencyState + .selector + ); + polygonZkEVMEtrog.forceBatch(bytes(""), 0); + } + + function testRevert_forceBatch_notEnoughPOLAmount() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + + vm.prank(admin); + vm.expectRevert(IPolygonZkEVMVEtrogErrors.NotEnoughPOLAmount.selector); + polygonZkEVMEtrog.forceBatch(bytes(""), 0); + } + + function testRevert_forceBatch_transactionsLengthAboveMax() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + + bytes memory hugeData = new bytes(1_000_000); // huge data + + vm.prank(admin); + vm.expectRevert( + IPolygonZkEVMErrors.TransactionsLengthAboveMax.selector + ); + polygonZkEVMEtrog.forceBatch(hugeData, tokenTransferAmount); + } + + function test_forceBatch() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + + uint64 lastForcedBatch = 1; + + vm.prank(polTokenOwner); + pol.transfer(admin, 1_000); + + vm.startPrank(admin); + pol.approve(address(polygonZkEVMEtrog), tokenTransferAmount); + + vm.expectEmit(); + emit ForceBatch( + lastForcedBatch, + polygonZkEVMGlobalExitRootV2.getLastGlobalExitRoot(), + admin, + l2TxData + ); + polygonZkEVMEtrog.forceBatch(l2TxData, tokenTransferAmount); + vm.stopPrank(); + + assertEq( + polygonZkEVMEtrog.calculatePolPerForceBatch(), + polygonRollupManager.getForcedBatchFee() + ); + } + + function test_forceBatch_sendFromContract() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + + SendData sendData = new SendData(); + vm.prank(polTokenOwner); + pol.transfer(address(sendData), 1_000); + + bytes memory approveData = abi.encodeWithSelector( + pol.approve.selector, + address(polygonZkEVMEtrog), + tokenTransferAmount + ); + sendData.sendData(address(pol), approveData); + + vm.expectEmit(); + emit SetForceBatchAddress(address(sendData)); + vm.prank(admin); + polygonZkEVMEtrog.setForceBatchAddress(address(sendData)); + + uint64 lastForcedBatch = polygonZkEVMEtrog.lastForceBatch() + 1; // checks increment + bytes32 globalExitRoot = polygonZkEVMGlobalExitRootV2 + .getLastGlobalExitRoot(); + + bytes memory forceBatchData = abi.encodeWithSelector( + polygonZkEVMEtrog.forceBatch.selector, + l2TxData, + tokenTransferAmount + ); + vm.expectEmit(); + emit ForceBatch( + lastForcedBatch, + globalExitRoot, + address(sendData), + l2TxData + ); + sendData.sendData(address(polygonZkEVMEtrog), forceBatchData); + + assertEq( + polygonZkEVMEtrog.calculatePolPerForceBatch(), + polygonRollupManager.getForcedBatchFee() + ); + } + + function testRevert_sequenceForceBatches_haltTimeoutNotExpiredAfterEmergencyState() + public + { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[](1); + + vm.prank(admin); + vm.expectRevert( + IPolygonZkEVMVEtrogErrors + .HaltTimeoutNotExpiredAfterEmergencyState + .selector + ); + polygonZkEVMEtrog.sequenceForceBatches(batchData); + } + + function testRevert_sequenceForceBatches_sequenceZeroBatches() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + skip(1 weeks); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[](0); // Empty batchData + + vm.prank(admin); + vm.expectRevert(IPolygonZkEVMErrors.SequenceZeroBatches.selector); + polygonZkEVMEtrog.sequenceForceBatches(batchData); + } + + function testRevert_sequenceForceBatches_exceedMaxVerifyBatches() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + skip(1 weeks); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[]( + MAX_VERIFY_BATCHES + 1 + ); // Exceed max verify batches + + vm.prank(admin); + vm.expectRevert(IPolygonZkEVMErrors.ExceedMaxVerifyBatches.selector); + polygonZkEVMEtrog.sequenceForceBatches(batchData); + } + + function testRevert_sequenceForceBatches_forceBatchesOverflow() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + skip(1 weeks); + + PolygonRollupBaseEtrog.BatchData[] + memory batchData = new PolygonRollupBaseEtrog.BatchData[](1); + + vm.prank(admin); + vm.expectRevert(IPolygonZkEVMErrors.ForceBatchesOverflow.selector); + polygonZkEVMEtrog.sequenceForceBatches(batchData); + } + + function testRevert_sequenceForceBatches_forcedDataDoesNotMatch() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + skip(1 weeks); + + vm.prank(polTokenOwner); + pol.transfer(admin, 1_000); + + vm.startPrank(admin); + pol.approve(address(polygonZkEVMEtrog), 100); + polygonZkEVMEtrog.forceBatch(l2TxData, tokenTransferAmount); + + PolygonRollupBaseEtrog.BatchData[] + memory batchDataArray = new PolygonRollupBaseEtrog.BatchData[](1); + batchDataArray[0] = PolygonRollupBaseEtrog.BatchData( + bytes(""), + bytes32("Random"), + 1000, + bytes32(0) + ); + + vm.expectRevert(IPolygonZkEVMErrors.ForcedDataDoesNotMatch.selector); + polygonZkEVMEtrog.sequenceForceBatches(batchDataArray); + vm.stopPrank(); + } + + function testRevert_sequenceForceBatches_forceBatchTimeoutNotExpired() + public + { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + skip(1 weeks); + + vm.prank(polTokenOwner); + pol.transfer(admin, 1_000); + + bytes32 lastGlobalExitRoot = polygonZkEVMGlobalExitRootV2 + .getLastGlobalExitRoot(); + + vm.startPrank(admin); + pol.approve(address(polygonZkEVMEtrog), 100); + polygonZkEVMEtrog.forceBatch(l2TxData, tokenTransferAmount); + + uint64 currentTime = uint64(block.timestamp); + bytes32 parentHash = blockhash(block.number - 1); + + PolygonRollupBaseEtrog.BatchData[] + memory batchDataArray = new PolygonRollupBaseEtrog.BatchData[](1); + batchDataArray[0] = PolygonRollupBaseEtrog.BatchData( + l2TxData, + lastGlobalExitRoot, + currentTime, + parentHash + ); + + vm.expectRevert( + IPolygonZkEVMErrors.ForceBatchTimeoutNotExpired.selector + ); + polygonZkEVMEtrog.sequenceForceBatches(batchDataArray); + vm.stopPrank(); + } + + function test_sequenceForceBatches() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + skip(1 weeks); + + vm.prank(polTokenOwner); + pol.transfer(admin, 1_000); + + bytes32 lastGlobalExitRoot = polygonZkEVMGlobalExitRootV2 + .getLastGlobalExitRoot(); + + vm.startPrank(admin); + pol.approve(address(polygonZkEVMEtrog), 100); + polygonZkEVMEtrog.forceBatch(l2TxData, tokenTransferAmount); + + uint64 currentTime = uint64(block.timestamp); + bytes32 parentHash = blockhash(block.number - 1); + + PolygonRollupBaseEtrog.BatchData[] + memory batchDataArray = new PolygonRollupBaseEtrog.BatchData[](1); + batchDataArray[0] = PolygonRollupBaseEtrog.BatchData( + l2TxData, + lastGlobalExitRoot, + currentTime, + parentHash + ); + + skip(FORCE_BATCH_TIMEOUT); + uint64 expectedBatchNum = polygonZkEVMEtrog.lastForceBatch() + 1; + bytes32 lastAccInputHash = polygonZkEVMEtrog.lastAccInputHash(); + + vm.expectEmit(); + emit SequenceForceBatches(expectedBatchNum); + polygonZkEVMEtrog.sequenceForceBatches(batchDataArray); + + bytes32 expectedAccInputHash = _calculateAccInputHash( + lastAccInputHash, + keccak256(l2TxData), + lastGlobalExitRoot, + currentTime, + admin, + parentHash + ); + assertEq(polygonZkEVMEtrog.lastAccInputHash(), expectedAccInputHash); + vm.stopPrank(); + } + + function testRevert_onVerifyBatches_onlyRollupManager() public { + vm.prank(address(polygonRollupManager)); + _initializePolygonZkEVMEtrog(); + + vm.expectRevert(IPolygonZkEVMVEtrogErrors.OnlyRollupManager.selector); + polygonZkEVMEtrog.onVerifyBatches(0, bytes32(0), trustedAggregator); + } + + function _initializePolygonZkEVMEtrog() internal { + polygonZkEVMEtrog.initialize( + admin, + trustedSequencer, + networkIDRollup, + address(0), + sequencerURL, + networkName + ); + } + + function _proxify(address logic) internal returns (address proxy) { + TransparentUpgradeableProxy proxy_ = new TransparentUpgradeableProxy( + logic, + msg.sender, + "" + ); + return (address(proxy_)); + } + + function _preDeployPolygonZkEVMBridgeV2() + internal + returns (address implementation) + { + string[] memory exe = new string[](5); + exe[0] = "forge"; + exe[1] = "inspect"; + exe[2] = "PolygonZkEVMBridgeV2"; + exe[3] = "bytecode"; + exe[ + 4 + ] = "--contracts=contracts-ignored-originals/PolygonZkEVMBridgeV2.sol"; + + bytes memory creationCode = vm.ffi(exe); + implementation = makeAddr("PolygonZkEVMBridgeV2"); + + vm.etch(implementation, creationCode); + (bool success, bytes memory runtimeBytecode) = implementation.call(""); + require(success, "Failed to predeploy PolygonZkEVMBridgeV2"); + vm.etch(implementation, runtimeBytecode); + } + + function _calculateAccInputHash( + bytes32 oldAccInputHash, + bytes32 batchHashData, + bytes32 globalExitRoot, + uint64 timestamp, + address sequencerAddress, + bytes32 forcedBlockHash + ) internal pure returns (bytes32) { + return + keccak256( + abi.encodePacked( + oldAccInputHash, + batchHashData, + globalExitRoot, + timestamp, + sequencerAddress, + forcedBlockHash + ) + ); + } +} + +contract SendData { + function sendData(address destination, bytes memory data) public { + (bool success, ) = destination.call(data); + require(success, "SendData: failed to send data"); + } +} diff --git a/test/PolygonZkEVMGlobalExitRootV2.t.sol b/test/PolygonZkEVMGlobalExitRootV2.t.sol new file mode 100644 index 000000000..8d4b522d0 --- /dev/null +++ b/test/PolygonZkEVMGlobalExitRootV2.t.sol @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "test/util/TestHelpers.sol"; + +import {ZkEVMCommon} from "test/util/ZkEVMCommon.sol"; + +import "contracts/interfaces/IBasePolygonZkEVMGlobalExitRoot.sol"; + +import "script/deployers/PolygonZkEVMGlobalExitRootV2Deployer.s.sol"; + +contract PolygonZkEVMGlobalExitRootV2Test is + Test, + TestHelpers, + ZkEVMCommon, + PolygonZkEVMGlobalExitRootV2Deployer +{ + address rollupManager = makeAddr("rollupManager"); + address polygonZkEVMBridge = makeAddr("polygonZkEVMBridge"); + address polygonZkEVMGlobalExitRootV2ProxyOwner = + makeAddr("polygonZkEVMGlobalExitRootV2ProxyOwner"); + + event UpdateL1InfoTree( + bytes32 indexed mainnetExitRoot, + bytes32 indexed rollupExitRoot + ); + + event UpdateL1InfoTreeV2( + bytes32 currentL1InfoRoot, + uint32 indexed leafCount, + uint256 blockhash, + uint64 minTimestamp + ); + + function setUp() public { + deployPolygonZkEVMGlobalExitRootV2Transparent( + polygonZkEVMGlobalExitRootV2ProxyOwner, + rollupManager, + polygonZkEVMBridge + ); + } + + function test_initialize() public view { + assertEq(polygonZkEVMGlobalExitRootV2.rollupManager(), rollupManager); + assertEq( + polygonZkEVMGlobalExitRootV2.bridgeAddress(), + polygonZkEVMBridge + ); + bytes32 zeroHash = bytes32(0); + assertEq(polygonZkEVMGlobalExitRootV2.lastRollupExitRoot(), zeroHash); + assertEq(polygonZkEVMGlobalExitRootV2.lastMainnetExitRoot(), zeroHash); + } + + function testRevert_initialize_reinitialize() public { + vm.expectRevert("Initializable: contract is already initialized"); + polygonZkEVMGlobalExitRootV2.initialize(); + } + + function testRevert_updateExitRoot_onlyAllowedContracts() public { + bytes32 newRoot = keccak256(abi.encode("newRoot")); + vm.expectRevert( + IBasePolygonZkEVMGlobalExitRoot.OnlyAllowedContracts.selector + ); + polygonZkEVMGlobalExitRootV2.updateExitRoot(newRoot); + } + + function test_updateExitRoot_asBridge() public { + bytes32 newRootBridge = keccak256(abi.encode("newRootBridge")); + + vm.recordLogs(); + + vm.prank(polygonZkEVMBridge); + polygonZkEVMGlobalExitRootV2.updateExitRoot(newRootBridge); + bytes32 currentL1InfoRoot = polygonZkEVMGlobalExitRootV2.getRoot(); + uint256 leafCount = polygonZkEVMGlobalExitRootV2.depositCount(); + + Vm.Log[] memory entries = vm.getRecordedLogs(); + assertEq(entries.length, 2); + + assertEq(entries[0].topics.length, 3); + assertEq(entries[0].topics[0], UpdateL1InfoTree.selector); + assertEq(entries[0].topics[1], newRootBridge); + assertEq(entries[0].topics[2], bytes32(0)); + + assertEq(entries[1].topics.length, 2); + assertEq(entries[1].topics[0], UpdateL1InfoTreeV2.selector); + assertEq(entries[1].topics[1], bytes32(leafCount)); + ( + bytes32 eventCurrentL1InfoRoot, + uint256 eventBlockhash, + uint64 eventminTimestamp + ) = abi.decode(entries[1].data, (bytes32, uint256, uint64)); + assertEq(eventCurrentL1InfoRoot, currentL1InfoRoot); + assertEq(eventBlockhash, uint256(blockhash(block.number - 1))); + assertEq(eventminTimestamp, uint64(block.timestamp)); + + assertEq( + polygonZkEVMGlobalExitRootV2.lastMainnetExitRoot(), + newRootBridge + ); + assertEq(polygonZkEVMGlobalExitRootV2.lastRollupExitRoot(), bytes32(0)); + + bytes32 expectedGlobalExitRoot = calculateGlobalExitRoot( + newRootBridge, + bytes32(0) + ); + assertEq( + polygonZkEVMGlobalExitRootV2.getLastGlobalExitRoot(), + expectedGlobalExitRoot + ); + } + + function test_updateExitRoot_asRollupManager() public { + bytes32 newRootRollup = keccak256(abi.encode("newRootRollup")); + + vm.recordLogs(); + + vm.prank(rollupManager); + polygonZkEVMGlobalExitRootV2.updateExitRoot(newRootRollup); + bytes32 currentL1InfoRoot = polygonZkEVMGlobalExitRootV2.getRoot(); + uint256 leafCount = polygonZkEVMGlobalExitRootV2.depositCount(); + + Vm.Log[] memory entries = vm.getRecordedLogs(); + assertEq(entries.length, 2); + assertEq(entries[0].topics.length, 3); + assertEq(entries[0].topics[0], UpdateL1InfoTree.selector); + assertEq(entries[0].topics[1], bytes32(0)); + assertEq(entries[0].topics[2], newRootRollup); + + assertEq(entries[1].topics.length, 2); + assertEq(entries[1].topics[0], UpdateL1InfoTreeV2.selector); + assertEq(entries[1].topics[1], bytes32(leafCount)); + ( + bytes32 eventCurrentL1InfoRoot, + uint256 eventBlockhash, + uint64 eventminTimestamp + ) = abi.decode(entries[1].data, (bytes32, uint256, uint64)); + assertEq(eventCurrentL1InfoRoot, currentL1InfoRoot); + assertEq(eventBlockhash, uint256(blockhash(block.number - 1))); + assertEq(eventminTimestamp, uint64(block.timestamp)); + + assertEq( + polygonZkEVMGlobalExitRootV2.lastMainnetExitRoot(), + bytes32(0) + ); + assertEq( + polygonZkEVMGlobalExitRootV2.lastRollupExitRoot(), + newRootRollup + ); + + bytes32 expectedGlobalExitRoot = calculateGlobalExitRoot( + bytes32(0), + newRootRollup + ); + assertEq( + polygonZkEVMGlobalExitRootV2.getLastGlobalExitRoot(), + expectedGlobalExitRoot + ); + } + + function calculateGlobalExitRoot( + bytes32 mainnetExitRoot, + bytes32 rollupExitRoot + ) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(mainnetExitRoot, rollupExitRoot)); + } +} diff --git a/test/contractsv2/PolygonRollupManager.test.ts b/test/contractsv2/PolygonRollupManager.test.ts index 18272eae2..d508058e1 100644 --- a/test/contractsv2/PolygonRollupManager.test.ts +++ b/test/contractsv2/PolygonRollupManager.test.ts @@ -370,6 +370,21 @@ describe("Polygon Rollup Manager", () => { ) ).to.be.revertedWithCustomError(rollupManagerContract, "ChainIDOutOfRange"); + // UNexisting rollupType: other branch + await expect( + rollupManagerContract + .connect(admin) + .createNewRollup( + 999999999, + chainID, + admin.address, + trustedSequencer.address, + gasTokenAddress, + urlSequencer, + networkName + ) + ).to.be.revertedWithCustomError(rollupManagerContract, "RollupTypeDoesNotExist"); + // UNexisting rollupType await expect( rollupManagerContract diff --git a/test/contractsv2/PolygonValidiumEtrog.test.ts b/test/contractsv2/PolygonValidiumEtrog.test.ts index cb9199f82..504c04eab 100644 --- a/test/contractsv2/PolygonValidiumEtrog.test.ts +++ b/test/contractsv2/PolygonValidiumEtrog.test.ts @@ -16,7 +16,7 @@ import { import {takeSnapshot, time} from "@nomicfoundation/hardhat-network-helpers"; import {processorUtils, contractUtils, MTBridge, mtBridgeUtils} from "@0xpolygonhermez/zkevm-commonjs"; import {array} from "yargs"; -import {PolygonDataCommittee} from "../../typechain-types/contracts/v2/consensus/dataComittee"; +import {PolygonDataCommittee} from "../../typechain-types/contracts/consensus/validium"; const {calculateSnarkInput, calculateAccInputHash, calculateBatchHashData} = contractUtils; type BatchDataStructEtrog = PolygonRollupBaseEtrog.BatchDataStruct; diff --git a/test/util/TestHelpers.sol b/test/util/TestHelpers.sol new file mode 100644 index 000000000..44493805b --- /dev/null +++ b/test/util/TestHelpers.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; + +abstract contract TestHelpers is Test { + Account internal DEPLOYER; + + constructor() { + DEPLOYER = makeAccount("DEPLOYER"); + vm.setEnv("PRIVATE_KEY", vm.toString(DEPLOYER.key)); + } +} diff --git a/test/util/ZkEVMCommon.sol b/test/util/ZkEVMCommon.sol new file mode 100644 index 000000000..7994b1778 --- /dev/null +++ b/test/util/ZkEVMCommon.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; + +abstract contract ZkEVMCommon is Test { + string constant MERKLE_TREE_HEIGHT = "32"; // As of now, the height of the Merkle tree is fixed to 32 + + function _encodeLeaves( + bytes32[] memory leaves + ) public pure returns (string memory encodedLeaves) { + for (uint i = 0; i < leaves.length; i++) { + encodedLeaves = string( + abi.encodePacked( + encodedLeaves, + _bytes32ToHex(abi.encodePacked(leaves[i])) + ) + ); + } + } + + function _getMerkleTreeRoot( + string memory encodedLeaves + ) public returns (bytes32) { + string[] memory operation = new string[](5); + operation[0] = "node"; + operation[1] = "tools/zkevm-commonjs-wrapper"; + operation[2] = "makeTreeAndGetRoot"; + operation[3] = MERKLE_TREE_HEIGHT; + operation[4] = encodedLeaves; + + bytes memory result = vm.ffi(operation); + return abi.decode(result, (bytes32)); + } + + function _getProofByIndex( + string memory encodedLeaves, + string memory index + ) public returns (bytes32[32] memory) { + string[] memory operation = new string[](6); + operation[0] = "node"; + operation[1] = "tools/zkevm-commonjs-wrapper"; + operation[2] = "makeTreeAndGetProofByIndex"; + operation[3] = MERKLE_TREE_HEIGHT; + operation[4] = encodedLeaves; + operation[5] = index; + + bytes memory result = vm.ffi(operation); + return abi.decode(result, (bytes32[32])); + } + + function _bytes32ToHex( + bytes memory buffer + ) internal pure returns (string memory) { + bytes memory converted = new bytes(buffer.length * 2); + + bytes memory _base = "0123456789abcdef"; + + for (uint256 i = 0; i < buffer.length; i++) { + converted[i * 2] = _base[uint8(buffer[i]) / _base.length]; + converted[i * 2 + 1] = _base[uint8(buffer[i]) % _base.length]; + } + + return string(abi.encodePacked(converted)); // do not append "0x" prefix for encoding + } +} diff --git a/tools/zkevm-commonjs-wrapper.js b/tools/zkevm-commonjs-wrapper.js new file mode 100644 index 000000000..1de6992a4 --- /dev/null +++ b/tools/zkevm-commonjs-wrapper.js @@ -0,0 +1,118 @@ +/* eslint-disable no-undef */ +/* eslint-disable no-console */ +const { + MemDB, ZkEVMDB, getPoseidon, smtUtils, MTBridge, mtBridgeUtils, +} = require('@0xpolygonhermez/zkevm-commonjs'); + +const { verifyMerkleProof } = mtBridgeUtils; + +const LEAF_LENGTH = 32 * 2; // 32 bytes * 2 hex characters per byte + +function decodeLeaves(leavesEncoded) { + const leaves = []; + const numberOfLeaves = leavesEncoded.length / LEAF_LENGTH; + for (let i = 0; i < numberOfLeaves; i++) { + leaves.push(`0x${leavesEncoded.slice(i * LEAF_LENGTH, (i + 1) * LEAF_LENGTH)}`); + } + return leaves; +} + +function makeTreeAndGetRoot(height, encodedLeaves) { + const leaves = decodeLeaves(encodedLeaves); + const tree = new MTBridge(height); + leaves.forEach((leaf) => tree.add(leaf)); + console.log(tree.getRoot()); + return tree.getRoot(); +} + +function makeTreeAndGetProofByIndex(height, encodedLeaves, index) { + const leaves = decodeLeaves(encodedLeaves); + index = parseInt(index, 10); + const tree = new MTBridge(height); + leaves.forEach((leaf) => tree.add(leaf)); + const proof = tree.getProofTreeByIndex(index); + const proofBytesString = `0x${proof.reduce((acc, el) => acc + el.slice(2), '')}`; + console.log(proofBytesString); + return tree.getProofTreeByValue(index); +} + +function makeTreeAndVerifyProof(height, encodedLeaves, index, root) { + const leaves = decodeLeaves(encodedLeaves); + index = parseInt(index, 10); + const tree = new MTBridge(height); + leaves.forEach((leaf) => tree.add(leaf)); + const proof = tree.getProofTreeByValue(index); + console.log(verifyMerkleProof(leaves[index], proof, index, root)); + return verifyMerkleProof(leaves[index], proof, index, root); +} + +async function calculateRoot(genesisJson) { + const parsedGenesis = JSON.parse(genesisJson); + + const genesis = []; + // eslint-disable-next-line no-restricted-syntax + for (entry of parsedGenesis.genesis) { + if (entry.contractName !== null) { + genesis.push({ + contractName: entry.contractName, + balance: BigInt(entry.balance), + nonce: BigInt(entry.nonce), + address: entry.address, + bytecode: entry.bytecode, + storage: entry.storage, + }); + } else if (entry.accountName !== null) { + genesis.push({ + accountName: entry.accountName, + balance: BigInt(entry.balance), + nonce: BigInt(entry.nonce), + address: entry.address, + }); + } + } + + const poseidon = await getPoseidon(); + const { F } = poseidon; + const db = new MemDB(F); + const genesisRoot = [F.zero, F.zero, F.zero, F.zero]; + const accHashInput = [F.zero, F.zero, F.zero, F.zero]; + const defaultChainId = 1000; + + const zkEVMDB = await ZkEVMDB.newZkEVM( + db, + poseidon, + genesisRoot, + accHashInput, + genesis, + null, + null, + defaultChainId, + ); + + const root = smtUtils.h4toString(zkEVMDB.stateRoot); + console.log(root); + return root; +} + +function main(args) { + const [command, ...rest] = args; + switch (command) { + case 'makeTreeAndGetRoot': + return makeTreeAndGetRoot(...rest); + case 'makeTreeAndGetProofByIndex': + return makeTreeAndGetProofByIndex(...rest); + case 'makeTreeAndVerifyProof': + return makeTreeAndVerifyProof(...rest); + case 'calculateRoot': + return calculateRoot(...rest); + default: + throw new Error('Usage: zkevm-commonjs-wrapper.js '); + } +} + +try { + main(process.argv.slice(2)); +} catch (e) { + console.error(e); + process.exit(1); +} diff --git a/verifyMainnetDeployment/verifyMainnetDeployment.js b/verifyMainnetDeployment/verifyMainnetDeployment.js index 0b7dc7b28..4e5e9fa5e 100644 --- a/verifyMainnetDeployment/verifyMainnetDeployment.js +++ b/verifyMainnetDeployment/verifyMainnetDeployment.js @@ -9,13 +9,13 @@ const mainnetDeployParameters = require("./mainnetDeployParameters.json"); const pathFflonkVerifier = '../artifacts/contracts/verifiers/FflonkVerifier.sol/FflonkVerifier.json'; const pathPolygonZkEVMDeployer = '../artifacts/contracts/deployment/PolygonZkEVMDeployer.sol/PolygonZkEVMDeployer.json'; -const pathPolygonZkEVMBridge = '../artifacts/contracts/PolygonZkEVMBridge.sol/PolygonZkEVMBridge.json'; +const pathPolygonZkEVMBridge = '../artifacts/contracts/outdated/PolygonZkEVMBridge.sol/PolygonZkEVMBridge.json'; const pathTransparentProxyOZUpgradeDep = '../node_modules/@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json'; const pathProxyAdmin = '../artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json'; const pathTransparentProxy = '../artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json'; -const pathPolygonZkEVMTimelock = '../artifacts/contracts/PolygonZkEVMTimelock.sol/PolygonZkEVMTimelock.json'; -const pathPolygonZkEVM = '../artifacts/contracts/PolygonZkEVM.sol/PolygonZkEVM.json'; -const pathPolygonZkEVMGlobalExitRoot = '../artifacts/contracts/PolygonZkEVMGlobalExitRoot.sol/PolygonZkEVMGlobalExitRoot.json'; +const pathPolygonZkEVMTimelock = '../artifacts/contracts/outdated/PolygonZkEVMTimelock.sol/PolygonZkEVMTimelock.json'; +const pathPolygonZkEVM = '../artifacts/contracts/outdated/PolygonZkEVM.sol/PolygonZkEVM.json'; +const pathPolygonZkEVMGlobalExitRoot = '../artifacts/contracts/outdated/PolygonZkEVMGlobalExitRoot.sol/PolygonZkEVMGlobalExitRoot.json'; const FflonkVerifier = require(pathFflonkVerifier); const PolygonZkEVMDeployer = require(pathPolygonZkEVMDeployer);