diff --git a/.github/workflows/build-release-manual.yaml b/.github/workflows/build-release.yaml similarity index 100% rename from .github/workflows/build-release-manual.yaml rename to .github/workflows/build-release.yaml diff --git a/.github/workflows/buld-release.yaml b/.github/workflows/buld-release.yaml deleted file mode 100644 index c2be06f80..000000000 --- a/.github/workflows/buld-release.yaml +++ /dev/null @@ -1,50 +0,0 @@ -name: Build and release - -on: - push: - branches: - - "*" - -jobs: - build-contracts: - runs-on: ubuntu-latest - - steps: - - name: Checkout the repository - uses: actions/checkout@v4 - - - name: Use Node.js - uses: actions/setup-node@v3 - with: - node-version: 18.18.0 - cache: yarn - - - name: Init - id: init - run: | - yarn - echo "release_tag=$(echo ${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}})-$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - - - name: Build contracts - run: | - yarn l1 build - yarn l2 build - yarn sc build - - - name: Prepare artifacts - run: | - tar -czvf l1-contracts.tar.gz ./l1-contracts - tar -czvf l2-contracts.tar.gz ./l2-contracts - tar -czvf system-contracts.tar.gz ./system-contracts - - - name: Release - uses: softprops/action-gh-release@v1 - with: - tag_name: ${{ steps.init.outputs.release_tag }} - fail_on_unmatched_files: true - target_commitish: ${{ github.sha }} - body: "" - files: | - l1-contracts.tar.gz - l2-contracts.tar.gz - system-contracts.tar.gz diff --git a/.github/workflows/l1-contracts-foundry-ci.yaml b/.github/workflows/l1-contracts-foundry-ci.yaml index 367ea2977..02c551d63 100644 --- a/.github/workflows/l1-contracts-foundry-ci.yaml +++ b/.github/workflows/l1-contracts-foundry-ci.yaml @@ -6,6 +6,7 @@ env: on: pull_request: + jobs: build: runs-on: ubuntu-latest @@ -28,7 +29,7 @@ jobs: run: yarn - name: Build artifacts - working-directory: ./l1-contracts-foundry + working-directory: ./l1-contracts run: forge build - name: Build system-contract artifacts @@ -39,8 +40,8 @@ jobs: with: key: artifacts-l1-contracts-foudry-${{ github.sha }} path: | - l1-contracts-foundry/cache - l1-contracts-foundry/out + l1-contracts/cache + l1-contracts/out system-contracts/artifacts-zk system-contracts/bootloader/build system-contracts/cache-zk @@ -62,8 +63,8 @@ jobs: fail-on-cache-miss: true key: artifacts-l1-contracts-foudry-${{ github.sha }} path: | - l1-contracts-foundry/cache - l1-contracts-foundry/out + l1-contracts/cache + l1-contracts/out system-contracts/artifacts-zk system-contracts/bootloader/build system-contracts/cache-zk @@ -73,6 +74,10 @@ jobs: - name: Use Foundry uses: foundry-rs/foundry-toolchain@v1 + - name: Copy configs from template + working-directory: ./l1-contracts + run: cp -r deploy-script-config-template script-config + - name: Run anvil run: | anvil --silent & @@ -95,17 +100,18 @@ jobs: fi - name: Run DeployL1 script - working-directory: ./l1-contracts-foundry - run: forge script ./script/DeployL1.s.sol --ffi --rpc-url $ANVIL_RPC_URL --broadcast --private-key $ANVIL_PRIVATE_KEY - - - name: Run RegisterHyperchain script - working-directory: ./l1-contracts-foundry - run: forge script ./script/RegisterHyperchain.s.sol --ffi --rpc-url $ANVIL_RPC_URL --broadcast --private-key $ANVIL_PRIVATE_KEY + working-directory: ./l1-contracts + run: forge script ./deploy-scripts/DeployL1.s.sol --ffi --rpc-url $ANVIL_RPC_URL --broadcast --private-key $ANVIL_PRIVATE_KEY - name: Run DeployErc20 script - working-directory: ./l1-contracts-foundry - run: forge script ./script/DeployErc20.s.sol --ffi --rpc-url $ANVIL_RPC_URL --broadcast --private-key $ANVIL_PRIVATE_KEY - - - name: Run InitializeL2WethToken script - working-directory: ./l1-contracts-foundry - run: forge script ./script/InitializeL2WethToken.s.sol --ffi --rpc-url $ANVIL_RPC_URL --broadcast --private-key $ANVIL_PRIVATE_KEY + working-directory: ./l1-contracts + run: forge script ./deploy-scripts/DeployErc20.s.sol --ffi --rpc-url $ANVIL_RPC_URL --broadcast --private-key $ANVIL_PRIVATE_KEY +# TODO restore scripts verification +# - name: Run RegisterHyperchain script +# working-directory: ./l1-contracts +# run: | +# cat ./script-out/output-deploy-l1.toml >> ./script-config/register-hyperchain.toml +# forge script ./deploy-scripts/RegisterHyperchain.s.sol --ffi --rpc-url $ANVIL_RPC_URL --broadcast --private-key $ANVIL_PRIVATE_KEY +# - name: Run InitializeL2WethToken script +# working-directory: ./l1-contracts-foundry +# run: forge script ./deploy-scripts/InitializeL2WethToken.s.sol --ffi --rpc-url $ANVIL_RPC_URL --broadcast --private-key $ANVIL_PRIVATE_KEY diff --git a/.gitignore b/.gitignore index 6cb7fef4f..63f96063b 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,7 @@ l1-contracts/lcov.info l1-contracts/report/* l1-contracts/coverage/* l1-contracts/out/* -l1-contracts-foundry/broadcast/* -l1-contracts-foundry/script-out/* -!l1-contracts-foundry/script-out/.gitkeep -*.timestamp +l1-contracts/broadcast/* +l1-contracts/script-config/* +l1-contracts/script-out/* +!l1-contracts/script-out/.gitkeep diff --git a/.gitmodules b/.gitmodules index 2a0ad281f..5cbc631ba 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,12 +4,11 @@ [submodule "l1-contracts/lib/murky"] path = l1-contracts/lib/murky url = https://github.com/dmfxyz/murky -[submodule "l1-contracts-foundry/lib/forge-std"] - path = l1-contracts-foundry/lib/forge-std - url = https://github.com/foundry-rs/forge-std -[submodule "l1-contracts-foundry/lib/openzeppelin-contracts"] - path = l1-contracts-foundry/lib/openzeppelin-contracts - url = https://github.com/Openzeppelin/openzeppelin-contracts -[submodule "l1-contracts-foundry/lib/openzeppelin-contracts-upgradeable"] - path = l1-contracts-foundry/lib/openzeppelin-contracts-upgradeable +[submodule "l1-contracts/lib/openzeppelin-contracts-upgradeable"] + path = l1-contracts/lib/openzeppelin-contracts-upgradeable url = https://github.com/Openzeppelin/openzeppelin-contracts-upgradeable + branch = release-v4.9 +[submodule "l1-contracts/lib/openzeppelin-contracts"] + path = l1-contracts/lib/openzeppelin-contracts + url = https://github.com/Openzeppelin/openzeppelin-contracts + branch = release-v4.9 diff --git a/.solhintignore b/.solhintignore index d6471dfce..8e9f84ade 100644 --- a/.solhintignore +++ b/.solhintignore @@ -22,3 +22,6 @@ system-contracts/contracts/openzeppelin system-contracts/contracts/Constants.sol system-contracts/contracts/test-contracts system-contracts/contracts-preprocessed + +# gas-bound-caller +gas-bound-caller/contracts/test-contracts diff --git a/gas-bound-caller/README.md b/gas-bound-caller/README.md new file mode 100644 index 000000000..71962937b --- /dev/null +++ b/gas-bound-caller/README.md @@ -0,0 +1,51 @@ +# GasBoundCaller + +Starting from v24 On Era, the gas for pubdata is charged at the end of the execution of the entire transaction. This means that if a subcall is not trusted, it can consume a significant amount of pubdata during the process. While this may not be an issue for most contracts, there are use cases, e.g., for relayers, where it is crucial to ensure that the subcall will not spend more money than intended. + +The `GasBoundCaller` is a contract with the following interface: + +```solidity +function gasBoundCall(address _to, uint256 _maxTotalGas, bytes calldata _data) external payable +``` + +> Note that the amount of gas passed into this function should be less than or equal to `_maxTotalGas`. If the computational gas provided is higher than `_maxTotalGas`, the higher value will be used. + +This contract will call the address `_to` with the entire execution gas passed to it, while ensuring that the total consumed gas does not exceed `_maxTotalGas` under any circumstances. + +If the call to the `_to` address fails, the gas used on pubdata is considered zero, and the total gas used is fully equivalent to the gas consumed within the execution. The `GasBoundCaller` will relay the revert message as-is. + +If the call to the `_to` address succeeds, the `GasBoundCaller` will ensure that the total consumed gas does not exceed `_maxTotalGas`. If it does, it will revert with a "Not enough gas for pubdata" error. If the total consumed gas is less than or equal to `_maxTotalGas`, the `GasBoundCaller` will return returndata equal to `abi.encode(bytes returndataFromSubcall, uint256 gasUsedForPubdata)`. + +## Usage + +Summing up the information from the previous chapter, the `GasBoundCaller` should be used in the following way: + +TODO(EVM-585): switch `addr` with address. + +```solidity +uint256 computeGasBefore = gasleft(); + +(bool success, bytes memory returnData) = address(this).call{gas: _gasToPass}(abi.encodeWithSelector(GasBoundCaller.gasBoundCall.selector, _to, _maxTotalGas, _data)); + +uint256 pubdataGasSpent; +if (success) { + (returnData, pubdataGasSpent) = abi.decode(returnData, (bytes, uint256)); +} else { + // `returnData` is fully equal to the returndata, while `pubdataGasSpent` is equal to 0 +} + +uint256 computeGasAfter = gasleft(); + +// This is the total gas that the subcall made the transaction to be charged for +uint256 totalGasConsumed = computeGasBefore - computeGasAfter + pubdataGasSpent; +``` + +### Preserving `msg.sender` + +Since `GasBoundCaller` would be the contract that calls the `_to` contract, the `msg.sender` will be equal to the `GasBoundCaller`'s address. To preserve the current `msg.sender`, this contract can be inherited from and used the same way, but instead of calling `GasBoundCaller.gasBoundCall`, `this.gasBoundCall` could be called. + +## Deployed Address + +It should be deployed via a built-in CREATE2 factory on each individual chain. + +TODO(EVM-585) diff --git a/gas-bound-caller/canonical-bytecodes/GasBoundCaller b/gas-bound-caller/canonical-bytecodes/GasBoundCaller new file mode 100644 index 000000000..17dcacc84 --- /dev/null +++ b/gas-bound-caller/canonical-bytecodes/GasBoundCaller @@ -0,0 +1 @@ +0x00120000000000020006000000000002000000000301001900000060043002700000009e03400197000100000031035500020000003103550003000000310355000400000031035500050000003103550006000000310355000700000031035500080000003103550009000000310355000a000000310355000b000000310355000c000000310355000d000000310355000e000000310355000f000000310355001000000031035500110000000103550000009e0040019d0000008004000039000000400040043f00000001022001900000004d0000c13d000000040230008c000000a50000413d000000000201043b000000a002200197000000a10220009c000000a50000c13d000000040230008a000000600220008c000000a50000413d0000000402100370000000000802043b000000a20280009c000000a50000213d0000004402100370000000000202043b000000a30420009c000000a50000213d0000002304200039000000a405000041000000000634004b00000000060000190000000006058019000000a404400197000000000704004b0000000005008019000000a40440009c000000000506c019000000000405004b000000a50000c13d0000000404200039000000000441034f000000000604043b000000a30460009c000000a50000213d00000024052000390000000004560019000000000234004b000000a50000213d0000002401100370000000000201043b0000000003000414000003200100008a000000000113004b000000550000413d000000b10100004100000000001004350000001101000039000000040010043f000000b20100004100000274000104300000000001000416000000000101004b000000a50000c13d0000002001000039000001000010044300000120000004430000009f01000041000002730001042e0000000001000414000000000121004b000000620000a13d000000af01000041000000800010043f0000002001000039000000840010043f0000001401000039000000a40010043f000000b401000041000000c40010043f000000b5010000410000027400010430000200000006001d000100000005001d000500000004001d000600000008001d000000a501000041000000800010043f0000009e01000041000400000002001d0000000002000414000300000003001d0000009e0320009c0000000002018019000000c001200210000000a6011001c70000800b02000039027202680000040f00000003030000290000000403300069000003200a30008a0000000403a0006c00000000030000190000000103002039000000000303004b000000000a00c019000000000301001900000060033002700000009e03300197000000200430008c000000000403001900000020040080390000001f0540018f00000005064002720000008c0000613d00000000070000190000000508700210000000000981034f000000000909043b000000800880003900000000009804350000000107700039000000000867004b000000840000413d000000000705004b000000060b0000290000009c0000613d0000000506600210000000000761034f00000003055002100000008006600039000000000806043300000000085801cf000000000858022f000000000707043b0000010005500089000000000757022f00000000055701cf000000000585019f00000000005604350000000102200190000000050c000029000000a70000613d0000001f01400039000000600210018f00000080012001bf000000400010043f000000200330008c000000ca0000813d00000000010000190000027400010430000000400200043d0000001f0430018f0000000505300272000000b40000613d000000000600001900000005076002100000000008720019000000000771034f000000000707043b00000000007804350000000106600039000000000756004b000000ac0000413d000000000604004b000000c30000613d0000000505500210000000000151034f00000000055200190000000304400210000000000605043300000000064601cf000000000646022f000000000101043b0000010004400089000000000141022f00000000014101cf000000000161019f00000000001504350000009e010000410000009e0420009c000000000201801900000040012002100000006002300210000000000121019f0000027400010430000000800900043d0000000004000414000000000600003100000011050003670000000003000416000000000703004b000000e80000c13d000000020300006b000000da0000613d00000001030000290000009e0330019700010000003503550000000007c6004b000000470000413d000000000535034f0000000006c600490000009e0360019700010000003503e50000009e0640009c000000f50000213d000400000009001d00050000000a001d00000000013503df000000c002400210000000a802200197000000aa022001c700010000002103b500000000012103af00000000020b00190000010f0000013d000000020700006b000000f10000613d00000001070000290000009e0770019700010000007503550000000008c6004b000000470000413d000000000575034f0000000006c600490000009e0660019700010000006503e5000000a70740009c000001030000413d000000af03000041000000000031043500000084032001bf00000020040000390000000000430435000000c403200039000000b3040000410000000000430435000000a402200039000000080300003900000000003204350000004001100210000000b0011001c70000027400010430000400000009001d00050000000a001d00000000016503df000000c002400210000000a802200197000000a9022001c700010000002103b500000000012103af000080090200003900000000040b0019000000000500001900000000060000190272026d0000040f000000000301001900000060033002700000009e033001970000000102200190000001d90000613d0000003f02300039000000ab02200197000000400600043d0000000002260019000000000462004b00000000040000190000000104004039000000a30520009c000001d50000213d0000000104400190000001d50000c13d000000400020043f000200000006001d00000000023604360000001f043000390000000504400272000001310000613d00000000050000310000001105500367000000000600001900000005076002100000000008720019000000000775034f000000000707043b00000000007804350000000106600039000000000746004b000001290000413d000000000400004b000001330000613d0000001f0430018f00000005033002720000013f0000613d000000000500001900000005065002100000000007620019000000000661034f000000000606043b00000000006704350000000105500039000000000635004b000001370000413d000000000504004b0000014e0000613d0000000503300210000000000131034f00000000023200190000000303400210000000000402043300000000043401cf000000000434022f000000000101043b0000010003300089000000000131022f00000000013101cf000000000141019f0000000000120435000000400400043d000600000004001d000000a50100004100000000001404350000009e0100004100000000020004140000009e0320009c00000000020180190000009e0340009c00000000010440190000004001100210000000c002200210000000000112019f000000ac011001c70000800b02000039027202680000040f000000060a000029000000000301001900000060033002700000009e03300197000000200430008c000000000403001900000020040080390000001f0540018f0000000506400272000001710000613d0000000007000019000000050870021000000000098a0019000000000881034f000000000808043b00000000008904350000000107700039000000000867004b000001690000413d00000000090a0019000000000705004b000001810000613d0000000506600210000000000761034f00000000066900190000000305500210000000000806043300000000085801cf000000000858022f000000000707043b0000010005500089000000000757022f00000000055701cf000000000585019f00000000005604350000000102200190000001f40000613d0000001f01400039000000600110018f0000000002910019000000000112004b00000000010000190000000101004039000300000002001d000000a30220009c000001d50000213d0000000101100190000001d50000c13d0000000301000029000000400010043f000000200130008c0000000501000029000000a50000413d000500000001001d0000000001090433000600000001001d000000ad01000041000000030400002900000000001404350000009e0100004100000000020004140000009e0320009c00000000020180190000009e0340009c00000000010440190000004001100210000000c002200210000000000112019f000000ac011001c70000800b02000039027202680000040f000000030b0000290000000605000029000000040450006a000000000354004b00000000030000190000000103002039000000000303004b000000000400c019000000000301001900000060033002700000009e03300197000000200530008c000000000503001900000020050080390000001f0650018f0000000507500272000001bf0000613d00000000080000190000000509800210000000000a9b0019000000000991034f000000000909043b00000000009a04350000000108800039000000000978004b000001b70000413d000000000806004b000001ce0000613d0000000507700210000000000871034f00000003077000290000000306600210000000000907043300000000096901cf000000000969022f000000000808043b0000010006600089000000000868022f00000000066801cf000000000696019f00000000006704350000000102200190000002110000613d0000001f01500039000000600110018f0000000301100029000000a30210009c0000022e0000a13d000000b101000041000000000010043500000041010000390000004a0000013d0000001f0430018f0000000502300272000001e40000613d00000000050000190000000506500210000000000761034f000000000707043b00000000007604350000000105500039000000000625004b000001dd0000413d000000000504004b000001f20000613d00000003044002100000000502200210000000000502043300000000054501cf000000000545022f000000000121034f000000000101043b0000010004400089000000000141022f00000000014101cf000000000151019f000000000012043500000060013002100000027400010430000000400200043d0000001f0430018f0000000505300272000002010000613d000000000600001900000005076002100000000008720019000000000771034f000000000707043b00000000007804350000000106600039000000000756004b000001f90000413d000000000604004b000002100000613d0000000505500210000000000151034f00000000055200190000000304400210000000000605043300000000064601cf000000000646022f000000000101043b0000010004400089000000000141022f00000000014101cf000000000161019f0000000000150435000000c30000013d000000400200043d0000001f0430018f00000005053002720000021e0000613d000000000600001900000005076002100000000008720019000000000771034f000000000707043b00000000007804350000000106600039000000000756004b000002160000413d000000000604004b0000022d0000613d0000000505500210000000000151034f00000000055200190000000304400210000000000605043300000000064601cf000000000646022f000000000101043b0000010004400089000000000141022f00000000014101cf000000000161019f0000000000150435000000c30000013d000000400010043f000000200230008c000000a50000413d0000000302000029000000000302043300000000524300a9000000000503004b0000023b0000613d00000000533200d9000000000343004b000000470000c13d000000000302004b0000024c0000c13d0000000203000029000000200130008a0000000000210435000000400130008a00000040020000390000000000210435000000000203043300000060022000390000009e030000410000009e0420009c00000000020380190000009e0410009c000000000103801900000040011002100000006002200210000000000112019f000002730001042e00000000040004140000000503400029000000000443004b000000000400001900000001040040390000000104400190000000470000c13d000001910400008a000000000442004b000000470000213d0000019004200039000000000343004b0000023b0000813d0000004402100039000000ae03000041000000000032043500000024021000390000001a030000390000000000320435000000af0200004100000000002104350000000402100039000000200300003900000000003204350000009e020000410000009e0310009c0000000001028019000001000000013d0000026b002104230000000102000039000000000001042d0000000002000019000000000001042d00000270002104210000000102000039000000000001042d0000000002000019000000000001042d0000027200000432000002730001042e000002740001043000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0000000200000000000000000000000000000040000001000000000000000000ffffffff0000000000000000000000000000000000000000000000000000000041a8596c00000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000ffffffffffffffff8000000000000000000000000000000000000000000000000000000000000000c0d5b949000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000800000000000000000000000000000000000000000000000000000000000000000000000010000000000000000ffffffff0000000000000000000000000000000000000000000000000100000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001ffffffe000000000000000000000000000000000000000040000000000000000000000007cb9357e000000000000000000000000000000000000000000000000000000004e6f7420656e6f7567682067617320666f72207075626461746100000000000008c379a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000640000000000000000000000004e487b710000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000240000000000000000000000004f766572666c6f77000000000000000000000000000000000000000000000000476173206c696d697420697320746f6f206c6f7700000000000000000000000000000000000000000000000000000000000000640000008000000000000000006c300d3e2b009b9a5b1fe7d853a9ae64e30e1d513df698d5c9801da939df7831 \ No newline at end of file diff --git a/system-contracts/contracts/GasBoundCaller.sol b/gas-bound-caller/contracts/GasBoundCaller.sol similarity index 72% rename from system-contracts/contracts/GasBoundCaller.sol rename to gas-bound-caller/contracts/GasBoundCaller.sol index 47b12b6ff..78af446ca 100644 --- a/system-contracts/contracts/GasBoundCaller.sol +++ b/gas-bound-caller/contracts/GasBoundCaller.sol @@ -2,10 +2,12 @@ pragma solidity 0.8.20; -import {EfficientCall} from "./libraries/EfficientCall.sol"; -import {REAL_SYSTEM_CONTEXT_CONTRACT} from "./Constants.sol"; +import {EfficientCall} from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/EfficientCall.sol"; +import {ISystemContext} from "./ISystemContext.sol"; import {InsufficientGas} from "./SystemContractErrors.sol"; +ISystemContext constant SYSTEM_CONTEXT_CONTRACT = ISystemContext(address(0x800b)); + /** * @author Matter Labs * @custom:security-contact security@matterlabs.dev @@ -19,7 +21,7 @@ contract GasBoundCaller { uint256 internal constant CALL_ENTRY_OVERHEAD = 800; /// @notice We assume that no more than `CALL_RETURN_OVERHEAD` gas are used for the O(1) operations at the end of the execution, /// as such relaying the return. - uint256 internal constant CALL_RETURN_OVERHEAD = 200; + uint256 internal constant CALL_RETURN_OVERHEAD = 400; /// @notice The function that implements limiting of the total gas expenditure of the call. /// @dev On Era, the gas for pubdata is charged at the end of the execution of the entire transaction, meaning @@ -51,7 +53,7 @@ contract GasBoundCaller { // This is the amount of gas that can be spent *exclusively* on pubdata in addition to the `gas` provided to this function. uint256 pubdataAllowance = _maxTotalGas > expectedForCompute ? _maxTotalGas - expectedForCompute : 0; - uint256 pubdataPublishedBefore = REAL_SYSTEM_CONTEXT_CONTRACT.getCurrentPubdataSpent(); + uint256 pubdataPublishedBefore = SYSTEM_CONTEXT_CONTRACT.getCurrentPubdataSpent(); // We never permit system contract calls. // If the call fails, the `EfficientCall.call` will propagate the revert. @@ -65,7 +67,16 @@ contract GasBoundCaller { _isSystem: false }); - uint256 pubdataPublishedAfter = REAL_SYSTEM_CONTEXT_CONTRACT.getCurrentPubdataSpent(); + // We will calculate the length of the returndata to be used at the end of the function. + // We need additional `96` bytes to encode the offset `0x40` for the entire pubdata, + // the gas spent on pubdata as well as the length of the original returndata. + // Note, that it has to be padded to the 32 bytes to adhere to proper ABI encoding. + uint256 paddedReturndataLen = returnData.length + 96; + if (paddedReturndataLen % 32 != 0) { + paddedReturndataLen += 32 - (paddedReturndataLen % 32); + } + + uint256 pubdataPublishedAfter = SYSTEM_CONTEXT_CONTRACT.getCurrentPubdataSpent(); // It is possible that pubdataPublishedAfter < pubdataPublishedBefore if the call, e.g. removes // some of the previously created state diffs @@ -73,7 +84,7 @@ contract GasBoundCaller { ? pubdataPublishedAfter - pubdataPublishedBefore : 0; - uint256 pubdataGasRate = REAL_SYSTEM_CONTEXT_CONTRACT.gasPerPubdataByte(); + uint256 pubdataGasRate = SYSTEM_CONTEXT_CONTRACT.gasPerPubdataByte(); // In case there is an overflow here, the `_maxTotalGas` wouldn't be able to cover it anyway, so // we don't mind the contract panicking here in case of it. @@ -88,8 +99,16 @@ contract GasBoundCaller { } assembly { - // We just relay the return data from the call. - return(add(returnData, 0x20), mload(returnData)) + // This place does interfere with the memory layout, however, it is done right before + // the `return` statement, so it is safe to do. + // We need to transform `bytes memory returnData` into (bytes memory returndata, gasSpentOnPubdata) + // `bytes memory returnData` is encoded as `length` + `data`. + // We need to prepend it with 0x40 and `pubdataGas`. + // + // It is assumed that the position of returndata is >= 0x40, since 0x40 is the free memory pointer location. + mstore(sub(returnData, 0x40), 0x40) + mstore(sub(returnData, 0x20), pubdataGas) + return(sub(returnData, 0x40), paddedReturndataLen) } } } diff --git a/gas-bound-caller/contracts/ISystemContext.sol b/gas-bound-caller/contracts/ISystemContext.sol new file mode 100644 index 000000000..a122a04f5 --- /dev/null +++ b/gas-bound-caller/contracts/ISystemContext.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +/** + * @author Matter Labs + * @custom:security-contact security@matterlabs.dev + * @notice Contract that stores some of the context variables, that may be either + * block-scoped, tx-scoped or system-wide. + */ +interface ISystemContext { + struct BlockInfo { + uint128 timestamp; + uint128 number; + } + + /// @notice A structure representing the timeline for the upgrade from the batch numbers to the L2 block numbers. + /// @dev It will be used for the L1 batch -> L2 block migration in Q3 2023 only. + struct VirtualBlockUpgradeInfo { + /// @notice In order to maintain consistent results for `blockhash` requests, we'll + /// have to remember the number of the batch when the upgrade to the virtual blocks has been done. + /// The hashes for virtual blocks before the upgrade are identical to the hashes of the corresponding batches. + uint128 virtualBlockStartBatch; + /// @notice L2 block when the virtual blocks have caught up with the L2 blocks. Starting from this block, + /// all the information returned to users for block.timestamp/number, etc should be the information about the L2 blocks and + /// not virtual blocks. + uint128 virtualBlockFinishL2Block; + } + + function chainId() external view returns (uint256); + + function origin() external view returns (address); + + function gasPrice() external view returns (uint256); + + function blockGasLimit() external view returns (uint256); + + function coinbase() external view returns (address); + + function difficulty() external view returns (uint256); + + function baseFee() external view returns (uint256); + + function txNumberInBlock() external view returns (uint16); + + function getBlockHashEVM(uint256 _block) external view returns (bytes32); + + function getBatchHash(uint256 _batchNumber) external view returns (bytes32 hash); + + function getBlockNumber() external view returns (uint128); + + function getBlockTimestamp() external view returns (uint128); + + function getBatchNumberAndTimestamp() external view returns (uint128 blockNumber, uint128 blockTimestamp); + + function getL2BlockNumberAndTimestamp() external view returns (uint128 blockNumber, uint128 blockTimestamp); + + function gasPerPubdataByte() external view returns (uint256 gasPerPubdataByte); + + function getCurrentPubdataSpent() external view returns (uint256 currentPubdataSpent); +} diff --git a/gas-bound-caller/contracts/test-contracts/GasBoundCallerTester.sol b/gas-bound-caller/contracts/test-contracts/GasBoundCallerTester.sol new file mode 100644 index 000000000..8c1b790d6 --- /dev/null +++ b/gas-bound-caller/contracts/test-contracts/GasBoundCallerTester.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {GasBoundCaller} from "../GasBoundCaller.sol"; +import {SystemContractHelper} from "./SystemContractHelper.sol"; + +/** + * @author Matter Labs + * @custom:security-contact security@matterlabs.dev + * @notice The contract that allows to limit the final gas expenditure of the call. + */ +contract GasBoundCallerTester is GasBoundCaller { + uint256 public lastRecordedGasLeft; + + function testEntryOverheadInner(uint256 _expectedGas) external payable { + // `2/3` to ensure that the constant is good with sufficient overhead + require(gasleft() + (2 * CALL_ENTRY_OVERHEAD) / 3 >= _expectedGas, "Entry overhead is incorrect"); + + lastRecordedGasLeft = gasleft(); + } + + function testEntryOverhead(uint256 _expectedGas) external payable { + this.testEntryOverheadInner{gas: _expectedGas}(_expectedGas); + } + + function testReturndataOverheadInner(bool _shouldReturn, uint256 _len) external { + if (_shouldReturn) { + assembly { + return(0, _len) + } + } else { + (bool success, bytes memory returnData) = address(this).call( + abi.encodeWithSignature("testReturndataOverheadInner(bool,uint256)", true, _len) + ); + require(success, "Call failed"); + + // It is not needed to query the exact value for the test. + uint256 pubdataGas = 100; + + // `2/3` to ensure that the constant is good with sufficient overhead + SystemContractHelper.burnGas(uint32(gasleft() - (2 * CALL_RETURN_OVERHEAD) / 3), 0); + assembly { + // This place does interfere with the memory layout, however, it is done right before + // the `return` statement, so it is safe to do. + // We need to transform `bytes memory returnData` into (bytes memory returndata, gasSpentOnPubdata) + // `bytes memory returnData` is encoded as `length` + `data`. + // We need to prepend it with 0x40 and `pubdataGas`. + // + // It is assumed that the position of returndata is >= 0x40, since 0x40 is the free memory pointer location. + mstore(sub(returnData, 0x40), 0x40) + mstore(sub(returnData, 0x20), pubdataGas) + let returndataLen := add(mload(returnData), 0x60) + + return(sub(returnData, 0x40), returndataLen) + } + } + } + + function testReturndataOverhead(uint256 len) external { + uint256 gasbefore = gasleft(); + this.testReturndataOverheadInner(false, len); + lastRecordedGasLeft = gasbefore - gasleft(); + } + + function spender(uint32 _ergsToBurn, uint32 _pubdataToUse, bytes memory expectedReturndata) external { + SystemContractHelper.burnGas(_ergsToBurn, _pubdataToUse); + + assembly { + // Return the expected returndata + return(add(expectedReturndata, 0x20), mload(expectedReturndata)) + } + } + + function gasBoundCallRelayer( + uint256 _gasToPass, + address _to, + uint256 _maxTotalGas, + bytes calldata _data, + bytes memory expectedReturndata, + uint256 expectedPubdataGas + ) external payable { + (bool success, bytes memory returnData) = address(this).call{gas: _gasToPass}( + abi.encodeWithSelector(GasBoundCaller.gasBoundCall.selector, _to, _maxTotalGas, _data) + ); + + require(success); + + (bytes memory realReturnData, uint256 pubdataGas) = abi.decode(returnData, (bytes, uint256)); + + require(keccak256(expectedReturndata) == keccak256(realReturnData), "Return data is incorrect"); + require(pubdataGas == expectedPubdataGas, "Pubdata gas is incorrect"); + } +} diff --git a/gas-bound-caller/contracts/test-contracts/SystemContractHelper.sol b/gas-bound-caller/contracts/test-contracts/SystemContractHelper.sol new file mode 100644 index 000000000..47f267005 --- /dev/null +++ b/gas-bound-caller/contracts/test-contracts/SystemContractHelper.sol @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {MAX_SYSTEM_CONTRACT_ADDRESS} from "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; + +import {CALLFLAGS_CALL_ADDRESS, CODE_ADDRESS_CALL_ADDRESS, EVENT_WRITE_ADDRESS, EVENT_INITIALIZE_ADDRESS, GET_EXTRA_ABI_DATA_ADDRESS, LOAD_CALLDATA_INTO_ACTIVE_PTR_CALL_ADDRESS, META_CODE_SHARD_ID_OFFSET, META_CALLER_SHARD_ID_OFFSET, META_SHARD_ID_OFFSET, META_AUX_HEAP_SIZE_OFFSET, META_HEAP_SIZE_OFFSET, META_PUBDATA_PUBLISHED_OFFSET, META_CALL_ADDRESS, PTR_CALLDATA_CALL_ADDRESS, PTR_ADD_INTO_ACTIVE_CALL_ADDRESS, PTR_SHRINK_INTO_ACTIVE_CALL_ADDRESS, PTR_PACK_INTO_ACTIVE_CALL_ADDRESS, PRECOMPILE_CALL_ADDRESS, SET_CONTEXT_VALUE_CALL_ADDRESS, TO_L1_CALL_ADDRESS} from "./SystemContractsCaller.sol"; + +uint256 constant UINT32_MASK = type(uint32).max; +uint256 constant UINT64_MASK = type(uint64).max; +uint256 constant UINT128_MASK = type(uint128).max; +uint256 constant ADDRESS_MASK = type(uint160).max; + +/// @notice NOTE: The `getZkSyncMeta` that is used to obtain this struct will experience a breaking change in 2024. +struct ZkSyncMeta { + uint32 pubdataPublished; + uint32 heapSize; + uint32 auxHeapSize; + uint8 shardId; + uint8 callerShardId; + uint8 codeShardId; +} + +enum Global { + CalldataPtr, + CallFlags, + ExtraABIData1, + ExtraABIData2, + ReturndataPtr +} + +/** + * @author Matter Labs + * @custom:security-contact security@matterlabs.dev + * @notice Library used for accessing zkEVM-specific opcodes, needed for the development + * of system contracts. + * @dev While this library will be eventually available to public, some of the provided + * methods won't work for non-system contracts and also breaking changes at short notice are possible. + * We do not recommend this library for external use. + */ +library SystemContractHelper { + /// @notice Send an L2Log to L1. + /// @param _isService The `isService` flag. + /// @param _key The `key` part of the L2Log. + /// @param _value The `value` part of the L2Log. + /// @dev The meaning of all these parameters is context-dependent, but they + /// have no intrinsic meaning per se. + function toL1(bool _isService, bytes32 _key, bytes32 _value) internal { + address callAddr = TO_L1_CALL_ADDRESS; + assembly { + // Ensuring that the type is bool + _isService := and(_isService, 1) + // This `success` is always 0, but the method always succeeds + // (except for the cases when there is not enough gas) + // solhint-disable-next-line no-unused-vars + let success := call(_isService, callAddr, _key, _value, 0xFFFF, 0, 0) + } + } + + /// @notice Get address of the currently executed code. + /// @dev This allows differentiating between `call` and `delegatecall`. + /// During the former `this` and `codeAddress` are the same, while + /// during the latter they are not. + function getCodeAddress() internal view returns (address addr) { + address callAddr = CODE_ADDRESS_CALL_ADDRESS; + assembly { + addr := staticcall(0, callAddr, 0, 0xFFFF, 0, 0) + } + } + + /// @notice Provide a compiler hint, by placing calldata fat pointer into virtual `ACTIVE_PTR`, + /// that can be manipulated by `ptr.add`/`ptr.sub`/`ptr.pack`/`ptr.shrink` later. + /// @dev This allows making a call by forwarding calldata pointer to the child call. + /// It is a much more efficient way to forward calldata, than standard EVM bytes copying. + function loadCalldataIntoActivePtr() internal view { + address callAddr = LOAD_CALLDATA_INTO_ACTIVE_PTR_CALL_ADDRESS; + assembly { + pop(staticcall(0, callAddr, 0, 0xFFFF, 0, 0)) + } + } + + /// @notice Compiler simulation of the `ptr.pack` opcode for the virtual `ACTIVE_PTR` pointer. + /// @dev Do the concatenation between lowest part of `ACTIVE_PTR` and highest part of `_farCallAbi` + /// forming packed fat pointer for a far call or ret ABI when necessary. + /// Note: Panics if the lowest 128 bits of `_farCallAbi` are not zeroes. + function ptrPackIntoActivePtr(uint256 _farCallAbi) internal view { + address callAddr = PTR_PACK_INTO_ACTIVE_CALL_ADDRESS; + assembly { + pop(staticcall(_farCallAbi, callAddr, 0, 0xFFFF, 0, 0)) + } + } + + /// @notice Compiler simulation of the `ptr.add` opcode for the virtual `ACTIVE_PTR` pointer. + /// @dev Transforms `ACTIVE_PTR.offset` into `ACTIVE_PTR.offset + u32(_value)`. If overflow happens then it panics. + function ptrAddIntoActive(uint32 _value) internal view { + address callAddr = PTR_ADD_INTO_ACTIVE_CALL_ADDRESS; + uint256 cleanupMask = UINT32_MASK; + assembly { + // Clearing input params as they are not cleaned by Solidity by default + _value := and(_value, cleanupMask) + pop(staticcall(_value, callAddr, 0, 0xFFFF, 0, 0)) + } + } + + /// @notice Compiler simulation of the `ptr.shrink` opcode for the virtual `ACTIVE_PTR` pointer. + /// @dev Transforms `ACTIVE_PTR.length` into `ACTIVE_PTR.length - u32(_shrink)`. If underflow happens then it panics. + function ptrShrinkIntoActive(uint32 _shrink) internal view { + address callAddr = PTR_SHRINK_INTO_ACTIVE_CALL_ADDRESS; + uint256 cleanupMask = UINT32_MASK; + assembly { + // Clearing input params as they are not cleaned by Solidity by default + _shrink := and(_shrink, cleanupMask) + pop(staticcall(_shrink, callAddr, 0, 0xFFFF, 0, 0)) + } + } + + /// @notice packs precompile parameters into one word + /// @param _inputMemoryOffset The memory offset in 32-byte words for the input data for calling the precompile. + /// @param _inputMemoryLength The length of the input data in words. + /// @param _outputMemoryOffset The memory offset in 32-byte words for the output data. + /// @param _outputMemoryLength The length of the output data in words. + /// @param _perPrecompileInterpreted The constant, the meaning of which is defined separately for + /// each precompile. For information, please read the documentation of the precompilecall log in + /// the VM. + function packPrecompileParams( + uint32 _inputMemoryOffset, + uint32 _inputMemoryLength, + uint32 _outputMemoryOffset, + uint32 _outputMemoryLength, + uint64 _perPrecompileInterpreted + ) internal pure returns (uint256 rawParams) { + rawParams = _inputMemoryOffset; + rawParams |= uint256(_inputMemoryLength) << 32; + rawParams |= uint256(_outputMemoryOffset) << 64; + rawParams |= uint256(_outputMemoryLength) << 96; + rawParams |= uint256(_perPrecompileInterpreted) << 192; + } + + /// @notice Call precompile with given parameters. + /// @param _rawParams The packed precompile params. They can be retrieved by + /// the `packPrecompileParams` method. + /// @param _gasToBurn The number of gas to burn during this call. + /// @param _pubdataToSpend The number of pubdata bytes to burn during the call. + /// @return success Whether the call was successful. + /// @dev The list of currently available precompiles sha256, keccak256, ecrecover. + /// NOTE: The precompile type depends on `this` which calls precompile, which means that only + /// system contracts corresponding to the list of precompiles above can do `precompileCall`. + /// @dev If used not in the `sha256`, `keccak256` or `ecrecover` contracts, it will just burn the gas provided. + /// @dev This method is `unsafe` because it does not check whether there is enough gas to burn. + function unsafePrecompileCall( + uint256 _rawParams, + uint32 _gasToBurn, + uint32 _pubdataToSpend + ) internal view returns (bool success) { + address callAddr = PRECOMPILE_CALL_ADDRESS; + + uint256 params = uint256(_gasToBurn) + (uint256(_pubdataToSpend) << 32); + + uint256 cleanupMask = UINT64_MASK; + assembly { + // Clearing input params as they are not cleaned by Solidity by default + params := and(params, cleanupMask) + success := staticcall(_rawParams, callAddr, params, 0xFFFF, 0, 0) + } + } + + /// @notice Set `msg.value` to next far call. + /// @param _value The msg.value that will be used for the *next* call. + /// @dev If called not in kernel mode, it will result in a revert (enforced by the VM) + function setValueForNextFarCall(uint128 _value) internal returns (bool success) { + uint256 cleanupMask = UINT128_MASK; + address callAddr = SET_CONTEXT_VALUE_CALL_ADDRESS; + assembly { + // Clearing input params as they are not cleaned by Solidity by default + _value := and(_value, cleanupMask) + success := call(0, callAddr, _value, 0, 0xFFFF, 0, 0) + } + } + + /// @notice Initialize a new event. + /// @param initializer The event initializing value. + /// @param value1 The first topic or data chunk. + function eventInitialize(uint256 initializer, uint256 value1) internal { + address callAddr = EVENT_INITIALIZE_ADDRESS; + assembly { + pop(call(initializer, callAddr, value1, 0, 0xFFFF, 0, 0)) + } + } + + /// @notice Continue writing the previously initialized event. + /// @param value1 The first topic or data chunk. + /// @param value2 The second topic or data chunk. + function eventWrite(uint256 value1, uint256 value2) internal { + address callAddr = EVENT_WRITE_ADDRESS; + assembly { + pop(call(value1, callAddr, value2, 0, 0xFFFF, 0, 0)) + } + } + + /// @notice Get the packed representation of the `ZkSyncMeta` from the current context. + /// @notice NOTE: The behavior of this function will experience a breaking change in 2024. + /// @return meta The packed representation of the ZkSyncMeta. + /// @dev The fields in ZkSyncMeta are NOT tightly packed, i.e. there is a special rule on how + /// they are packed. For more information, please read the documentation on ZkSyncMeta. + function getZkSyncMetaBytes() internal view returns (uint256 meta) { + address callAddr = META_CALL_ADDRESS; + assembly { + meta := staticcall(0, callAddr, 0, 0xFFFF, 0, 0) + } + } + + /// @notice Returns the bits [offset..offset+size-1] of the meta. + /// @param meta Packed representation of the ZkSyncMeta. + /// @param offset The offset of the bits. + /// @param size The size of the extracted number in bits. + /// @return result The extracted number. + function extractNumberFromMeta(uint256 meta, uint256 offset, uint256 size) internal pure returns (uint256 result) { + // Firstly, we delete all the bits after the field + uint256 shifted = (meta << (256 - size - offset)); + // Then we shift everything back + result = (shifted >> (256 - size)); + } + + /// @notice Given the packed representation of `ZkSyncMeta`, retrieves the number of pubdata + /// bytes published in the batch so far. + /// @notice NOTE: The behavior of this function will experience a breaking change in 2024. + /// @param meta Packed representation of the ZkSyncMeta. + /// @return pubdataPublished The amount of pubdata published in the system so far. + function getPubdataPublishedFromMeta(uint256 meta) internal pure returns (uint32 pubdataPublished) { + pubdataPublished = uint32(extractNumberFromMeta(meta, META_PUBDATA_PUBLISHED_OFFSET, 32)); + } + + /// @notice Given the packed representation of `ZkSyncMeta`, retrieves the number of the current size + /// of the heap in bytes. + /// @param meta Packed representation of the ZkSyncMeta. + /// @return heapSize The size of the memory in bytes byte. + /// @dev The following expression: getHeapSizeFromMeta(getZkSyncMetaBytes()) is + /// equivalent to the MSIZE in Solidity. + function getHeapSizeFromMeta(uint256 meta) internal pure returns (uint32 heapSize) { + heapSize = uint32(extractNumberFromMeta(meta, META_HEAP_SIZE_OFFSET, 32)); + } + + /// @notice Given the packed representation of `ZkSyncMeta`, retrieves the number of the current size + /// of the auxiliary heap in bytes. + /// @param meta Packed representation of the ZkSyncMeta. + /// @return auxHeapSize The size of the auxiliary memory in bytes byte. + /// @dev You can read more on auxiliary memory in the VM1.2 documentation. + function getAuxHeapSizeFromMeta(uint256 meta) internal pure returns (uint32 auxHeapSize) { + auxHeapSize = uint32(extractNumberFromMeta(meta, META_AUX_HEAP_SIZE_OFFSET, 32)); + } + + /// @notice Given the packed representation of `ZkSyncMeta`, retrieves the shardId of `this`. + /// @param meta Packed representation of the ZkSyncMeta. + /// @return shardId The shardId of `this`. + /// @dev Currently only shard 0 (zkRollup) is supported. + function getShardIdFromMeta(uint256 meta) internal pure returns (uint8 shardId) { + shardId = uint8(extractNumberFromMeta(meta, META_SHARD_ID_OFFSET, 8)); + } + + /// @notice Given the packed representation of `ZkSyncMeta`, retrieves the shardId of + /// the msg.sender. + /// @param meta Packed representation of the ZkSyncMeta. + /// @return callerShardId The shardId of the msg.sender. + /// @dev Currently only shard 0 (zkRollup) is supported. + function getCallerShardIdFromMeta(uint256 meta) internal pure returns (uint8 callerShardId) { + callerShardId = uint8(extractNumberFromMeta(meta, META_CALLER_SHARD_ID_OFFSET, 8)); + } + + /// @notice Given the packed representation of `ZkSyncMeta`, retrieves the shardId of + /// the currently executed code. + /// @param meta Packed representation of the ZkSyncMeta. + /// @return codeShardId The shardId of the currently executed code. + /// @dev Currently only shard 0 (zkRollup) is supported. + function getCodeShardIdFromMeta(uint256 meta) internal pure returns (uint8 codeShardId) { + codeShardId = uint8(extractNumberFromMeta(meta, META_CODE_SHARD_ID_OFFSET, 8)); + } + + /// @notice Retrieves the ZkSyncMeta structure. + /// @notice NOTE: The behavior of this function will experience a breaking change in 2024. + /// @return meta The ZkSyncMeta execution context parameters. + function getZkSyncMeta() internal view returns (ZkSyncMeta memory meta) { + uint256 metaPacked = getZkSyncMetaBytes(); + meta.pubdataPublished = getPubdataPublishedFromMeta(metaPacked); + meta.heapSize = getHeapSizeFromMeta(metaPacked); + meta.auxHeapSize = getAuxHeapSizeFromMeta(metaPacked); + meta.shardId = getShardIdFromMeta(metaPacked); + meta.callerShardId = getCallerShardIdFromMeta(metaPacked); + meta.codeShardId = getCodeShardIdFromMeta(metaPacked); + } + + /// @notice Returns the call flags for the current call. + /// @return callFlags The bitmask of the callflags. + /// @dev Call flags is the value of the first register + /// at the start of the call. + /// @dev The zero bit of the callFlags indicates whether the call is + /// a constructor call. The first bit of the callFlags indicates whether + /// the call is a system one. + function getCallFlags() internal view returns (uint256 callFlags) { + address callAddr = CALLFLAGS_CALL_ADDRESS; + assembly { + callFlags := staticcall(0, callAddr, 0, 0xFFFF, 0, 0) + } + } + + /// @notice Returns the current calldata pointer. + /// @return ptr The current calldata pointer. + /// @dev NOTE: This file is just an integer and it cannot be used + /// to forward the calldata to the next calls in any way. + function getCalldataPtr() internal view returns (uint256 ptr) { + address callAddr = PTR_CALLDATA_CALL_ADDRESS; + assembly { + ptr := staticcall(0, callAddr, 0, 0xFFFF, 0, 0) + } + } + + /// @notice Returns the N-th extraAbiParam for the current call. + /// @return extraAbiData The value of the N-th extraAbiParam for this call. + /// @dev It is equal to the value of the (N+2)-th register + /// at the start of the call. + function getExtraAbiData(uint256 index) internal view returns (uint256 extraAbiData) { + require(index < 10, "There are only 10 accessible registers"); + + address callAddr = GET_EXTRA_ABI_DATA_ADDRESS; + assembly { + extraAbiData := staticcall(index, callAddr, 0, 0xFFFF, 0, 0) + } + } + + /// @notice Returns whether the current call is a system call. + /// @return `true` or `false` based on whether the current call is a system call. + function isSystemCall() internal view returns (bool) { + uint256 callFlags = getCallFlags(); + // When the system call is passed, the 2-bit is set to 1 + return (callFlags & 2) != 0; + } + + /// @notice Returns whether the address is a system contract. + /// @param _address The address to test + /// @return `true` or `false` based on whether the `_address` is a system contract. + function isSystemContract(address _address) internal pure returns (bool) { + return uint160(_address) <= uint160(MAX_SYSTEM_CONTRACT_ADDRESS); + } + + /// @notice Method used for burning a certain amount of gas. + /// @param _gasToPay The number of gas to burn. + /// @param _pubdataToSpend The number of pubdata bytes to burn during the call. + function burnGas(uint32 _gasToPay, uint32 _pubdataToSpend) internal view { + bool precompileCallSuccess = unsafePrecompileCall( + 0, // The precompile parameters are formal ones. We only need the precompile call to burn gas. + _gasToPay, + _pubdataToSpend + ); + require(precompileCallSuccess, "Failed to charge gas"); + } +} diff --git a/gas-bound-caller/contracts/test-contracts/SystemContractsCaller.sol b/gas-bound-caller/contracts/test-contracts/SystemContractsCaller.sol new file mode 100644 index 000000000..ca7c870c7 --- /dev/null +++ b/gas-bound-caller/contracts/test-contracts/SystemContractsCaller.sol @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {MSG_VALUE_SYSTEM_CONTRACT, MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT} from "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; +import {Utils} from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/Utils.sol"; + +// Addresses used for the compiler to be replaced with the +// zkSync-specific opcodes during the compilation. +// IMPORTANT: these are just compile-time constants and are used +// only if used in-place by Yul optimizer. +address constant TO_L1_CALL_ADDRESS = address((1 << 16) - 1); +address constant CODE_ADDRESS_CALL_ADDRESS = address((1 << 16) - 2); +address constant PRECOMPILE_CALL_ADDRESS = address((1 << 16) - 3); +address constant META_CALL_ADDRESS = address((1 << 16) - 4); +address constant MIMIC_CALL_CALL_ADDRESS = address((1 << 16) - 5); +address constant SYSTEM_MIMIC_CALL_CALL_ADDRESS = address((1 << 16) - 6); +address constant MIMIC_CALL_BY_REF_CALL_ADDRESS = address((1 << 16) - 7); +address constant SYSTEM_MIMIC_CALL_BY_REF_CALL_ADDRESS = address((1 << 16) - 8); +address constant RAW_FAR_CALL_CALL_ADDRESS = address((1 << 16) - 9); +address constant RAW_FAR_CALL_BY_REF_CALL_ADDRESS = address((1 << 16) - 10); +address constant SYSTEM_CALL_CALL_ADDRESS = address((1 << 16) - 11); +address constant SYSTEM_CALL_BY_REF_CALL_ADDRESS = address((1 << 16) - 12); +address constant SET_CONTEXT_VALUE_CALL_ADDRESS = address((1 << 16) - 13); +address constant SET_PUBDATA_PRICE_CALL_ADDRESS = address((1 << 16) - 14); +address constant INCREMENT_TX_COUNTER_CALL_ADDRESS = address((1 << 16) - 15); +address constant PTR_CALLDATA_CALL_ADDRESS = address((1 << 16) - 16); +address constant CALLFLAGS_CALL_ADDRESS = address((1 << 16) - 17); +address constant PTR_RETURNDATA_CALL_ADDRESS = address((1 << 16) - 18); +address constant EVENT_INITIALIZE_ADDRESS = address((1 << 16) - 19); +address constant EVENT_WRITE_ADDRESS = address((1 << 16) - 20); +address constant LOAD_CALLDATA_INTO_ACTIVE_PTR_CALL_ADDRESS = address((1 << 16) - 21); +address constant LOAD_LATEST_RETURNDATA_INTO_ACTIVE_PTR_CALL_ADDRESS = address((1 << 16) - 22); +address constant PTR_ADD_INTO_ACTIVE_CALL_ADDRESS = address((1 << 16) - 23); +address constant PTR_SHRINK_INTO_ACTIVE_CALL_ADDRESS = address((1 << 16) - 24); +address constant PTR_PACK_INTO_ACTIVE_CALL_ADDRESS = address((1 << 16) - 25); +address constant MULTIPLICATION_HIGH_ADDRESS = address((1 << 16) - 26); +address constant GET_EXTRA_ABI_DATA_ADDRESS = address((1 << 16) - 27); + +// All the offsets are in bits +uint256 constant META_PUBDATA_PUBLISHED_OFFSET = 0 * 8; +uint256 constant META_HEAP_SIZE_OFFSET = 8 * 8; +uint256 constant META_AUX_HEAP_SIZE_OFFSET = 12 * 8; +uint256 constant META_SHARD_ID_OFFSET = 28 * 8; +uint256 constant META_CALLER_SHARD_ID_OFFSET = 29 * 8; +uint256 constant META_CODE_SHARD_ID_OFFSET = 30 * 8; + +/// @notice The way to forward the calldata: +/// - Use the current heap (i.e. the same as on EVM). +/// - Use the auxiliary heap. +/// - Forward via a pointer +/// @dev Note, that currently, users do not have access to the auxiliary +/// heap and so the only type of forwarding that will be used by the users +/// are UseHeap and ForwardFatPointer for forwarding a slice of the current calldata +/// to the next call. +enum CalldataForwardingMode { + UseHeap, + ForwardFatPointer, + UseAuxHeap +} + +/** + * @author Matter Labs + * @custom:security-contact security@matterlabs.dev + * @notice A library that allows calling contracts with the `isSystem` flag. + * @dev It is needed to call ContractDeployer and NonceHolder. + */ +library SystemContractsCaller { + /// @notice Makes a call with the `isSystem` flag. + /// @param gasLimit The gas limit for the call. + /// @param to The address to call. + /// @param value The value to pass with the transaction. + /// @param data The calldata. + /// @return success Whether the transaction has been successful. + /// @dev Note, that the `isSystem` flag can only be set when calling system contracts. + function systemCall(uint32 gasLimit, address to, uint256 value, bytes memory data) internal returns (bool success) { + address callAddr = SYSTEM_CALL_CALL_ADDRESS; + + uint32 dataStart; + assembly { + dataStart := add(data, 0x20) + } + uint32 dataLength = uint32(Utils.safeCastToU32(data.length)); + + uint256 farCallAbi = SystemContractsCaller.getFarCallABI({ + dataOffset: 0, + memoryPage: 0, + dataStart: dataStart, + dataLength: dataLength, + gasPassed: gasLimit, + // Only rollup is supported for now + shardId: 0, + forwardingMode: CalldataForwardingMode.UseHeap, + isConstructorCall: false, + isSystemCall: true + }); + + if (value == 0) { + // Doing the system call directly + assembly { + success := call(to, callAddr, 0, 0, farCallAbi, 0, 0) + } + } else { + address msgValueSimulator = MSG_VALUE_SYSTEM_CONTRACT; + // We need to supply the mask to the MsgValueSimulator to denote + // that the call should be a system one. + uint256 forwardMask = MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT; + + assembly { + success := call(msgValueSimulator, callAddr, value, to, farCallAbi, forwardMask, 0) + } + } + } + + /// @notice Makes a call with the `isSystem` flag. + /// @param gasLimit The gas limit for the call. + /// @param to The address to call. + /// @param value The value to pass with the transaction. + /// @param data The calldata. + /// @return success Whether the transaction has been successful. + /// @return returnData The returndata of the transaction (revert reason in case the transaction has failed). + /// @dev Note, that the `isSystem` flag can only be set when calling system contracts. + function systemCallWithReturndata( + uint32 gasLimit, + address to, + uint128 value, + bytes memory data + ) internal returns (bool success, bytes memory returnData) { + success = systemCall(gasLimit, to, value, data); + + uint256 size; + assembly { + size := returndatasize() + } + + returnData = new bytes(size); + assembly { + returndatacopy(add(returnData, 0x20), 0, size) + } + } + + /// @notice Makes a call with the `isSystem` flag. + /// @param gasLimit The gas limit for the call. + /// @param to The address to call. + /// @param value The value to pass with the transaction. + /// @param data The calldata. + /// @return returnData The returndata of the transaction. In case the transaction reverts, the error + /// bubbles up to the parent frame. + /// @dev Note, that the `isSystem` flag can only be set when calling system contracts. + function systemCallWithPropagatedRevert( + uint32 gasLimit, + address to, + uint128 value, + bytes memory data + ) internal returns (bytes memory returnData) { + bool success; + (success, returnData) = systemCallWithReturndata(gasLimit, to, value, data); + + if (!success) { + assembly { + let size := mload(returnData) + revert(add(returnData, 0x20), size) + } + } + } + + /// @notice Calculates the packed representation of the FarCallABI. + /// @param dataOffset Calldata offset in memory. Provide 0 unless using custom pointer. + /// @param memoryPage Memory page to use. Provide 0 unless using custom pointer. + /// @param dataStart The start of the calldata slice. Provide the offset in memory + /// if not using custom pointer. + /// @param dataLength The calldata length. Provide the length of the calldata in bytes + /// unless using custom pointer. + /// @param gasPassed The gas to pass with the call. + /// @param shardId Of the account to call. Currently only 0 is supported. + /// @param forwardingMode The forwarding mode to use: + /// - provide CalldataForwardingMode.UseHeap when using your current memory + /// - provide CalldataForwardingMode.ForwardFatPointer when using custom pointer. + /// @param isConstructorCall Whether the call will be a call to the constructor + /// (ignored when the caller is not a system contract). + /// @param isSystemCall Whether the call will have the `isSystem` flag. + /// @return farCallAbi The far call ABI. + /// @dev The `FarCallABI` has the following structure: + /// pub struct FarCallABI { + /// pub memory_quasi_fat_pointer: FatPointer, + /// pub gas_passed: u32, + /// pub shard_id: u8, + /// pub forwarding_mode: FarCallForwardPageType, + /// pub constructor_call: bool, + /// pub to_system: bool, + /// } + /// + /// The FatPointer struct: + /// + /// pub struct FatPointer { + /// pub offset: u32, // offset relative to `start` + /// pub memory_page: u32, // memory page where slice is located + /// pub start: u32, // absolute start of the slice + /// pub length: u32, // length of the slice + /// } + /// + /// @dev Note, that the actual layout is the following: + /// + /// [0..32) bits -- the calldata offset + /// [32..64) bits -- the memory page to use. Can be left blank in most of the cases. + /// [64..96) bits -- the absolute start of the slice + /// [96..128) bits -- the length of the slice. + /// [128..192) bits -- empty bits. + /// [192..224) bits -- gasPassed. + /// [224..232) bits -- forwarding_mode + /// [232..240) bits -- shard id. + /// [240..248) bits -- constructor call flag + /// [248..256] bits -- system call flag + function getFarCallABI( + uint32 dataOffset, + uint32 memoryPage, + uint32 dataStart, + uint32 dataLength, + uint32 gasPassed, + uint8 shardId, + CalldataForwardingMode forwardingMode, + bool isConstructorCall, + bool isSystemCall + ) internal pure returns (uint256 farCallAbi) { + // Fill in the call parameter fields + farCallAbi = getFarCallABIWithEmptyFatPointer({ + gasPassed: gasPassed, + shardId: shardId, + forwardingMode: forwardingMode, + isConstructorCall: isConstructorCall, + isSystemCall: isSystemCall + }); + + // Fill in the fat pointer fields + farCallAbi |= dataOffset; + farCallAbi |= (uint256(memoryPage) << 32); + farCallAbi |= (uint256(dataStart) << 64); + farCallAbi |= (uint256(dataLength) << 96); + } + + /// @notice Calculates the packed representation of the FarCallABI with zero fat pointer fields. + /// @param gasPassed The gas to pass with the call. + /// @param shardId Of the account to call. Currently only 0 is supported. + /// @param forwardingMode The forwarding mode to use: + /// - provide CalldataForwardingMode.UseHeap when using your current memory + /// - provide CalldataForwardingMode.ForwardFatPointer when using custom pointer. + /// @param isConstructorCall Whether the call will be a call to the constructor + /// (ignored when the caller is not a system contract). + /// @param isSystemCall Whether the call will have the `isSystem` flag. + /// @return farCallAbiWithEmptyFatPtr The far call ABI with zero fat pointer fields. + function getFarCallABIWithEmptyFatPointer( + uint32 gasPassed, + uint8 shardId, + CalldataForwardingMode forwardingMode, + bool isConstructorCall, + bool isSystemCall + ) internal pure returns (uint256 farCallAbiWithEmptyFatPtr) { + farCallAbiWithEmptyFatPtr |= (uint256(gasPassed) << 192); + farCallAbiWithEmptyFatPtr |= (uint256(forwardingMode) << 224); + farCallAbiWithEmptyFatPtr |= (uint256(shardId) << 232); + if (isConstructorCall) { + farCallAbiWithEmptyFatPtr |= (1 << 240); + } + if (isSystemCall) { + farCallAbiWithEmptyFatPtr |= (1 << 248); + } + } +} diff --git a/gas-bound-caller/hardhat.config.ts b/gas-bound-caller/hardhat.config.ts new file mode 100644 index 000000000..2c50fa5fc --- /dev/null +++ b/gas-bound-caller/hardhat.config.ts @@ -0,0 +1,62 @@ +import "@matterlabs/hardhat-zksync-chai-matchers"; +import "@matterlabs/hardhat-zksync-node"; +import "@matterlabs/hardhat-zksync-solc"; +import "@nomiclabs/hardhat-ethers"; +import "hardhat-typechain"; + +// This version of system contracts requires a pre release of the compiler +const COMPILER_VERSION = "1.5.0"; +const PRE_RELEASE_VERSION = "prerelease-a167aa3-code4rena"; +function getZksolcUrl(): string { + // @ts-ignore + const platform = { darwin: "macosx", linux: "linux", win32: "windows" }[process.platform]; + // @ts-ignore + const toolchain = { linux: "-musl", win32: "-gnu", darwin: "" }[process.platform]; + const arch = process.arch === "x64" ? "amd64" : process.arch; + const ext = process.platform === "win32" ? ".exe" : ""; + + return `https://github.com/matter-labs/era-compiler-solidity/releases/download/${PRE_RELEASE_VERSION}/zksolc-${platform}-${arch}${toolchain}-v${COMPILER_VERSION}${ext}`; +} + +console.log(`Using zksolc from ${getZksolcUrl()}`); + +export default { + zksolc: { + compilerSource: "binary", + settings: { + compilerPath: getZksolcUrl(), + isSystem: true, + }, + }, + zkSyncDeploy: { + zkSyncNetwork: "http://localhost:3050", + ethNetwork: "http://localhost:8545", + }, + solidity: { + version: "0.8.20", + settings: { + optimizer: { + enabled: true, + runs: 9999999, + }, + outputSelection: { + "*": { + "*": ["storageLayout"], + }, + }, + }, + }, + networks: { + hardhat: { + zksync: true, + }, + zkSyncTestNode: { + url: "http://127.0.0.1:8011", + ethNetwork: "localhost", + zksync: true, + }, + }, + paths: { + sources: "./contracts", + }, +}; diff --git a/gas-bound-caller/package.json b/gas-bound-caller/package.json new file mode 100644 index 000000000..de6220930 --- /dev/null +++ b/gas-bound-caller/package.json @@ -0,0 +1,60 @@ +{ + "name": "gas-bound-caller", + "version": "0.1.0", + "repository": "git@github.com:matter-labs/era-contracts.git", + "license": "MIT", + "dependencies": { + "@matterlabs/hardhat-zksync-deploy": "^0.6.5", + "@matterlabs/hardhat-zksync-solc": "^1.1.4", + "@matterlabs/zksync-contracts": "^0.6.1", + "commander": "^9.4.1", + "eslint": "^8.51.0", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-prettier": "^5.0.1", + "ethers": "^5.7.0", + "fast-glob": "^3.3.2", + "hardhat": "^2.18.3", + "preprocess": "^3.2.0", + "zksync-ethers": "https://github.com/zksync-sdk/zksync-ethers#ethers-v5-feat/bridgehub" + }, + "devDependencies": { + "@matterlabs/hardhat-zksync-chai-matchers": "^0.1.4", + "@matterlabs/hardhat-zksync-node": "^0.0.1-beta.7", + "@nomicfoundation/hardhat-chai-matchers": "^1.0.3", + "@nomiclabs/hardhat-ethers": "^2.0.0", + "@typechain/ethers-v5": "^2.0.0", + "@types/chai": "^4.2.21", + "@types/elliptic": "^6.4.18", + "@types/lodash": "^4.14.199", + "@types/mocha": "^8.2.3", + "@types/node": "^17.0.34", + "chai": "^4.3.10", + "elliptic": "^6.5.4", + "hardhat-typechain": "^0.3.3", + "lodash": "^4.17.21", + "mocha": "^9.0.2", + "template-file": "^6.0.1", + "ts-generator": "^0.1.1", + "ts-node": "^10.1.0", + "typechain": "^4.0.0", + "typescript": "^4.6.4", + "zksync-ethers": "https://github.com/zksync-sdk/zksync-ethers#ethers-v5-feat/bridgehub" + }, + "mocha": { + "timeout": 240000, + "exit": true, + "color": false, + "slow": 0, + "require": [ + "ts-node/register" + ] + }, + "scripts": { + "build": "hardhat compile", + "clean": "yarn clean:bootloader && yarn clean:system-contracts", + "test": "yarn build && hardhat test --network zkSyncTestNode", + "test-node": "hardhat node-zksync --tag v0.0.1-vm1.5.0", + "deploy-on-hyperchain": "ts-node ./scripts/deploy-on-hyperchain.ts", + "deploy-on-localhost": "hardhat deploy --network localhost" + } +} diff --git a/gas-bound-caller/scripts/check-canonical-bytecode.ts b/gas-bound-caller/scripts/check-canonical-bytecode.ts new file mode 100644 index 000000000..041e8e9d1 --- /dev/null +++ b/gas-bound-caller/scripts/check-canonical-bytecode.ts @@ -0,0 +1,42 @@ +// hardhat import should be the first import in the file +import * as hre from "hardhat"; +import { Command } from "commander"; +import { readCanonicalArtifact, writeCanonicalArtifact } from "./utils"; + +async function main() { + const program = new Command(); + + program + .version("0.1.0") + .name("check canonical bytecode") + .description("Checks that the locally built artifacts match the canonical bytecode"); + + program.command("check").action(async () => { + const compiledBytecode = (await hre.artifacts.readArtifact("GasBoundCaller")).bytecode; + const canonicalBytecode = readCanonicalArtifact(); + + if (compiledBytecode.toLocaleLowerCase() != canonicalBytecode.toLocaleLowerCase()) { + throw new Error("Compiled bytecode is not correct"); + } + }); + + program.command("fix").action(async () => { + const compiledBytecode = (await hre.artifacts.readArtifact("GasBoundCaller")).bytecode; + const canonicalBytecode = readCanonicalArtifact(); + + if (compiledBytecode.toLocaleLowerCase() != canonicalBytecode.toLocaleLowerCase()) { + writeCanonicalArtifact(compiledBytecode); + } else { + console.log("There is nothing to fix"); + } + }); + + await program.parseAsync(process.argv); +} + +main() + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err.message || err); + process.exit(1); + }); diff --git a/gas-bound-caller/scripts/deploy-on-hyperchain.ts b/gas-bound-caller/scripts/deploy-on-hyperchain.ts new file mode 100644 index 000000000..35d013fd7 --- /dev/null +++ b/gas-bound-caller/scripts/deploy-on-hyperchain.ts @@ -0,0 +1,104 @@ +// hardhat import should be the first import in the file +import { ethers } from "ethers"; +import { Command } from "commander"; +import { readCanonicalArtifact } from "./utils"; +import { Wallet, Provider, Contract, utils } from "zksync-ethers"; + +// This factory should be predeployed on each post-v24 zksync network +const PREDEPLOYED_CREATE2_ADDRESS = "0x0000000000000000000000000000000000010000"; + +const singletonFactoryAbi = [ + { + inputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + name: "create2", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "payable", + type: "function", + }, +]; + +async function main() { + const program = new Command(); + + program + .version("0.1.0") + .name("Deploy on hyperchain") + .description("Deploys the GasBoundCaller on a predetermined Hyperchain network") + .option("--private-key ") + .option("--l2Rpc ") + .action(async (cmd) => { + console.log("Reading the canonical bytecode of the GasBoundCaller"); + const bytecode = readCanonicalArtifact(); + + const wallet = new Wallet(cmd.privateKey, new Provider(cmd.l2Rpc)); + + const singleTonFactory = new Contract(PREDEPLOYED_CREATE2_ADDRESS, singletonFactoryAbi, wallet); + + const bytecodeHash = ethers.utils.hexlify(utils.hashBytecode(bytecode)); + + const expectedAddress = utils.create2Address( + PREDEPLOYED_CREATE2_ADDRESS, + bytecodeHash, + ethers.constants.HashZero, + "0x" + ); + console.log("Expected address:", expectedAddress); + + const currentCode = await wallet.provider.getCode(expectedAddress); + if (currentCode !== "0x") { + if (currentCode === bytecode) { + console.log("The GasBoundCaller is already deployed on the expected address"); + return; + } + throw new Error("The expected address is already occupied by a contract with different bytecode"); + } + + console.log("Sending transaction to deploy the GasBoundCaller"); + const tx = await singleTonFactory.create2(ethers.constants.HashZero, bytecodeHash, "0x", { + customData: { + factoryDeps: [bytecode], + }, + }); + + console.log("Transaction hash:", tx.hash); + await tx.wait(); + + const codeAfterDeploy = await wallet.provider.getCode(expectedAddress); + if (codeAfterDeploy.toLowerCase() !== bytecode.toLowerCase()) { + throw new Error("Deployment failed. The bytecode on the expected address is not the same as the bytecode."); + } + + console.log("Transaction complete!"); + }); + + await program.parseAsync(process.argv); +} + +main() + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err.message || err); + process.exit(1); + }); diff --git a/gas-bound-caller/scripts/utils.ts b/gas-bound-caller/scripts/utils.ts new file mode 100644 index 000000000..81c84b4a7 --- /dev/null +++ b/gas-bound-caller/scripts/utils.ts @@ -0,0 +1,11 @@ +import * as fs from "fs"; + +const CANONICAL_BYTECODES_PATH = "./canonical-bytecodes/GasBoundCaller"; + +export function writeCanonicalArtifact(newBytecode: string) { + fs.writeFileSync(CANONICAL_BYTECODES_PATH, newBytecode); +} + +export function readCanonicalArtifact() { + return fs.readFileSync(CANONICAL_BYTECODES_PATH).toString(); +} diff --git a/system-contracts/test/GasBoundCaller.spec.ts b/gas-bound-caller/test/GasBoundCaller.spec.ts similarity index 77% rename from system-contracts/test/GasBoundCaller.spec.ts rename to gas-bound-caller/test/GasBoundCaller.spec.ts index ef6a300d6..1a970f0fc 100644 --- a/system-contracts/test/GasBoundCaller.spec.ts +++ b/gas-bound-caller/test/GasBoundCaller.spec.ts @@ -1,23 +1,22 @@ -import type { SystemContext, GasBoundCallerTester } from "../typechain"; -import { GasBoundCallerTesterFactory, SystemContextFactory } from "../typechain"; -import { REAL_SYSTEM_CONTEXT_ADDRESS } from "./shared/constants"; -import { deployContractOnAddress, getWallets } from "./shared/utils"; +import { REAL_SYSTEM_CONTEXT_ADDRESS } from "../../system-contracts/test/shared/constants"; +import { deployContractOnAddress, getWallets } from "../../system-contracts/test/shared/utils"; +import type { GasBoundCallerTester } from "../typechain"; +import { GasBoundCallerTesterFactory } from "../typechain"; +import type { ISystemContext } from "../typechain/ISystemContext"; +import { ISystemContextFactory } from "../typechain/ISystemContextFactory"; import { expect } from "chai"; -import { prepareEnvironment } from "./shared/mocks"; describe("GasBoundCaller tests", function () { let tester: GasBoundCallerTester; - let systemContext: SystemContext; + let systemContext: ISystemContext; before(async () => { - await prepareEnvironment(); - // Note, that while the gas bound caller itself does not need to be in kernel space, // it does help a lot for easier testing, so the tester is in kernel space. const GAS_BOUND_CALLER_TESTER_ADDRESS = "0x000000000000000000000000000000000000ffff"; await deployContractOnAddress(GAS_BOUND_CALLER_TESTER_ADDRESS, "GasBoundCallerTester"); tester = GasBoundCallerTesterFactory.connect(GAS_BOUND_CALLER_TESTER_ADDRESS, getWallets()[0]); - systemContext = SystemContextFactory.connect(REAL_SYSTEM_CONTEXT_ADDRESS, getWallets()[0]); + systemContext = ISystemContextFactory.connect(REAL_SYSTEM_CONTEXT_ADDRESS, getWallets()[0]); }); it("Test entry overhead", async () => { @@ -61,9 +60,14 @@ describe("GasBoundCaller tests", function () { it("Should work correctly if gas provided is enough to cover both gas and pubdata", async () => { // This tx should succeed, since enough gas was provided to it await ( - await tester.gasBoundCall(tester.address, 80_000_000, tester.interface.encodeFunctionData("spender", [0, 100]), { - gasLimit: 80_000_000, - }) + await tester.gasBoundCall( + tester.address, + 80_000_000, + tester.interface.encodeFunctionData("spender", [0, 100, "0x"]), + { + gasLimit: 80_000_000, + } + ) ).wait(); }); @@ -78,7 +82,9 @@ describe("GasBoundCaller tests", function () { gasSpentOnPubdata, tester.address, gasSpentOnPubdata, - tester.interface.encodeFunctionData("spender", [0, pubdataToSend]), + tester.interface.encodeFunctionData("spender", [0, pubdataToSend, "0x"]), + "0x", + gasSpentOnPubdata, { gasLimit: 80_000_000, } @@ -97,7 +103,9 @@ describe("GasBoundCaller tests", function () { gasSpentOnPubdata, tester.address, 80_000_000, - tester.interface.encodeFunctionData("spender", [0, pubdataToSend]), + tester.interface.encodeFunctionData("spender", [0, pubdataToSend, "0x12345678"]), + "0x12345678", + gasSpentOnPubdata, { // Since we'll also spend some funds on execution, this gasLimit: 80_000_000, diff --git a/l1-contracts-foundry/foundry.toml b/l1-contracts-foundry/foundry.toml deleted file mode 100644 index 74e27e5a7..000000000 --- a/l1-contracts-foundry/foundry.toml +++ /dev/null @@ -1,19 +0,0 @@ -[profile.default] -src = "../l1-contracts/contracts" -out = "out" -libs = ["lib"] -remappings = [ - "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", - "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/", - "contracts/=../l1-contracts/contracts/", - "l2-contracts/=../l2-contracts/contracts/" -] -allow_paths = ["../l1-contracts/contracts", "../l2-contracts/contracts"] -fs_permissions = [ - { access = "read", path = "../system-contracts/bootloader/build/artifacts"}, - { access = "read", path = "../system-contracts/artifacts-zk/contracts-preprocessed"}, - { access = "read", path = "./script-config" }, - { access = "read-write", path = "./script-out" }, - { access = "read", path = "./out" } -] -evm_version="cancun" diff --git a/l1-contracts-foundry/lib/forge-std b/l1-contracts-foundry/lib/forge-std deleted file mode 160000 index b6a506db2..000000000 --- a/l1-contracts-foundry/lib/forge-std +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b6a506db2262cad5ff982a87789ee6d1558ec861 diff --git a/l1-contracts-foundry/lib/openzeppelin-contracts-upgradeable b/l1-contracts-foundry/lib/openzeppelin-contracts-upgradeable deleted file mode 160000 index a40cb0bda..000000000 --- a/l1-contracts-foundry/lib/openzeppelin-contracts-upgradeable +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a40cb0bda838c2ef3dfc252c179f5c37c32e80c4 diff --git a/l1-contracts/contracts/bridge/L1SharedBridge.sol b/l1-contracts/contracts/bridge/L1SharedBridge.sol index 65033089e..541b0e8eb 100644 --- a/l1-contracts/contracts/bridge/L1SharedBridge.sol +++ b/l1-contracts/contracts/bridge/L1SharedBridge.sol @@ -112,6 +112,12 @@ contract L1SharedBridge is IL1SharedBridge, ReentrancyGuard, Ownable2StepUpgrade _; } + /// @notice Checks that the message sender is the shared bridge itself. + modifier onlySelf() { + require(msg.sender == address(this), "ShB not shared bridge"); + _; + } + /// @dev Contract is expected to be used as proxy implementation. /// @dev Initialize the implementation to prevent Parity hack. constructor( @@ -163,7 +169,7 @@ contract L1SharedBridge is IL1SharedBridge, ReentrancyGuard, Ownable2StepUpgrade } /// @dev transfer tokens from legacy erc20 bridge or mailbox and set chainBalance as part of migration process - function transferFundsFromLegacy(address _token, address _target, uint256 _targetChainId) external onlyOwner { + function transferFundsFromLegacy(address _token, address _target, uint256 _targetChainId) external onlySelf { if (_token == ETH_TOKEN_ADDRESS) { uint256 balanceBefore = address(this).balance; IMailbox(_target).transferEthToSharedBridge(); @@ -179,11 +185,25 @@ contract L1SharedBridge is IL1SharedBridge, ReentrancyGuard, Ownable2StepUpgrade require(legacyBridgeBalance > 0, "ShB: 0 amount to transfer"); IL1ERC20Bridge(_target).transferTokenToSharedBridge(_token); uint256 balanceAfter = IERC20(_token).balanceOf(address(this)); - require(balanceAfter - balanceBefore == legacyBridgeBalance, "ShB: wrong amount transferred"); + require(balanceAfter - balanceBefore >= legacyBridgeBalance, "ShB: wrong amount transferred"); chainBalance[_targetChainId][_token] = chainBalance[_targetChainId][_token] + legacyBridgeBalance; } } + /// @dev transfer tokens from legacy erc20 bridge or mailbox and set chainBalance as part of migration process. + /// @dev Unlike `transferFundsFromLegacy` is provides a concrete limit on the gas used for the transfer and even if it will fail, it will not revert the whole transaction. + function safeTransferFundsFromLegacy( + address _token, + address _target, + uint256 _targetChainId, + uint256 _gasPerToken + ) external onlyOwner { + try this.transferFundsFromLegacy{gas: _gasPerToken}(_token, _target, _targetChainId) {} catch { + // A reasonable amount of gas will be provided to transfer the token. + // If the transfer fails, we don't want to revert the whole transaction. + } + } + function receiveEth(uint256 _chainId) external payable { require(BRIDGE_HUB.getHyperchain(_chainId) == msg.sender, "receiveEth not state transition"); } diff --git a/l1-contracts/contracts/common/Config.sol b/l1-contracts/contracts/common/Config.sol index 1e23213d1..72ca11aa1 100644 --- a/l1-contracts/contracts/common/Config.sol +++ b/l1-contracts/contracts/common/Config.sol @@ -27,10 +27,11 @@ uint256 constant PRIORITY_OPERATION_L2_TX_TYPE = 255; /// @dev Denotes the type of the zkSync transaction that is used for system upgrades. uint256 constant SYSTEM_UPGRADE_L2_TX_TYPE = 254; -/// @dev The maximal allowed difference between protocol versions in an upgrade. The 100 gap is needed +/// @dev The maximal allowed difference between protocol minor versions in an upgrade. The 100 gap is needed /// in case a protocol version has been tested on testnet, but then not launched on mainnet, e.g. /// due to a bug found. -uint256 constant MAX_ALLOWED_PROTOCOL_VERSION_DELTA = 100; +/// We are allowed to jump at most 100 minor versions at a time. The major version is always expected to be 0. +uint256 constant MAX_ALLOWED_MINOR_VERSION_DELTA = 100; /// @dev The amount of time in seconds the validator has to process the priority transaction /// NOTE: The constant is set to zero for the Alpha release period diff --git a/l1-contracts/contracts/common/libraries/SemVer.sol b/l1-contracts/contracts/common/libraries/SemVer.sol new file mode 100644 index 000000000..d20f6a1d1 --- /dev/null +++ b/l1-contracts/contracts/common/libraries/SemVer.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +/// @dev The number of bits dedicated to the "patch" portion of the protocol version. +/// This also defines the bit starting from which the "minor" part is located. +uint256 constant SEMVER_MINOR_OFFSET = 32; + +/// @dev The number of bits dedicated to the "patch" and "minor" portions of the protocol version. +/// This also defines the bit starting from which the "major" part is located. +/// Note, that currently, only major version of "0" is supported. +uint256 constant SEMVER_MAJOR_OFFSET = 64; + +/** + * @author Matter Labs + * @custom:security-contact security@matterlabs.dev + * @notice The library for managing SemVer for the protocol version. + */ +library SemVer { + /// @notice Unpacks the SemVer version from a single uint256 into major, minor and patch components. + /// @param _packedProtocolVersion The packed protocol version. + /// @return major The major version. + /// @return minor The minor version. + /// @return patch The patch version. + function unpackSemVer( + uint96 _packedProtocolVersion + ) internal pure returns (uint32 major, uint32 minor, uint32 patch) { + patch = uint32(_packedProtocolVersion); + minor = uint32(_packedProtocolVersion >> SEMVER_MINOR_OFFSET); + major = uint32(_packedProtocolVersion >> SEMVER_MAJOR_OFFSET); + } + + /// @notice Packs the SemVer version from the major, minor and patch components into a single uint96. + /// @param _major The major version. + /// @param _minor The minor version. + /// @param _patch The patch version. + /// @return packedProtocolVersion The packed protocol version. + function packSemVer( + uint32 _major, + uint32 _minor, + uint32 _patch + ) internal pure returns (uint96 packedProtocolVersion) { + packedProtocolVersion = + uint96(_patch) | + (uint96(_minor) << SEMVER_MINOR_OFFSET) | + (uint96(_major) << SEMVER_MAJOR_OFFSET); + } +} diff --git a/l1-contracts/contracts/dev-contracts/test/CustomUpgradeTest.sol b/l1-contracts/contracts/dev-contracts/test/CustomUpgradeTest.sol index e7c82d29c..7055ce557 100644 --- a/l1-contracts/contracts/dev-contracts/test/CustomUpgradeTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/CustomUpgradeTest.sol @@ -28,16 +28,17 @@ contract CustomUpgradeTest is BaseZkSyncUpgrade { /// @notice The main function that will be called by the upgrade proxy. /// @param _proposedUpgrade The upgrade to be executed. function upgrade(ProposedUpgrade calldata _proposedUpgrade) public override returns (bytes32) { - _setNewProtocolVersion(_proposedUpgrade.newProtocolVersion); + (uint32 newMinorVersion, bool isPatchOnly) = _setNewProtocolVersion(_proposedUpgrade.newProtocolVersion); _upgradeL1Contract(_proposedUpgrade.l1ContractsUpgradeCalldata); _upgradeVerifier(_proposedUpgrade.verifier, _proposedUpgrade.verifierParams); - _setBaseSystemContracts(_proposedUpgrade.bootloaderHash, _proposedUpgrade.defaultAccountHash); + _setBaseSystemContracts(_proposedUpgrade.bootloaderHash, _proposedUpgrade.defaultAccountHash, isPatchOnly); bytes32 txHash; txHash = _setL2SystemContractUpgrade( _proposedUpgrade.l2ProtocolUpgradeTx, _proposedUpgrade.factoryDeps, - _proposedUpgrade.newProtocolVersion + newMinorVersion, + isPatchOnly ); _postUpgrade(_proposedUpgrade.postUpgradeCalldata); diff --git a/l1-contracts/contracts/state-transition/IStateTransitionManager.sol b/l1-contracts/contracts/state-transition/IStateTransitionManager.sol index d275075d6..fef2398a5 100644 --- a/l1-contracts/contracts/state-transition/IStateTransitionManager.sol +++ b/l1-contracts/contracts/state-transition/IStateTransitionManager.sol @@ -10,22 +10,29 @@ import {FeeParams} from "./chain-deps/ZkSyncHyperchainStorage.sol"; /// @dev We use struct instead of raw parameters in `initialize` function to prevent "Stack too deep" error /// @param owner The address who can manage non-critical updates in the contract /// @param validatorTimelock The address that serves as consensus, i.e. can submit blocks to be processed +/// @param chainCreationParams The struct that contains the fields that define how a new chain should be created +/// @param protocolVersion The initial protocol version on the newly deployed chain +struct StateTransitionManagerInitializeData { + address owner; + address validatorTimelock; + ChainCreationParams chainCreationParams; + uint256 protocolVersion; +} + +/// @notice The struct that contains the fields that define how a new chain should be created +/// within this STM. /// @param genesisUpgrade The address that is used in the diamond cut initialize address on chain creation /// @param genesisBatchHash Batch hash of the genesis (initial) batch /// @param genesisIndexRepeatedStorageChanges The serial number of the shortcut storage key for the genesis batch /// @param genesisBatchCommitment The zk-proof commitment for the genesis batch /// @param diamondCut The diamond cut for the first upgrade transaction on the newly deployed chain -/// @param protocolVersion The initial protocol version on the newly deployed chain // solhint-disable-next-line gas-struct-packing -struct StateTransitionManagerInitializeData { - address owner; - address validatorTimelock; +struct ChainCreationParams { address genesisUpgrade; bytes32 genesisBatchHash; uint64 genesisIndexRepeatedStorageChanges; bytes32 genesisBatchCommitment; Diamond.DiamondCutData diamondCut; - uint256 protocolVersion; } interface IStateTransitionManager { @@ -49,8 +56,14 @@ interface IStateTransitionManager { /// @notice ValidatorTimelock changed event NewValidatorTimelock(address indexed oldValidatorTimelock, address indexed newValidatorTimelock); - /// @notice InitialCutHash changed - event NewInitialCutHash(bytes32 indexed oldInitialCutHash, bytes32 indexed newInitialCutHash); + /// @notice chain creation parameters changed + event NewChainCreationParams( + address genesisUpgrade, + bytes32 genesisBatchHash, + uint64 genesisIndexRepeatedStorageChanges, + bytes32 genesisBatchCommitment, + bytes32 newInitialCutHash + ); /// @notice new UpgradeCutHash event NewUpgradeCutHash(uint256 indexed protocolVersion, bytes32 indexed upgradeCutHash); @@ -86,10 +99,10 @@ interface IStateTransitionManager { function initialize(StateTransitionManagerInitializeData calldata _initializeData) external; - function setInitialCutHash(Diamond.DiamondCutData calldata _diamondCut) external; - function setValidatorTimelock(address _validatorTimelock) external; + function setChainCreationParams(ChainCreationParams calldata _chainCreationParams) external; + function getChainAdmin(uint256 _chainId) external view returns (address); function createNewChain( @@ -132,4 +145,6 @@ interface IStateTransitionManager { uint256 _oldProtocolVersion, Diamond.DiamondCutData calldata _diamondCut ) external; + + function getSemverProtocolVersion() external view returns (uint32, uint32, uint32); } diff --git a/l1-contracts/contracts/state-transition/StateTransitionManager.sol b/l1-contracts/contracts/state-transition/StateTransitionManager.sol index a152da054..982e39e58 100644 --- a/l1-contracts/contracts/state-transition/StateTransitionManager.sol +++ b/l1-contracts/contracts/state-transition/StateTransitionManager.sol @@ -2,9 +2,10 @@ pragma solidity 0.8.24; -// solhint-disable gas-custom-errors +// solhint-disable gas-custom-errors, reason-string import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {Diamond} from "./libraries/Diamond.sol"; import {DiamondProxy} from "./chain-deps/DiamondProxy.sol"; @@ -12,7 +13,7 @@ import {IAdmin} from "./chain-interfaces/IAdmin.sol"; import {IDefaultUpgrade} from "../upgrades/IDefaultUpgrade.sol"; import {IDiamondInit} from "./chain-interfaces/IDiamondInit.sol"; import {IExecutor} from "./chain-interfaces/IExecutor.sol"; -import {IStateTransitionManager, StateTransitionManagerInitializeData} from "./IStateTransitionManager.sol"; +import {IStateTransitionManager, StateTransitionManagerInitializeData, ChainCreationParams} from "./IStateTransitionManager.sol"; import {ISystemContext} from "./l2-deps/ISystemContext.sol"; import {IZkSyncHyperchain} from "./chain-interfaces/IZkSyncHyperchain.sol"; import {FeeParams} from "./chain-deps/ZkSyncHyperchainStorage.sol"; @@ -23,6 +24,7 @@ import {ProposedUpgrade} from "../upgrades/BaseZkSyncUpgrade.sol"; import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, L2_TO_L1_LOG_SERIALIZE_SIZE, DEFAULT_L2_LOGS_TREE_ROOT_HASH, EMPTY_STRING_KECCAK, SYSTEM_UPGRADE_L2_TX_TYPE, PRIORITY_TX_MAX_GAS_LIMIT} from "../common/Config.sol"; import {VerifierParams} from "./chain-interfaces/IVerifier.sol"; +import {SemVer} from "../common/libraries/SemVer.sol"; /// @title State Transition Manager contract /// @author Matter Labs @@ -49,7 +51,7 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own /// @dev The genesisUpgrade contract address, used to setChainId address public genesisUpgrade; - /// @dev The current protocolVersion + /// @dev The current packed protocolVersion. To access human-readable version, use `getSemverProtocolVersion` function. uint256 public protocolVersion; /// @dev The timestamp when protocolVersion can be last used @@ -72,6 +74,10 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own constructor(address _bridgehub, uint256 _maxNumberOfHyperchains) reentrancyGuardInitializer { BRIDGE_HUB = _bridgehub; MAX_NUMBER_OF_HYPERCHAINS = _maxNumberOfHyperchains; + + // While this does not provide a protection in the production, it is needed for local testing + // Length of the L2Log encoding should not be equal to the length of other L2Logs' tree nodes preimages + assert(L2_TO_L1_LOG_SERIALIZE_SIZE != 2 * 32); } /// @notice only the bridgehub can call @@ -86,6 +92,12 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own _; } + /// @return The tuple of (major, minor, patch) protocol version. + function getSemverProtocolVersion() external view returns (uint32, uint32, uint32) { + // slither-disable-next-line unused-return + return SemVer.unpackSemVer(SafeCast.toUint96(protocolVersion)); + } + /// @notice Returns all the registered hyperchain addresses function getAllHyperchains() public view override returns (address[] memory chainAddresses) { uint256[] memory keys = hyperchainMap.keys(); @@ -119,28 +131,54 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own require(_initializeData.owner != address(0), "STM: owner zero"); _transferOwnership(_initializeData.owner); - genesisUpgrade = _initializeData.genesisUpgrade; protocolVersion = _initializeData.protocolVersion; protocolVersionDeadline[_initializeData.protocolVersion] = type(uint256).max; validatorTimelock = _initializeData.validatorTimelock; + _setChainCreationParams(_initializeData.chainCreationParams); + } + + /// @notice Updates the parameters with which a new chain is created + /// @param _chainCreationParams The new chain creation parameters + function _setChainCreationParams(ChainCreationParams calldata _chainCreationParams) internal { + require(_chainCreationParams.genesisUpgrade != address(0), "STM: genesisUpgrade zero"); + require(_chainCreationParams.genesisBatchHash != bytes32(0), "STM: genesisBatchHash zero"); + require( + _chainCreationParams.genesisIndexRepeatedStorageChanges != uint64(0), + "STM: genesisIndexRepeatedStorageChanges zero" + ); + require(_chainCreationParams.genesisBatchCommitment != bytes32(0), "STM: genesisBatchCommitment zero"); + + genesisUpgrade = _chainCreationParams.genesisUpgrade; + // We need to initialize the state hash because it is used in the commitment of the next batch IExecutor.StoredBatchInfo memory batchZero = IExecutor.StoredBatchInfo({ batchNumber: 0, - batchHash: _initializeData.genesisBatchHash, - indexRepeatedStorageChanges: _initializeData.genesisIndexRepeatedStorageChanges, + batchHash: _chainCreationParams.genesisBatchHash, + indexRepeatedStorageChanges: _chainCreationParams.genesisIndexRepeatedStorageChanges, numberOfLayer1Txs: 0, priorityOperationsHash: EMPTY_STRING_KECCAK, l2LogsTreeRoot: DEFAULT_L2_LOGS_TREE_ROOT_HASH, timestamp: 0, - commitment: _initializeData.genesisBatchCommitment + commitment: _chainCreationParams.genesisBatchCommitment }); storedBatchZero = keccak256(abi.encode(batchZero)); - initialCutHash = keccak256(abi.encode(_initializeData.diamondCut)); + bytes32 newInitialCutHash = keccak256(abi.encode(_chainCreationParams.diamondCut)); + initialCutHash = newInitialCutHash; + + emit NewChainCreationParams({ + genesisUpgrade: _chainCreationParams.genesisUpgrade, + genesisBatchHash: _chainCreationParams.genesisBatchHash, + genesisIndexRepeatedStorageChanges: _chainCreationParams.genesisIndexRepeatedStorageChanges, + genesisBatchCommitment: _chainCreationParams.genesisBatchCommitment, + newInitialCutHash: newInitialCutHash + }); + } - // While this does not provide a protection in the production, it is needed for local testing - // Length of the L2Log encoding should not be equal to the length of other L2Logs' tree nodes preimages - assert(L2_TO_L1_LOG_SERIALIZE_SIZE != 2 * 32); + /// @notice Updates the parameters with which a new chain is created + /// @param _chainCreationParams The new chain creation parameters + function setChainCreationParams(ChainCreationParams calldata _chainCreationParams) external onlyOwner { + _setChainCreationParams(_chainCreationParams); } /// @notice Starts the transfer of admin rights. Only the current admin can propose a new pending one. @@ -176,14 +214,6 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own emit NewValidatorTimelock(oldValidatorTimelock, _validatorTimelock); } - /// @dev set initial cutHash - function setInitialCutHash(Diamond.DiamondCutData calldata _diamondCut) external onlyOwner { - bytes32 oldInitialCutHash = initialCutHash; - bytes32 newCutHash = keccak256(abi.encode(_diamondCut)); - initialCutHash = newCutHash; - emit NewInitialCutHash(oldInitialCutHash, newCutHash); - } - /// @dev set New Version with upgrade from old version function setNewVersionUpgrade( Diamond.DiamondCutData calldata _cutData, @@ -283,6 +313,10 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own uint256[] memory uintEmptyArray; bytes[] memory bytesEmptyArray; + uint256 cachedProtocolVersion = protocolVersion; + // slither-disable-next-line unused-return + (, uint32 minorVersion, ) = SemVer.unpackSemVer(SafeCast.toUint96(cachedProtocolVersion)); + L2CanonicalTransaction memory l2ProtocolUpgradeTx = L2CanonicalTransaction({ txType: SYSTEM_UPGRADE_L2_TX_TYPE, from: uint256(uint160(L2_FORCE_DEPLOYER_ADDR)), @@ -292,8 +326,8 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own maxFeePerGas: uint256(0), maxPriorityFeePerGas: uint256(0), paymaster: uint256(0), - // Note, that the protocol version is used as "nonce" for system upgrade transactions - nonce: protocolVersion, + // Note, that the `minor` of the protocol version is used as "nonce" for system upgrade transactions + nonce: uint256(minorVersion), value: 0, reserved: [uint256(0), 0, 0, 0], data: systemContextCalldata, @@ -317,7 +351,7 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own l1ContractsUpgradeCalldata: new bytes(0), postUpgradeCalldata: new bytes(0), upgradeTimestamp: 0, - newProtocolVersion: protocolVersion + newProtocolVersion: cachedProtocolVersion }); Diamond.FacetCut[] memory emptyArray; @@ -328,7 +362,7 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own }); IAdmin(_chainContract).executeUpgrade(cutData); - emit SetChainIdUpgrade(_chainContract, l2ProtocolUpgradeTx, protocolVersion); + emit SetChainIdUpgrade(_chainContract, l2ProtocolUpgradeTx, cachedProtocolVersion); } /// @dev used to register already deployed hyperchain contracts diff --git a/l1-contracts/contracts/state-transition/Verifier.sol b/l1-contracts/contracts/state-transition/Verifier.sol index 4f0b1d043..cfc1f848b 100644 --- a/l1-contracts/contracts/state-transition/Verifier.sol +++ b/l1-contracts/contracts/state-transition/Verifier.sol @@ -9,7 +9,7 @@ import {IVerifier} from "./chain-interfaces/IVerifier.sol"; /// @notice Modified version of the Permutations over Lagrange-bases for Oecumenical Noninteractive arguments of /// Knowledge (PLONK) verifier. /// Modifications have been made to optimize the proof system for zkSync hyperchain circuits. -/// @dev Contract was generated from a verification key with a hash of 0x1d485be42d712856dfe85b3cf7823f020fa5f83cb41c83f9da307fdc2089beee +/// @dev Contract was generated from a verification key with a hash of 0x14f97b81e54b35fe673d8708cc1a19e1ea5b5e348e12d31e39824ed4f42bbca2 /// @dev It uses a custom memory layout inside the inline assembly block. Each reserved memory cell is declared in the /// constants below. /// @dev For a better understanding of the verifier algorithm please refer to the following papers: @@ -284,8 +284,8 @@ contract Verifier is IVerifier { function _loadVerificationKey() internal pure virtual { assembly { // gate setup commitments - mstore(VK_GATE_SETUP_0_X_SLOT, 0x2b344993e706c18427e96e2eefd8e89aaf4cd464814ed978b98793bf129a786c) - mstore(VK_GATE_SETUP_0_Y_SLOT, 0x123af72bf1c5e693516b037d16ba48711c3486823aec3772318b6737634575a4) + mstore(VK_GATE_SETUP_0_X_SLOT, 0x110deb1e0863737f9a3d7b4de641a03aa00a77bc9f1a05acc9d55b76ab9fdd4d) + mstore(VK_GATE_SETUP_0_Y_SLOT, 0x2c9dc252441e9298b7f6df6335a252517b7bccb924adf537b87c5cd3383fd7a9) mstore(VK_GATE_SETUP_1_X_SLOT, 0x04659caf7b05471ba5ba85b1ab62267aa6c456836e625f169f7119d55b9462d2) mstore(VK_GATE_SETUP_1_Y_SLOT, 0x0ea63403692148d2ad22189a1e5420076312f4d46e62036a043a6b0b84d5b410) mstore(VK_GATE_SETUP_2_X_SLOT, 0x0e6696d09d65fce1e42805be03fca1f14aea247281f688981f925e77d4ce2291) @@ -296,8 +296,8 @@ contract Verifier is IVerifier { mstore(VK_GATE_SETUP_4_Y_SLOT, 0x22e404bc91350f3bc7daad1d1025113742436983c85eac5ab7b42221a181b81e) mstore(VK_GATE_SETUP_5_X_SLOT, 0x0d9b29613037a5025655c82b143d2b7449c98f3aea358307c8529249cc54f3b9) mstore(VK_GATE_SETUP_5_Y_SLOT, 0x15b3c4c946ad1babfc4c03ff7c2423fd354af3a9305c499b7fb3aaebe2fee746) - mstore(VK_GATE_SETUP_6_X_SLOT, 0x18e466aac9b830ea5417e1ba2e920acf041e3976d36fe891be80beac1d1696c7) - mstore(VK_GATE_SETUP_6_Y_SLOT, 0x025f89444627d09c7b1bf665dc6f58055bf729c683a4b3e815215758a7075602) + mstore(VK_GATE_SETUP_6_X_SLOT, 0x2a4cb6c495dbc7201142cc773da895ae2046e790073988fb850aca6aead27b8a) + mstore(VK_GATE_SETUP_6_Y_SLOT, 0x28ef9200c3cb67da82030520d640292014f5f7c2e2909da608812e04671a3acf) mstore(VK_GATE_SETUP_7_X_SLOT, 0x283344a1ab3e55ecfd904d0b8e9f4faea338df5a4ead2fa9a42f0e103da40abc) mstore(VK_GATE_SETUP_7_Y_SLOT, 0x223b37b83b9687512d322993edd70e508dd80adb10bcf7321a3cc8a44c269521) diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol index 169426871..217e08a1a 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol @@ -53,8 +53,10 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { bytes32[] memory blobCommitments = new bytes32[](MAX_NUMBER_OF_BLOBS); if (pricingMode == PubdataPricingMode.Validium) { // skipping data validation for validium, we just check that the data is empty - require(logOutput.pubdataHash == 0x00, "v0h"); require(_newBatch.pubdataCommitments.length == 1, "EF: v0l"); + for (uint8 i = uint8(SystemLogKey.BLOB_ONE_HASH_KEY); i <= uint8(SystemLogKey.BLOB_SIX_HASH_KEY); ++i) { + logOutput.blobHashes[i - uint8(SystemLogKey.BLOB_ONE_HASH_KEY)] = bytes32(0); + } } else if (pubdataSource == uint8(PubdataSource.Blob)) { // In this scenario, pubdataCommitments is a list of: opening point (16 bytes) || claimed value (32 bytes) || commitment (48 bytes) || proof (48 bytes)) = 144 bytes blobCommitments = _verifyBlobInformation(_newBatch.pubdataCommitments[1:], logOutput.blobHashes); diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Getters.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Getters.sol index 3c72805eb..77800ed12 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Getters.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Getters.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.24; // solhint-disable gas-custom-errors +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {ZkSyncHyperchainBase} from "./ZkSyncHyperchainBase.sol"; import {PubdataPricingMode} from "../ZkSyncHyperchainStorage.sol"; @@ -12,6 +13,7 @@ import {PriorityQueue, PriorityOperation} from "../../../state-transition/librar import {UncheckedMath} from "../../../common/libraries/UncheckedMath.sol"; import {IGetters} from "../../chain-interfaces/IGetters.sol"; import {ILegacyGetters} from "../../chain-interfaces/ILegacyGetters.sol"; +import {SemVer} from "../../../common/libraries/SemVer.sol"; // While formally the following import is not used, it is needed to inherit documentation from it import {IZkSyncHyperchainBase} from "../../chain-interfaces/IZkSyncHyperchainBase.sol"; @@ -145,6 +147,12 @@ contract GettersFacet is ZkSyncHyperchainBase, IGetters, ILegacyGetters { return s.protocolVersion; } + /// @inheritdoc IGetters + function getSemverProtocolVersion() external view returns (uint32, uint32, uint32) { + // slither-disable-next-line unused-return + return SemVer.unpackSemVer(SafeCast.toUint96(s.protocolVersion)); + } + /// @inheritdoc IGetters function getL2SystemContractsUpgradeTxHash() external view returns (bytes32) { return s.l2SystemContractsUpgradeTxHash; diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol index 4eec88868..9d77efdf3 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol @@ -85,9 +85,12 @@ interface IGetters is IZkSyncHyperchainBase { /// @return Whether the diamond is frozen or not function isDiamondStorageFrozen() external view returns (bool); - /// @return The current protocol version + /// @return The current packed protocol version. To access human-readable version, use `getSemverProtocolVersion` function. function getProtocolVersion() external view returns (uint256); + /// @return The tuple of (major, minor, patch) protocol version. + function getSemverProtocolVersion() external view returns (uint32, uint32, uint32); + /// @return The upgrade system contract transaction hash, 0 if the upgrade is not initialized function getL2SystemContractsUpgradeTxHash() external view returns (bytes32); diff --git a/l1-contracts/contracts/upgrades/BaseZkSyncUpgrade.sol b/l1-contracts/contracts/upgrades/BaseZkSyncUpgrade.sol index 597563b03..6510a2b7d 100644 --- a/l1-contracts/contracts/upgrades/BaseZkSyncUpgrade.sol +++ b/l1-contracts/contracts/upgrades/BaseZkSyncUpgrade.sol @@ -3,14 +3,16 @@ pragma solidity 0.8.24; // solhint-disable reason-string, gas-custom-errors +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {ZkSyncHyperchainBase} from "../state-transition/chain-deps/facets/ZkSyncHyperchainBase.sol"; import {VerifierParams} from "../state-transition/chain-interfaces/IVerifier.sol"; import {IVerifier} from "../state-transition/chain-interfaces/IVerifier.sol"; import {L2ContractHelper} from "../common/libraries/L2ContractHelper.sol"; import {TransactionValidator} from "../state-transition/libraries/TransactionValidator.sol"; -import {MAX_NEW_FACTORY_DEPS, SYSTEM_UPGRADE_L2_TX_TYPE, MAX_ALLOWED_PROTOCOL_VERSION_DELTA} from "../common/Config.sol"; +import {MAX_NEW_FACTORY_DEPS, SYSTEM_UPGRADE_L2_TX_TYPE, MAX_ALLOWED_MINOR_VERSION_DELTA} from "../common/Config.sol"; import {L2CanonicalTransaction} from "../common/Messaging.sol"; +import {SemVer} from "../common/libraries/SemVer.sol"; /// @notice The struct that represents the upgrade proposal. /// @param l2ProtocolUpgradeTx The system upgrade transaction. @@ -72,15 +74,16 @@ abstract contract BaseZkSyncUpgrade is ZkSyncHyperchainBase { // as the permitted delay window is reduced in the future. require(block.timestamp >= _proposedUpgrade.upgradeTimestamp, "Upgrade is not ready yet"); - _setNewProtocolVersion(_proposedUpgrade.newProtocolVersion); + (uint32 newMinorVersion, bool isPatchOnly) = _setNewProtocolVersion(_proposedUpgrade.newProtocolVersion); _upgradeL1Contract(_proposedUpgrade.l1ContractsUpgradeCalldata); _upgradeVerifier(_proposedUpgrade.verifier, _proposedUpgrade.verifierParams); - _setBaseSystemContracts(_proposedUpgrade.bootloaderHash, _proposedUpgrade.defaultAccountHash); + _setBaseSystemContracts(_proposedUpgrade.bootloaderHash, _proposedUpgrade.defaultAccountHash, isPatchOnly); txHash = _setL2SystemContractUpgrade( _proposedUpgrade.l2ProtocolUpgradeTx, _proposedUpgrade.factoryDeps, - _proposedUpgrade.newProtocolVersion + newMinorVersion, + isPatchOnly ); _postUpgrade(_proposedUpgrade.postUpgradeCalldata); @@ -90,11 +93,14 @@ abstract contract BaseZkSyncUpgrade is ZkSyncHyperchainBase { /// @notice Change default account bytecode hash, that is used on L2 /// @param _l2DefaultAccountBytecodeHash The hash of default account L2 bytecode - function _setL2DefaultAccountBytecodeHash(bytes32 _l2DefaultAccountBytecodeHash) private { + /// @param _patchOnly Whether only the patch part of the protocol version semver has changed + function _setL2DefaultAccountBytecodeHash(bytes32 _l2DefaultAccountBytecodeHash, bool _patchOnly) private { if (_l2DefaultAccountBytecodeHash == bytes32(0)) { return; } + require(!_patchOnly, "Patch only upgrade can not set new default account"); + L2ContractHelper.validateBytecodeHash(_l2DefaultAccountBytecodeHash); // Save previous value into the stack to put it into the event later @@ -107,11 +113,14 @@ abstract contract BaseZkSyncUpgrade is ZkSyncHyperchainBase { /// @notice Change bootloader bytecode hash, that is used on L2 /// @param _l2BootloaderBytecodeHash The hash of bootloader L2 bytecode - function _setL2BootloaderBytecodeHash(bytes32 _l2BootloaderBytecodeHash) private { + /// @param _patchOnly Whether only the patch part of the protocol version semver has changed + function _setL2BootloaderBytecodeHash(bytes32 _l2BootloaderBytecodeHash, bool _patchOnly) private { if (_l2BootloaderBytecodeHash == bytes32(0)) { return; } + require(!_patchOnly, "Patch only upgrade can not set new bootloader"); + L2ContractHelper.validateBytecodeHash(_l2BootloaderBytecodeHash); // Save previous value into the stack to put it into the event later @@ -169,25 +178,33 @@ abstract contract BaseZkSyncUpgrade is ZkSyncHyperchainBase { /// @notice Updates the bootloader hash and the hash of the default account /// @param _bootloaderHash The hash of the new bootloader bytecode. If zero, it will not be updated. /// @param _defaultAccountHash The hash of the new default account bytecode. If zero, it will not be updated. - function _setBaseSystemContracts(bytes32 _bootloaderHash, bytes32 _defaultAccountHash) internal { - _setL2BootloaderBytecodeHash(_bootloaderHash); - _setL2DefaultAccountBytecodeHash(_defaultAccountHash); + /// @param _patchOnly Whether only the patch part of the protocol version semver has changed. + function _setBaseSystemContracts(bytes32 _bootloaderHash, bytes32 _defaultAccountHash, bool _patchOnly) internal { + _setL2BootloaderBytecodeHash(_bootloaderHash, _patchOnly); + _setL2DefaultAccountBytecodeHash(_defaultAccountHash, _patchOnly); } /// @notice Sets the hash of the L2 system contract upgrade transaction for the next batch to be committed /// @dev If the transaction is noop (i.e. its type is 0) it does nothing and returns 0. /// @param _l2ProtocolUpgradeTx The L2 system contract upgrade transaction. + /// @param _factoryDeps The factory dependencies that are used by the transaction. + /// @param _newMinorProtocolVersion The new minor protocol version. It must be used as the `nonce` field + /// of the `_l2ProtocolUpgradeTx`. + /// @param _patchOnly Whether only the patch part of the protocol version semver has changed. /// @return System contracts upgrade transaction hash. Zero if no upgrade transaction is set. function _setL2SystemContractUpgrade( L2CanonicalTransaction calldata _l2ProtocolUpgradeTx, bytes[] calldata _factoryDeps, - uint256 _newProtocolVersion + uint32 _newMinorProtocolVersion, + bool _patchOnly ) internal returns (bytes32) { // If the type is 0, it is considered as noop and so will not be required to be executed. if (_l2ProtocolUpgradeTx.txType == 0) { return bytes32(0); } + require(!_patchOnly, "Patch only upgrade can not set upgrade transaction"); + require(_l2ProtocolUpgradeTx.txType == SYSTEM_UPGRADE_L2_TX_TYPE, "L2 system upgrade tx type is wrong"); bytes memory encodedTransaction = abi.encode(_l2ProtocolUpgradeTx); @@ -204,7 +221,7 @@ abstract contract BaseZkSyncUpgrade is ZkSyncHyperchainBase { // We want the hashes of l2 system upgrade transactions to be unique. // This is why we require that the `nonce` field is unique to each upgrade. require( - _l2ProtocolUpgradeTx.nonce == _newProtocolVersion, + _l2ProtocolUpgradeTx.nonce == _newMinorProtocolVersion, "The new protocol version should be included in the L2 system upgrade tx" ); @@ -235,24 +252,48 @@ abstract contract BaseZkSyncUpgrade is ZkSyncHyperchainBase { /// @notice Changes the protocol version /// @param _newProtocolVersion The new protocol version - function _setNewProtocolVersion(uint256 _newProtocolVersion) internal virtual { + function _setNewProtocolVersion( + uint256 _newProtocolVersion + ) internal virtual returns (uint32 newMinorVersion, bool patchOnly) { uint256 previousProtocolVersion = s.protocolVersion; require( _newProtocolVersion > previousProtocolVersion, "New protocol version is not greater than the current one" ); - require( - _newProtocolVersion - previousProtocolVersion <= MAX_ALLOWED_PROTOCOL_VERSION_DELTA, - "Too big protocol version difference" + // slither-disable-next-line unused-return + (uint32 previousMajorVersion, uint32 previousMinorVersion, ) = SemVer.unpackSemVer( + SafeCast.toUint96(previousProtocolVersion) ); + require(previousMajorVersion == 0, "Implementation requires that the major version is 0 at all times"); - // If the previous upgrade had an L2 system upgrade transaction, we require that it is finalized. - // Note it is important to keep this check, as otherwise hyperchains might skip upgrades by overwriting - require(s.l2SystemContractsUpgradeTxHash == bytes32(0), "Previous upgrade has not been finalized"); - require( - s.l2SystemContractsUpgradeBatchNumber == 0, - "The batch number of the previous upgrade has not been cleaned" - ); + uint32 newMajorVersion; + // slither-disable-next-line unused-return + (newMajorVersion, newMinorVersion, ) = SemVer.unpackSemVer(SafeCast.toUint96(_newProtocolVersion)); + require(newMajorVersion == 0, "Major must always be 0"); + + // Since `_newProtocolVersion > previousProtocolVersion`, and both old and new major version is 0, + // the difference between minor versions is >= 0. + uint256 minorDelta = newMinorVersion - previousMinorVersion; + + if (minorDelta == 0) { + patchOnly = true; + } + + // While this is implicitly enforced by other checks above, we still double check just in case + require(minorDelta <= MAX_ALLOWED_MINOR_VERSION_DELTA, "Too big protocol version difference"); + + // If the minor version changes also, we need to ensure that the previous upgrade has been finalized. + // In case the minor version does not change, we permit to keep the old upgrade transaction in the system, but it + // must be ensured in the other parts of the upgrade that the is not overridden. + if (!patchOnly) { + // If the previous upgrade had an L2 system upgrade transaction, we require that it is finalized. + // Note it is important to keep this check, as otherwise hyperchains might skip upgrades by overwriting + require(s.l2SystemContractsUpgradeTxHash == bytes32(0), "Previous upgrade has not been finalized"); + require( + s.l2SystemContractsUpgradeBatchNumber == 0, + "The batch number of the previous upgrade has not been cleaned" + ); + } s.protocolVersion = _newProtocolVersion; emit NewProtocolVersion(previousProtocolVersion, _newProtocolVersion); diff --git a/l1-contracts/contracts/upgrades/BaseZkSyncUpgradeGenesis.sol b/l1-contracts/contracts/upgrades/BaseZkSyncUpgradeGenesis.sol index 656ddda13..51d24592d 100644 --- a/l1-contracts/contracts/upgrades/BaseZkSyncUpgradeGenesis.sol +++ b/l1-contracts/contracts/upgrades/BaseZkSyncUpgradeGenesis.sol @@ -2,9 +2,12 @@ pragma solidity 0.8.24; -import {MAX_ALLOWED_PROTOCOL_VERSION_DELTA} from "../common/Config.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; + import {BaseZkSyncUpgrade} from "./BaseZkSyncUpgrade.sol"; -import {ProtocolVersionShouldBeGreater, ProtocolVersionDeltaTooLarge, PreviousUpgradeNotFinalized, PreviousUpgradeBatchNotCleared} from "./ZkSyncUpgradeErrors.sol"; +import {ProtocolVersionShouldBeGreater, ProtocolVersionDeltaTooLarge, PreviousUpgradeNotFinalized, PreviousUpgradeBatchNotCleared, ProtocolMajorVersionNotZero} from "./ZkSyncUpgradeErrors.sol"; +import {MAX_ALLOWED_MINOR_VERSION_DELTA} from "../common/Config.sol"; +import {SemVer} from "../common/libraries/SemVer.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev @@ -12,7 +15,9 @@ import {ProtocolVersionShouldBeGreater, ProtocolVersionDeltaTooLarge, PreviousUp abstract contract BaseZkSyncUpgradeGenesis is BaseZkSyncUpgrade { /// @notice Changes the protocol version /// @param _newProtocolVersion The new protocol version - function _setNewProtocolVersion(uint256 _newProtocolVersion) internal override { + function _setNewProtocolVersion( + uint256 _newProtocolVersion + ) internal override returns (uint32 newMinorVersion, bool patchOnly) { uint256 previousProtocolVersion = s.protocolVersion; if ( // IMPORTANT Genesis Upgrade difference: Note this is the only thing change > to >= @@ -20,18 +25,46 @@ abstract contract BaseZkSyncUpgradeGenesis is BaseZkSyncUpgrade { ) { revert ProtocolVersionShouldBeGreater(previousProtocolVersion, _newProtocolVersion); } - uint256 protocolDiff = _newProtocolVersion - previousProtocolVersion; - if (protocolDiff > MAX_ALLOWED_PROTOCOL_VERSION_DELTA) { - revert ProtocolVersionDeltaTooLarge(protocolDiff, MAX_ALLOWED_PROTOCOL_VERSION_DELTA); + // slither-disable-next-line unused-return + (uint32 previousMajorVersion, uint32 previousMinorVersion, ) = SemVer.unpackSemVer( + SafeCast.toUint96(previousProtocolVersion) + ); + + if (previousMajorVersion != 0) { + revert ProtocolMajorVersionNotZero(); + } + + uint32 newMajorVersion; + // slither-disable-next-line unused-return + (newMajorVersion, newMinorVersion, ) = SemVer.unpackSemVer(SafeCast.toUint96(_newProtocolVersion)); + if (newMajorVersion != 0) { + revert ProtocolMajorVersionNotZero(); } - // If the previous upgrade had an L2 system upgrade transaction, we require that it is finalized. - if (s.l2SystemContractsUpgradeTxHash != bytes32(0)) { - revert PreviousUpgradeNotFinalized(); + // Since `_newProtocolVersion > previousProtocolVersion`, and both old and new major version is 0, + // the difference between minor versions is >= 0. + uint256 minorDelta = newMinorVersion - previousMinorVersion; + + // IMPORTANT Genesis Upgrade difference: We never set patchOnly to `true` to allow to put a system upgrade transaction there. + patchOnly = false; + + // While this is implicitly enforced by other checks above, we still double check just in case + if (minorDelta > MAX_ALLOWED_MINOR_VERSION_DELTA) { + revert ProtocolVersionDeltaTooLarge(minorDelta, MAX_ALLOWED_MINOR_VERSION_DELTA); } - if (s.l2SystemContractsUpgradeBatchNumber != 0) { - revert PreviousUpgradeBatchNotCleared(); + // If the minor version changes also, we need to ensure that the previous upgrade has been finalized. + // In case the minor version does not change, we permit to keep the old upgrade transaction in the system, but it + // must be ensured in the other parts of the upgrade that the is not overridden. + if (!patchOnly) { + // If the previous upgrade had an L2 system upgrade transaction, we require that it is finalized. + // Note it is important to keep this check, as otherwise hyperchains might skip upgrades by overwriting + if (s.l2SystemContractsUpgradeTxHash != bytes32(0)) { + revert PreviousUpgradeNotFinalized(); + } + if (s.l2SystemContractsUpgradeBatchNumber != 0) { + revert PreviousUpgradeBatchNotCleared(); + } } s.protocolVersion = _newProtocolVersion; diff --git a/l1-contracts/contracts/upgrades/UpgradeHyperchains.sol b/l1-contracts/contracts/upgrades/UpgradeHyperchains.sol index 32d6e4380..bcfdc22c7 100644 --- a/l1-contracts/contracts/upgrades/UpgradeHyperchains.sol +++ b/l1-contracts/contracts/upgrades/UpgradeHyperchains.sol @@ -15,12 +15,20 @@ contract UpgradeHyperchains is BaseZkSyncUpgrade { /// @notice The main function that will be called by the upgrade proxy. /// @param _proposedUpgrade The upgrade to be executed. function upgrade(ProposedUpgrade calldata _proposedUpgrade) public override returns (bytes32) { - (uint256 chainId, address bridgehubAddress, address stateTransitionManager, address sharedBridgeAddress) = abi - .decode(_proposedUpgrade.postUpgradeCalldata, (uint256, address, address, address)); + ( + uint256 chainId, + address bridgehubAddress, + address stateTransitionManager, + address sharedBridgeAddress, + address chainAdmin, + address validatorTimelock + ) = abi.decode(_proposedUpgrade.postUpgradeCalldata, (uint256, address, address, address, address, address)); require(chainId != 0, "UpgradeHyperchain: 1"); require(bridgehubAddress != address(0), "UpgradeHyperchain: 2"); require(stateTransitionManager != address(0), "UpgradeHyperchain: 3"); require(sharedBridgeAddress != address(0), "UpgradeHyperchain: 4"); + require(chainAdmin != address(0), "UpgradeHyperchains: 5"); + require(validatorTimelock != address(0), "UpgradeHyperchains: 6"); s.chainId = chainId; s.bridgehub = bridgehubAddress; @@ -29,6 +37,8 @@ contract UpgradeHyperchains is BaseZkSyncUpgrade { s.baseToken = ETH_TOKEN_ADDRESS; s.baseTokenGasPriceMultiplierNominator = 1; s.baseTokenGasPriceMultiplierDenominator = 1; + s.admin = chainAdmin; + s.validators[validatorTimelock] = true; super.upgrade(_proposedUpgrade); return Diamond.DIAMOND_INIT_SUCCESS_RETURN_VALUE; diff --git a/l1-contracts/contracts/upgrades/ZkSyncUpgradeErrors.sol b/l1-contracts/contracts/upgrades/ZkSyncUpgradeErrors.sol index d2e78011b..d71680e73 100644 --- a/l1-contracts/contracts/upgrades/ZkSyncUpgradeErrors.sol +++ b/l1-contracts/contracts/upgrades/ZkSyncUpgradeErrors.sol @@ -6,3 +6,4 @@ error ProtocolVersionShouldBeGreater(uint256 _oldProtocolVersion, uint256 _newPr error ProtocolVersionDeltaTooLarge(uint256 _proposedDelta, uint256 _maxDelta); error PreviousUpgradeNotFinalized(); error PreviousUpgradeBatchNotCleared(); +error ProtocolMajorVersionNotZero(); diff --git a/l1-contracts-foundry/script-config/config-deploy-erc20.toml b/l1-contracts/deploy-script-config-template/config-deploy-erc20.toml similarity index 51% rename from l1-contracts-foundry/script-config/config-deploy-erc20.toml rename to l1-contracts/deploy-script-config-template/config-deploy-erc20.toml index b77417c0d..a22795b9b 100644 --- a/l1-contracts-foundry/script-config/config-deploy-erc20.toml +++ b/l1-contracts/deploy-script-config-template/config-deploy-erc20.toml @@ -1,14 +1,11 @@ -create2_factory_addr = "0x4e59b44847b379578588920cA78FbF26c0B4956C" -create2_factory_salt = "0x00000000000000000000000000000000000000000000000000000000000000ff" - -[tokens.dai] +[tokens.DAI] name = "DAI" symbol = "DAI" decimals = 18 implementation = "TestnetERC20Token.sol" mint = "10000000000" -[tokens.weth] +[tokens.WETH] name = "Wrapped Ether" symbol = "WETH" decimals = 18 diff --git a/l1-contracts-foundry/script-config/config-deploy-l1.toml b/l1-contracts/deploy-script-config-template/config-deploy-l1.toml similarity index 65% rename from l1-contracts-foundry/script-config/config-deploy-l1.toml rename to l1-contracts/deploy-script-config-template/config-deploy-l1.toml index ddcd9b27e..ad8982ffc 100644 --- a/l1-contracts-foundry/script-config/config-deploy-l1.toml +++ b/l1-contracts/deploy-script-config-template/config-deploy-l1.toml @@ -1,16 +1,17 @@ era_chain_id = 9 owner_address = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" +testnet_verifier = true [contracts] governance_security_council_address = "0x0000000000000000000000000000000000000000" governance_min_delay = 0 -max_number_of_hyperchains = 100 +max_number_of_chains = 100 create2_factory_salt = "0x00000000000000000000000000000000000000000000000000000000000000ff" create2_factory_addr = "0x0000000000000000000000000000000000000000" validator_timelock_execution_delay = 0 -genesis_root = "0x0000000000000000000000000000000000000000000000000000000000000000" -genesis_rollup_leaf_index = 0 -genesis_batch_commitment = "0x0000000000000000000000000000000000000000000000000000000000000000" +genesis_root = "0x1000000000000000000000000000000000000000000000000000000000000000" +genesis_rollup_leaf_index = 1 +genesis_batch_commitment = "0x1000000000000000000000000000000000000000000000000000000000000000" latest_protocol_version = 0 recursion_node_level_vk_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" recursion_leaf_level_vk_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" @@ -22,16 +23,8 @@ diamond_init_max_pubdata_per_batch = 120000 diamond_init_max_l2_gas_per_batch = 80000000 diamond_init_priority_tx_max_pubdata = 99000 diamond_init_minimal_l2_gas_price = 250000000 +bootloader_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +default_aa_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" [tokens] token_weth_address = "0x0000000000000000000000000000000000000000" - -[hyperchain] -hyperchain_chain_id = 9 -base_token_addr = "0x0000000000000000000000000000000000000001" -bridgehub_create_new_chain_salt = 0 -validium_mode = false -validator_sender_operator_commit_eth = "0x0000000000000000000000000000000000000000" -validator_sender_operator_blobs_eth = "0x0000000000000000000000000000000000000001" -base_token_gas_price_multiplier_nominator = 1 -base_token_gas_price_multiplier_denominator = 1 diff --git a/l1-contracts/deploy-script-config-template/config-deploy-paymaster.toml b/l1-contracts/deploy-script-config-template/config-deploy-paymaster.toml new file mode 100644 index 000000000..b9eb46fbf --- /dev/null +++ b/l1-contracts/deploy-script-config-template/config-deploy-paymaster.toml @@ -0,0 +1,3 @@ +chain_id = 215 +l1_shared_bridge = "0x2ae37d8130b82c7e79b3863a39027178e073eedb" +bridgehub = "0xea785a9c91a07ed69b83eb165f4ce2c30ecb4c0b" diff --git a/l1-contracts-foundry/script-config/config-initialize-l2-weth-token.toml b/l1-contracts/deploy-script-config-template/config-initialize-l2-weth-token.toml similarity index 100% rename from l1-contracts-foundry/script-config/config-initialize-l2-weth-token.toml rename to l1-contracts/deploy-script-config-template/config-initialize-l2-weth-token.toml diff --git a/l1-contracts/deploy-script-config-template/config-initialize-shared-bridges.toml b/l1-contracts/deploy-script-config-template/config-initialize-shared-bridges.toml new file mode 100644 index 000000000..67e46ae38 --- /dev/null +++ b/l1-contracts/deploy-script-config-template/config-initialize-shared-bridges.toml @@ -0,0 +1,6 @@ +chain_id = 215 +era_chain_id = 270 +l1_shared_bridge = "0x2ae37d8130b82c7e79b3863a39027178e073eedb" +bridgehub = "0xea785a9c91a07ed69b83eb165f4ce2c30ecb4c0b" +governance = "0x6a08d69675af7755569a1a25ef37e795493473a1" +erc20_bridge = "0x84fbda16bd5f2d66d7fbaec5e8d816e7b7014595" diff --git a/l1-contracts/deploy-script-config-template/register-hyperchain.toml b/l1-contracts/deploy-script-config-template/register-hyperchain.toml new file mode 100644 index 000000000..dd93f34a4 --- /dev/null +++ b/l1-contracts/deploy-script-config-template/register-hyperchain.toml @@ -0,0 +1,12 @@ +owner_address = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" +[chain] +chain_chain_id = 9 +base_token_addr = "0x0000000000000000000000000000000000000001" +bridgehub_create_new_chain_salt = 0 +validium_mode = false +validator_sender_operator_commit_eth = "0x0000000000000000000000000000000000000000" +validator_sender_operator_blobs_eth = "0x0000000000000000000000000000000000000001" +base_token_gas_price_multiplier_nominator = 1 +base_token_gas_price_multiplier_denominator = 1 +governance_min_delay = 0 +governance_security_council_address = "0x0000000000000000000000000000000000000000" diff --git a/l1-contracts/deploy-scripts/AcceptAdmin.s.sol b/l1-contracts/deploy-scripts/AcceptAdmin.s.sol new file mode 100644 index 000000000..0af30c279 --- /dev/null +++ b/l1-contracts/deploy-scripts/AcceptAdmin.s.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import {Script} from "forge-std/Script.sol"; +import {stdToml} from "forge-std/StdToml.sol"; + +import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; +import {IZkSyncHyperchain} from "contracts/state-transition/chain-interfaces/IZkSyncHyperchain.sol"; +import {Utils} from "./Utils.sol"; + +contract AcceptAdmin is Script { + using stdToml for string; + + struct Config { + address admin; + address governor; + } + + Config internal config; + + function initConfig() public { + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/script-config/config-accept-admin.toml"); + string memory toml = vm.readFile(path); + config.admin = toml.readAddress("$.target_addr"); + config.governor = toml.readAddress("$.governor"); + } + + // This function should be called by the owner to accept the admin role + function acceptOwner() public { + initConfig(); + + Ownable2Step adminContract = Ownable2Step(config.admin); + Utils.executeUpgrade({ + _governor: config.governor, + _salt: bytes32(0), + _target: config.admin, + _data: abi.encodeCall(adminContract.acceptOwnership, ()), + _value: 0, + _delay: 0 + }); + } + + // This function should be called by the owner to accept the admin role + function acceptAdmin() public { + initConfig(); + IZkSyncHyperchain adminContract = IZkSyncHyperchain(config.admin); + Utils.executeUpgrade({ + _governor: config.governor, + _salt: bytes32(0), + _target: config.admin, + _data: abi.encodeCall(adminContract.acceptAdmin, ()), + _value: 0, + _delay: 0 + }); + } +} diff --git a/l1-contracts-foundry/script/DeployErc20.s.sol b/l1-contracts/deploy-scripts/DeployErc20.s.sol similarity index 94% rename from l1-contracts-foundry/script/DeployErc20.s.sol rename to l1-contracts/deploy-scripts/DeployErc20.s.sol index 49786b7ca..522abe8b8 100644 --- a/l1-contracts-foundry/script/DeployErc20.s.sol +++ b/l1-contracts/deploy-scripts/DeployErc20.s.sol @@ -39,18 +39,24 @@ contract DeployErc20Script is Script { } function initializeConfig() internal { - // Grab config from output of l1 deployment + config.deployerAddress = msg.sender; + string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/script-config/config-deploy-erc20.toml"); - string memory toml = vm.readFile(path); - config.deployerAddress = msg.sender; + // Grab config from output of l1 deployment + string memory path = string.concat(root, "/script-out/output-deploy-l1.toml"); + string memory toml = vm.readFile(path); // Config file must be parsed key by key, otherwise values returned // are parsed alfabetically and not by key. // https://book.getfoundry.sh/cheatcodes/parse-toml config.create2FactoryAddr = vm.parseTomlAddress(toml, "$.create2_factory_addr"); config.create2FactorySalt = vm.parseTomlBytes32(toml, "$.create2_factory_salt"); + + // Grab config from custom config file + path = string.concat(root, "/script-config/config-deploy-erc20.toml"); + toml = vm.readFile(path); + string[] memory tokens = vm.parseTomlKeys(toml, "$.tokens"); uint256 tokensLength = tokens.length; @@ -115,9 +121,6 @@ contract DeployErc20Script is Script { } function saveOutput() internal { - vm.serializeAddress("root", "create2_factory_addr", config.create2FactoryAddr); - vm.serializeBytes32("root", "create2_factory_salt", config.create2FactorySalt); - string memory tokens = ""; uint256 tokensLength = config.tokens.length; for (uint256 i = 0; i < tokensLength; ++i) { diff --git a/l1-contracts-foundry/script/DeployL1.s.sol b/l1-contracts/deploy-scripts/DeployL1.s.sol similarity index 83% rename from l1-contracts-foundry/script/DeployL1.s.sol rename to l1-contracts/deploy-scripts/DeployL1.s.sol index cf20e4f7a..42921b3cb 100644 --- a/l1-contracts-foundry/script/DeployL1.s.sol +++ b/l1-contracts/deploy-scripts/DeployL1.s.sol @@ -11,6 +11,7 @@ import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transpa import {Utils} from "./Utils.sol"; import {Multicall3} from "contracts/dev-contracts/Multicall3.sol"; import {Verifier} from "contracts/state-transition/Verifier.sol"; +import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; import {VerifierParams, IVerifier} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; import {DefaultUpgrade} from "contracts/upgrades/DefaultUpgrade.sol"; import {Governance} from "contracts/governance/Governance.sol"; @@ -23,7 +24,7 @@ import {MailboxFacet} from "contracts/state-transition/chain-deps/facets/Mailbox import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; import {DiamondInit} from "contracts/state-transition/chain-deps/DiamondInit.sol"; import {StateTransitionManager} from "contracts/state-transition/StateTransitionManager.sol"; -import {StateTransitionManagerInitializeData} from "contracts/state-transition/IStateTransitionManager.sol"; +import {StateTransitionManagerInitializeData, ChainCreationParams} from "contracts/state-transition/IStateTransitionManager.sol"; import {IStateTransitionManager} from "contracts/state-transition/IStateTransitionManager.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {InitializeDataNewChain as DiamondInitializeDataNewChain} from "contracts/state-transition/chain-interfaces/IDiamondInit.sol"; @@ -86,6 +87,7 @@ contract DeployL1Script is Script { uint256 eraChainId; address deployerAddress; address ownerAddress; + bool testnetVerifier; ContractsConfig contracts; TokensConfig tokens; } @@ -112,7 +114,10 @@ contract DeployL1Script is Script { uint256 diamondInitMinimalL2GasPrice; address governanceSecurityCouncilAddress; uint256 governanceMinDelay; - uint256 maxNumberOfHyperchains; + uint256 maxNumberOfChains; + bytes diamondCutData; + bytes32 bootloaderHash; + bytes32 defaultAAHash; } struct TokensConfig { @@ -168,12 +173,13 @@ contract DeployL1Script is Script { // https://book.getfoundry.sh/cheatcodes/parse-toml config.eraChainId = toml.readUint("$.era_chain_id"); config.ownerAddress = toml.readAddress("$.owner_address"); + config.testnetVerifier = toml.readBool("$.testnet_verifier"); config.contracts.governanceSecurityCouncilAddress = toml.readAddress( "$.contracts.governance_security_council_address" ); config.contracts.governanceMinDelay = toml.readUint("$.contracts.governance_min_delay"); - config.contracts.maxNumberOfHyperchains = toml.readUint("$.contracts.max_number_of_hyperchains"); + config.contracts.maxNumberOfChains = toml.readUint("$.contracts.max_number_of_chains"); config.contracts.create2FactorySalt = toml.readBytes32("$.contracts.create2_factory_salt"); if (vm.keyExistsToml(toml, "$.contracts.create2_factory_addr")) { config.contracts.create2FactoryAddr = toml.readAddress("$.contracts.create2_factory_addr"); @@ -203,6 +209,8 @@ contract DeployL1Script is Script { "$.contracts.diamond_init_priority_tx_max_pubdata" ); config.contracts.diamondInitMinimalL2GasPrice = toml.readUint("$.contracts.diamond_init_minimal_l2_gas_price"); + config.contracts.defaultAAHash = toml.readBytes32("$.contracts.default_aa_hash"); + config.contracts.bootloaderHash = toml.readBytes32("$.contracts.bootloader_hash"); config.tokens.tokenWethAddress = toml.readAddress("$.tokens.token_weth_address"); } @@ -242,7 +250,13 @@ contract DeployL1Script is Script { } function deployVerifier() internal { - address contractAddress = deployViaCreate2(type(Verifier).creationCode); + bytes memory code; + if (config.testnetVerifier) { + code = type(TestnetVerifier).creationCode; + } else { + code = type(Verifier).creationCode; + } + address contractAddress = deployViaCreate2(code); console.log("Verifier deployed at:", contractAddress); addresses.stateTransition.verifier = contractAddress; } @@ -354,7 +368,7 @@ contract DeployL1Script is Script { bytes memory bytecode = abi.encodePacked( type(StateTransitionManager).creationCode, abi.encode(addresses.bridgehub.bridgehubProxy), - abi.encode(config.contracts.maxNumberOfHyperchains) + abi.encode(config.contracts.maxNumberOfChains) ); address contractAddress = deployViaCreate2(bytecode); console.log("StateTransitionManagerImplementation deployed at:", contractAddress); @@ -406,8 +420,8 @@ contract DeployL1Script is Script { DiamondInitializeDataNewChain memory initializeData = DiamondInitializeDataNewChain({ verifier: IVerifier(addresses.stateTransition.verifier), verifierParams: verifierParams, - l2BootloaderBytecodeHash: bytes32(Utils.getBatchBootloaderBytecodeHash()), - l2DefaultAccountBytecodeHash: bytes32(Utils.readSystemContractsBytecode("DefaultAccount")), + l2BootloaderBytecodeHash: config.contracts.bootloaderHash, + l2DefaultAccountBytecodeHash: config.contracts.defaultAAHash, priorityTxMaxGasLimit: config.contracts.priorityTxMaxGasLimit, feeParams: feeParams, blobVersionedHashRetriever: addresses.blobVersionedHashRetriever @@ -419,14 +433,20 @@ contract DeployL1Script is Script { initCalldata: abi.encode(initializeData) }); - StateTransitionManagerInitializeData memory diamondInitData = StateTransitionManagerInitializeData({ - owner: config.ownerAddress, - validatorTimelock: addresses.validatorTimelock, + config.contracts.diamondCutData = abi.encode(diamondCut); + + ChainCreationParams memory chainCreationParams = ChainCreationParams({ genesisUpgrade: addresses.stateTransition.genesisUpgrade, genesisBatchHash: config.contracts.genesisRoot, genesisIndexRepeatedStorageChanges: uint64(config.contracts.genesisRollupLeafIndex), genesisBatchCommitment: config.contracts.genesisBatchCommitment, - diamondCut: diamondCut, + diamondCut: diamondCut + }); + + StateTransitionManagerInitializeData memory diamondInitData = StateTransitionManagerInitializeData({ + owner: config.ownerAddress, + validatorTimelock: addresses.validatorTimelock, + chainCreationParams: chainCreationParams, protocolVersion: config.contracts.latestProtocolVersion }); @@ -471,7 +491,7 @@ contract DeployL1Script is Script { Diamond.DiamondCutData memory diamondCut = Diamond.DiamondCutData({ facetCuts: facetCuts, initAddress: address(0), - initCalldata: hex"" + initCalldata: "" }); bytes memory bytecode = abi.encodePacked( type(DiamondProxy).creationCode, @@ -559,7 +579,7 @@ contract DeployL1Script is Script { validatorTimelock.transferOwnership(config.ownerAddress); Bridgehub bridgehub = Bridgehub(addresses.bridgehub.bridgehubProxy); - bridgehub.transferOwnership(config.ownerAddress); + bridgehub.transferOwnership(addresses.governance); L1SharedBridge sharedBridge = L1SharedBridge(addresses.bridges.sharedBridgeProxy); sharedBridge.transferOwnership(addresses.governance); @@ -570,116 +590,125 @@ contract DeployL1Script is Script { } function saveOutput() internal { - vm.serializeAddress("l1.bridgehub", "bridgehub_proxy_addr", addresses.bridgehub.bridgehubProxy); - string memory l1Bridgehub = vm.serializeAddress( - "l1.bridgehub", + vm.serializeAddress("bridgehub", "bridgehub_proxy_addr", addresses.bridgehub.bridgehubProxy); + string memory bridgehub = vm.serializeAddress( + "bridgehub", "bridgehub_implementation_addr", addresses.bridgehub.bridgehubImplementation ); vm.serializeAddress( - "l1.state_transition", + "state_transition", "state_transition_proxy_addr", addresses.stateTransition.stateTransitionProxy ); vm.serializeAddress( - "l1.state_transition", + "state_transition", "state_transition_implementation_addr", addresses.stateTransition.stateTransitionImplementation ); - vm.serializeAddress("l1.state_transition", "verifier_addr", addresses.stateTransition.verifier); - vm.serializeAddress("l1.state_transition", "admin_facet_addr", addresses.stateTransition.adminFacet); - vm.serializeAddress("l1.state_transition", "mailbox_facet_addr", addresses.stateTransition.mailboxFacet); - vm.serializeAddress("l1.state_transition", "executor_facet_addr", addresses.stateTransition.executorFacet); - vm.serializeAddress("l1.state_transition", "getters_facet_addr", addresses.stateTransition.gettersFacet); - vm.serializeAddress("l1.state_transition", "diamond_init_addr", addresses.stateTransition.diamondInit); - vm.serializeAddress("l1.state_transition", "genesis_upgrade_addr", addresses.stateTransition.genesisUpgrade); - vm.serializeAddress("l1.state_transition", "default_upgrade_addr", addresses.stateTransition.defaultUpgrade); - string memory l1StateTransition = vm.serializeAddress( - "l1.state_transition", + vm.serializeAddress("state_transition", "verifier_addr", addresses.stateTransition.verifier); + vm.serializeAddress("state_transition", "admin_facet_addr", addresses.stateTransition.adminFacet); + vm.serializeAddress("state_transition", "mailbox_facet_addr", addresses.stateTransition.mailboxFacet); + vm.serializeAddress("state_transition", "executor_facet_addr", addresses.stateTransition.executorFacet); + vm.serializeAddress("state_transition", "getters_facet_addr", addresses.stateTransition.gettersFacet); + vm.serializeAddress("state_transition", "diamond_init_addr", addresses.stateTransition.diamondInit); + vm.serializeAddress("state_transition", "genesis_upgrade_addr", addresses.stateTransition.genesisUpgrade); + vm.serializeAddress("state_transition", "default_upgrade_addr", addresses.stateTransition.defaultUpgrade); + string memory stateTransition = vm.serializeAddress( + "state_transition", "diamond_proxy_addr", addresses.stateTransition.diamondProxy ); + vm.serializeAddress("bridges", "erc20_bridge_implementation_addr", addresses.bridges.erc20BridgeImplementation); + vm.serializeAddress("bridges", "erc20_bridge_proxy_addr", addresses.bridges.erc20BridgeProxy); vm.serializeAddress( - "l1.bridges", - "erc20_bridge_implementation_addr", - addresses.bridges.erc20BridgeImplementation - ); - vm.serializeAddress("l1.bridges", "erc20_bridge_proxy_addr", addresses.bridges.erc20BridgeProxy); - vm.serializeAddress( - "l1.bridges", + "bridges", "shared_bridge_implementation_addr", addresses.bridges.sharedBridgeImplementation ); - string memory l1Bridges = vm.serializeAddress( - "l1.bridges", + string memory bridges = vm.serializeAddress( + "bridges", "shared_bridge_proxy_addr", addresses.bridges.sharedBridgeProxy ); vm.serializeUint( - "l1.config", + "contracts_config", "diamond_init_pubdata_pricing_mode", uint256(config.contracts.diamondInitPubdataPricingMode) ); vm.serializeUint( - "l1.config", + "contracts_config", "diamond_init_batch_overhead_l1_gas", config.contracts.diamondInitBatchOverheadL1Gas ); vm.serializeUint( - "l1.config", + "contracts_config", "diamond_init_max_pubdata_per_batch", config.contracts.diamondInitMaxPubdataPerBatch ); vm.serializeUint( - "l1.config", + "contracts_config", "diamond_init_max_l2_gas_per_batch", config.contracts.diamondInitMaxL2GasPerBatch ); vm.serializeUint( - "l1.config", + "contracts_config", "diamond_init_priority_tx_max_pubdata", config.contracts.diamondInitPriorityTxMaxPubdata ); vm.serializeUint( - "l1.config", + "contracts_config", "diamond_init_minimal_l2_gas_price", config.contracts.diamondInitMinimalL2GasPrice ); - vm.serializeBytes32("l1.config", "recursion_node_level_vk_hash", config.contracts.recursionNodeLevelVkHash); - vm.serializeBytes32("l1.config", "recursion_leaf_level_vk_hash", config.contracts.recursionLeafLevelVkHash); vm.serializeBytes32( - "l1.config", + "contracts_config", + "recursion_node_level_vk_hash", + config.contracts.recursionNodeLevelVkHash + ); + vm.serializeBytes32( + "contracts_config", + "recursion_leaf_level_vk_hash", + config.contracts.recursionLeafLevelVkHash + ); + vm.serializeBytes32( + "contracts_config", "recursion_circuits_set_vks_hash", config.contracts.recursionCircuitsSetVksHash ); - string memory l1Config = vm.serializeUint( - "l1.config", - "priority_tx_max_gas_limit", - config.contracts.priorityTxMaxGasLimit - ); - - vm.serializeAddress("l1", "transparent_proxy_admin_addr", addresses.transparentProxyAdmin); - vm.serializeAddress("l1", "governance_addr", addresses.governance); - vm.serializeAddress("l1", "blob_versioned_hash_retriever_addr", addresses.blobVersionedHashRetriever); - vm.serializeAddress("l1", "validator_timelock_addr", addresses.validatorTimelock); - vm.serializeAddress("l1", "create2_factory_addr", addresses.create2Factory); - vm.serializeBytes32("l1", "create2_factory_salt", config.contracts.create2FactorySalt); - vm.serializeAddress("l1", "multicall3_addr", config.contracts.multicall3Addr); - vm.serializeUint("l1", "l1_chain_id", config.l1ChainId); - vm.serializeUint("l1", "era_chain_id", config.eraChainId); - vm.serializeString("l1", "bridgehub", l1Bridgehub); - vm.serializeString("l1", "state_transition", l1StateTransition); - vm.serializeString("l1", "config", l1Config); - vm.serializeAddress("l1", "deployer_addr", config.deployerAddress); - vm.serializeAddress("l1", "owner_addr", config.ownerAddress); - string memory l1 = vm.serializeString("l1", "bridges", l1Bridges); - - string memory toml = vm.serializeString("toml", "l1", l1); - string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/script-out/output-deploy-l1.toml"); + vm.serializeUint("contracts_config", "priority_tx_max_gas_limit", config.contracts.priorityTxMaxGasLimit); + string memory contractsConfig = vm.serializeBytes( + "contracts_config", + "diamond_cut_data", + config.contracts.diamondCutData + ); + + vm.serializeAddress("deployed_addresses", "transparent_proxy_admin_addr", addresses.transparentProxyAdmin); + vm.serializeAddress("deployed_addresses", "governance_addr", addresses.governance); + vm.serializeAddress( + "deployed_addresses", + "blob_versioned_hash_retriever_addr", + addresses.blobVersionedHashRetriever + ); + vm.serializeAddress("deployed_addresses", "validator_timelock_addr", addresses.validatorTimelock); + vm.serializeString("deployed_addresses", "bridgehub", bridgehub); + vm.serializeString("deployed_addresses", "state_transition", stateTransition); + string memory deployedAddresses = vm.serializeString("deployed_addresses", "bridges", bridges); + + vm.serializeAddress("root", "create2_factory_addr", addresses.create2Factory); + vm.serializeBytes32("root", "create2_factory_salt", config.contracts.create2FactorySalt); + vm.serializeAddress("root", "multicall3_addr", config.contracts.multicall3Addr); + vm.serializeUint("root", "l1_chain_id", config.l1ChainId); + vm.serializeUint("root", "era_chain_id", config.eraChainId); + vm.serializeAddress("root", "deployer_addr", config.deployerAddress); + vm.serializeString("root", "deployed_addresses", deployedAddresses); + vm.serializeString("root", "contracts_config", contractsConfig); + string memory toml = vm.serializeAddress("root", "owner_addr", config.ownerAddress); + + string memory path = string.concat(vm.projectRoot(), "/script-out/output-deploy-l1.toml"); vm.writeToml(toml, path); } diff --git a/l1-contracts/deploy-scripts/DeployPaymaster.s.sol b/l1-contracts/deploy-scripts/DeployPaymaster.s.sol new file mode 100644 index 000000000..f7115a479 --- /dev/null +++ b/l1-contracts/deploy-scripts/DeployPaymaster.s.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {Script} from "forge-std/Script.sol"; +import {stdToml} from "forge-std/StdToml.sol"; + +import {Utils} from "./Utils.sol"; + +contract DeployPaymaster is Script { + using stdToml for string; + Config internal config; + + // solhint-disable-next-line gas-struct-packing + struct Config { + address bridgehubAddress; + address l1SharedBridgeProxy; + uint256 chainId; + address paymaster; + } + + function initializeConfig() internal { + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/script-config/config-deploy-paymaster.toml"); + string memory toml = vm.readFile(path); + config.bridgehubAddress = toml.readAddress("$.bridgehub"); + config.l1SharedBridgeProxy = toml.readAddress("$.l1_shared_bridge"); + config.chainId = toml.readUint("$.chain_id"); + } + + function saveOutput() internal { + string memory toml = vm.serializeAddress("root", "paymaster", config.paymaster); + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/script-out/output-deploy-paymaster.toml"); + vm.writeToml(toml, path); + } + + function run() external { + initializeConfig(); + + deploy(); + + saveOutput(); + } + + function deploy() internal { + bytes memory testnetPaymasterBytecode = Utils.readHardhatBytecode( + "/../l2-contracts/artifacts-zk/contracts/TestnetPaymaster.sol/TestnetPaymaster.json" + ); + + config.paymaster = Utils.deployThroughL1({ + bytecode: testnetPaymasterBytecode, + constructorargs: "", + create2salt: "", + l2GasLimit: Utils.MAX_PRIORITY_TX_GAS, + factoryDeps: new bytes[](0), + bridgehubAddress: config.bridgehubAddress, + l1SharedBridgeProxy: config.l1SharedBridgeProxy, + chainId: config.chainId + }); + } +} diff --git a/l1-contracts-foundry/script/InitializeL2WethToken.s.sol b/l1-contracts/deploy-scripts/InitializeL2WethToken.s.sol similarity index 92% rename from l1-contracts-foundry/script/InitializeL2WethToken.s.sol rename to l1-contracts/deploy-scripts/InitializeL2WethToken.s.sol index a147e3d00..815008317 100644 --- a/l1-contracts-foundry/script/InitializeL2WethToken.s.sol +++ b/l1-contracts/deploy-scripts/InitializeL2WethToken.s.sol @@ -47,10 +47,10 @@ contract InitializeL2WethTokenScript is Script { string memory path = string.concat(root, "/script-out/output-deploy-l1.toml"); string memory toml = vm.readFile(path); - config.create2FactoryAddr = toml.readAddress("$.l1.create2_factory_addr"); - config.create2FactorySalt = toml.readBytes32("$.l1.create2_factory_salt"); - config.eraChainId = toml.readUint("$.l1.era_chain_id"); - config.bridgehubProxyAddr = toml.readAddress("$.l1.bridgehub.bridgehub_proxy_addr"); + config.create2FactoryAddr = toml.readAddress("$.create2_factory_addr"); + config.create2FactorySalt = toml.readBytes32("$.create2_factory_salt"); + config.eraChainId = toml.readUint("$.era_chain_id"); + config.bridgehubProxyAddr = toml.readAddress("$.deployed_addresses.bridgehub.bridgehub_proxy_addr"); // Parse some config from output of erc20 tokens deployment path = string.concat(root, "/script-out/output-deploy-erc20.toml"); @@ -103,7 +103,7 @@ contract InitializeL2WethTokenScript is Script { console.log("L2 WETH token initialized"); } - function getL2Calldata() internal returns (bytes memory) { + function getL2Calldata() internal view returns (bytes memory) { // Low-level call is performed due to different solidity // compiler versions between L1 and L2 // solhint-disable-next-line func-named-parameters diff --git a/l1-contracts/deploy-scripts/InitializeSharedBridgeOnL2.sol b/l1-contracts/deploy-scripts/InitializeSharedBridgeOnL2.sol new file mode 100644 index 000000000..344ab59a1 --- /dev/null +++ b/l1-contracts/deploy-scripts/InitializeSharedBridgeOnL2.sol @@ -0,0 +1,159 @@ +pragma solidity ^0.8.24; + +import {Script} from "forge-std/Script.sol"; +import {stdToml} from "forge-std/StdToml.sol"; + +import {Utils} from "./Utils.sol"; +import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {L1SharedBridge} from "contracts/bridge/L1SharedBridge.sol"; + +contract DeployL2Script is Script { + using stdToml for string; + + Config internal config; + ContractsBytecodes internal contracts; + + // solhint-disable-next-line gas-struct-packing + struct Config { + address bridgehubAddress; + address l1SharedBridgeProxy; + address governance; + address erc20BridgeProxy; + uint256 chainId; + uint256 eraChainId; + address l2SharedBridgeImplementation; + address l2SharedBridgeProxy; + } + + struct ContractsBytecodes { + bytes l2StandardErc20FactoryBytecode; + bytes beaconProxy; + bytes l2StandardErc20Bytecode; + bytes l2SharedBridgeBytecode; + bytes l2SharedBridgeProxyBytecode; + } + + function run() public { + initializeConfig(); + loadContracts(); + + deployFactoryDeps(); + deploySharedBridge(); + deploySharedBridgeProxy(); + initializeChain(); + + saveOutput(); + } + + function loadContracts() internal { + //HACK: Meanwhile we are not integrated foundry zksync we use contracts that has been built using hardhat + contracts.l2StandardErc20FactoryBytecode = Utils.readHardhatBytecode( + "/../l2-contracts/artifacts-zk/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json" + ); + contracts.beaconProxy = Utils.readHardhatBytecode( + "/../l2-contracts/artifacts-zk/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.json" + ); + contracts.l2StandardErc20Bytecode = Utils.readHardhatBytecode( + "/../l2-contracts/artifacts-zk/contracts/bridge/L2StandardERC20.sol/L2StandardERC20.json" + ); + + contracts.l2SharedBridgeBytecode = Utils.readHardhatBytecode( + "/../l2-contracts/artifacts-zk/contracts/bridge/L2SharedBridge.sol/L2SharedBridge.json" + ); + + contracts.l2SharedBridgeProxyBytecode = Utils.readHardhatBytecode( + "/../l2-contracts/artifacts-zk/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json" + ); + } + + function initializeConfig() internal { + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/script-config/config-initialize-shared-bridges.toml"); + string memory toml = vm.readFile(path); + config.bridgehubAddress = toml.readAddress("$.bridgehub"); + config.governance = toml.readAddress("$.governance"); + config.l1SharedBridgeProxy = toml.readAddress("$.l1_shared_bridge"); + config.erc20BridgeProxy = toml.readAddress("$.erc20_bridge"); + config.chainId = toml.readUint("$.chain_id"); + config.eraChainId = toml.readUint("$.era_chain_id"); + } + + function saveOutput() internal { + vm.serializeAddress("root", "l2_shared_bridge_implementation", config.l2SharedBridgeImplementation); + string memory toml = vm.serializeAddress("root", "l2_shared_bridge_proxy", config.l2SharedBridgeProxy); + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/script-out/output-initialize-shared-bridges.toml"); + vm.writeToml(toml, path); + } + + function deployFactoryDeps() public { + bytes[] memory factoryDeps = new bytes[](3); + factoryDeps[0] = contracts.l2StandardErc20FactoryBytecode; + factoryDeps[1] = contracts.l2StandardErc20Bytecode; + factoryDeps[2] = contracts.beaconProxy; + Utils.publishBytecodes(factoryDeps, config.chainId, config.bridgehubAddress, config.l1SharedBridgeProxy); + } + + function deploySharedBridge() public { + bytes[] memory factoryDeps = new bytes[](1); + factoryDeps[0] = contracts.beaconProxy; + + bytes memory constructorData = abi.encode(config.eraChainId); + + config.l2SharedBridgeImplementation = Utils.deployThroughL1({ + bytecode: contracts.l2SharedBridgeBytecode, + constructorargs: constructorData, + create2salt: "", + l2GasLimit: Utils.MAX_PRIORITY_TX_GAS, + factoryDeps: factoryDeps, + chainId: config.chainId, + bridgehubAddress: config.bridgehubAddress, + l1SharedBridgeProxy: config.l1SharedBridgeProxy + }); + } + + function deploySharedBridgeProxy() public { + address l2GovernorAddress = AddressAliasHelper.applyL1ToL2Alias(config.governance); + bytes32 l2StandardErc20BytecodeHash = L2ContractHelper.hashL2Bytecode(contracts.beaconProxy); + + // solhint-disable-next-line func-named-parameters + bytes memory proxyInitializationParams = abi.encodeWithSignature( + "initialize(address,address,bytes32,address)", + config.l1SharedBridgeProxy, + config.erc20BridgeProxy, + l2StandardErc20BytecodeHash, + l2GovernorAddress + ); + + bytes memory l2SharedBridgeProxyConstructorData = abi.encode( + config.l2SharedBridgeImplementation, + l2GovernorAddress, + proxyInitializationParams + ); + + config.l2SharedBridgeProxy = Utils.deployThroughL1({ + bytecode: contracts.l2SharedBridgeProxyBytecode, + constructorargs: l2SharedBridgeProxyConstructorData, + create2salt: "", + l2GasLimit: Utils.MAX_PRIORITY_TX_GAS, + factoryDeps: new bytes[](0), + chainId: config.chainId, + bridgehubAddress: config.bridgehubAddress, + l1SharedBridgeProxy: config.l1SharedBridgeProxy + }); + } + + function initializeChain() public { + L1SharedBridge bridge = L1SharedBridge(config.l1SharedBridgeProxy); + + Utils.executeUpgrade({ + _governor: bridge.owner(), + _salt: bytes32(0), + _target: config.l1SharedBridgeProxy, + _data: abi.encodeCall(bridge.initializeChainGovernance, (config.chainId, config.l2SharedBridgeProxy)), + _value: 0, + _delay: 0 + }); + } +} diff --git a/l1-contracts/deploy-scripts/RegisterHyperchain.s.sol b/l1-contracts/deploy-scripts/RegisterHyperchain.s.sol new file mode 100644 index 000000000..6da3e5d0b --- /dev/null +++ b/l1-contracts/deploy-scripts/RegisterHyperchain.s.sol @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +// solhint-disable no-console, gas-custom-errors, reason-string + +import {Script, console2 as console} from "forge-std/Script.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {stdToml} from "forge-std/StdToml.sol"; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {IZkSyncHyperchain} from "contracts/state-transition/chain-interfaces/IZkSyncHyperchain.sol"; +import {ValidatorTimelock} from "contracts/state-transition/ValidatorTimelock.sol"; +import {Governance} from "contracts/governance/Governance.sol"; +import {Utils} from "./Utils.sol"; +import {PubdataPricingMode} from "contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol"; + +contract RegisterHyperchainScript is Script { + using stdToml for string; + + address internal constant ADDRESS_ONE = 0x0000000000000000000000000000000000000001; + bytes32 internal constant STATE_TRANSITION_NEW_CHAIN_HASH = keccak256("NewHyperchain(uint256,address)"); + + // solhint-disable-next-line gas-struct-packing + struct Config { + address deployerAddress; + address ownerAddress; + uint256 chainChainId; + bool validiumMode; + uint256 bridgehubCreateNewChainSalt; + address validatorSenderOperatorCommitEth; + address validatorSenderOperatorBlobsEth; + address baseToken; + uint128 baseTokenGasPriceMultiplierNominator; + uint128 baseTokenGasPriceMultiplierDenominator; + address bridgehub; + address stateTransitionProxy; + address validatorTimelock; + bytes diamondCutData; + address governanceSecurityCouncilAddress; + uint256 governanceMinDelay; + address newDiamondProxy; + address governance; + } + + Config internal config; + + function run() public { + console.log("Deploying Hyperchain"); + + initializeConfig(); + + deployGovernance(); + checkTokenAddress(); + registerTokenOnBridgehub(); + registerHyperchain(); + addValidators(); + configureZkSyncStateTransition(); + setPendingAdmin(); + + saveOutput(); + } + + function initializeConfig() internal { + // Grab config from output of l1 deployment + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/script-config/register-hyperchain.toml"); + string memory toml = vm.readFile(path); + + config.deployerAddress = msg.sender; + + // Config file must be parsed key by key, otherwise values returned + // are parsed alfabetically and not by key. + // https://book.getfoundry.sh/cheatcodes/parse-toml + config.ownerAddress = toml.readAddress("$.owner_address"); + + config.bridgehub = toml.readAddress("$.deployed_addresses.bridgehub.bridgehub_proxy_addr"); + config.stateTransitionProxy = toml.readAddress( + "$.deployed_addresses.state_transition.state_transition_proxy_addr" + ); + config.validatorTimelock = toml.readAddress("$.deployed_addresses.validator_timelock_addr"); + + config.diamondCutData = toml.readBytes("$.contracts_config.diamond_cut_data"); + + config.chainChainId = toml.readUint("$.chain.chain_chain_id"); + config.bridgehubCreateNewChainSalt = toml.readUint("$.chain.bridgehub_create_new_chain_salt"); + config.baseToken = toml.readAddress("$.chain.base_token_addr"); + config.validiumMode = toml.readBool("$.chain.validium_mode"); + config.validatorSenderOperatorCommitEth = toml.readAddress("$.chain.validator_sender_operator_commit_eth"); + config.validatorSenderOperatorBlobsEth = toml.readAddress("$.chain.validator_sender_operator_blobs_eth"); + config.baseTokenGasPriceMultiplierNominator = uint128( + toml.readUint("$.chain.base_token_gas_price_multiplier_nominator") + ); + config.baseTokenGasPriceMultiplierDenominator = uint128( + toml.readUint("$.chain.base_token_gas_price_multiplier_denominator") + ); + config.governanceMinDelay = uint256(toml.readUint("$.chain.governance_min_delay")); + config.governanceSecurityCouncilAddress = toml.readAddress("$.chain.governance_security_council_address"); + } + + function checkTokenAddress() internal view { + if (config.baseToken == address(0)) { + revert("Token address is not set"); + } + + // Check if it's ethereum address + if (config.baseToken == ADDRESS_ONE) { + return; + } + + if (config.baseToken.code.length == 0) { + revert("Token address is not a contract address"); + } + + console.log("Using base token address:", config.baseToken); + } + + function registerTokenOnBridgehub() internal { + IBridgehub bridgehub = IBridgehub(config.bridgehub); + Ownable ownable = Ownable(config.bridgehub); + + if (bridgehub.tokenIsRegistered(config.baseToken)) { + console.log("Token already registered on Bridgehub"); + } else { + bytes memory data = abi.encodeCall(bridgehub.addToken, (config.baseToken)); + Utils.executeUpgrade({ + _governor: ownable.owner(), + _salt: bytes32(config.bridgehubCreateNewChainSalt), + _target: config.bridgehub, + _data: data, + _value: 0, + _delay: 0 + }); + console.log("Token registered on Bridgehub"); + } + } + + function deployGovernance() internal { + vm.broadcast(); + Governance governance = new Governance( + config.ownerAddress, + config.governanceSecurityCouncilAddress, + config.governanceMinDelay + ); + console.log("Governance deployed at:", address(governance)); + config.governance = address(governance); + } + + function registerHyperchain() internal { + IBridgehub bridgehub = IBridgehub(config.bridgehub); + Ownable ownable = Ownable(config.bridgehub); + + vm.recordLogs(); + bytes memory data = abi.encodeCall( + bridgehub.createNewChain, + ( + config.chainChainId, + config.stateTransitionProxy, + config.baseToken, + config.bridgehubCreateNewChainSalt, + msg.sender, + config.diamondCutData + ) + ); + + Utils.executeUpgrade({ + _governor: ownable.owner(), + _salt: bytes32(config.bridgehubCreateNewChainSalt), + _target: config.bridgehub, + _data: data, + _value: 0, + _delay: 0 + }); + console.log("Hyperchain registered"); + + // Get new diamond proxy address from emitted events + Vm.Log[] memory logs = vm.getRecordedLogs(); + address diamondProxyAddress; + uint256 logsLength = logs.length; + for (uint256 i = 0; i < logsLength; ++i) { + if (logs[i].topics[0] == STATE_TRANSITION_NEW_CHAIN_HASH) { + diamondProxyAddress = address(uint160(uint256(logs[i].topics[2]))); + break; + } + } + if (diamondProxyAddress == address(0)) { + revert("Diamond proxy address not found"); + } + config.newDiamondProxy = diamondProxyAddress; + console.log("Hyperchain diamond proxy deployed at:", diamondProxyAddress); + } + + function addValidators() internal { + ValidatorTimelock validatorTimelock = ValidatorTimelock(config.validatorTimelock); + + vm.startBroadcast(); + validatorTimelock.addValidator(config.chainChainId, config.validatorSenderOperatorCommitEth); + validatorTimelock.addValidator(config.chainChainId, config.validatorSenderOperatorBlobsEth); + vm.stopBroadcast(); + + console.log("Validators added"); + } + + function configureZkSyncStateTransition() internal { + IZkSyncHyperchain hyperchain = IZkSyncHyperchain(config.newDiamondProxy); + + vm.startBroadcast(); + hyperchain.setTokenMultiplier( + config.baseTokenGasPriceMultiplierNominator, + config.baseTokenGasPriceMultiplierDenominator + ); + + if (config.validiumMode) { + hyperchain.setPubdataPricingMode(PubdataPricingMode.Validium); + } + + vm.stopBroadcast(); + console.log("ZkSync State Transition configured"); + } + + function setPendingAdmin() internal { + IZkSyncHyperchain hyperchain = IZkSyncHyperchain(config.newDiamondProxy); + + vm.broadcast(); + hyperchain.setPendingAdmin(config.governance); + console.log("Owner for ", config.newDiamondProxy, "set to", config.governance); + } + + function saveOutput() internal { + vm.serializeAddress("root", "diamond_proxy_addr", config.newDiamondProxy); + string memory toml = vm.serializeAddress("root", "governance_addr", config.governance); + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/script-out/output-register-hyperchain.toml"); + vm.writeToml(toml, path); + } +} diff --git a/l1-contracts/deploy-scripts/Utils.sol b/l1-contracts/deploy-scripts/Utils.sol new file mode 100644 index 000000000..318be6da3 --- /dev/null +++ b/l1-contracts/deploy-scripts/Utils.sol @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +// solhint-disable gas-custom-errors, reason-string + +import {Vm} from "forge-std/Vm.sol"; + +import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; +import {L2TransactionRequestDirect} from "contracts/bridgehub/IBridgehub.sol"; +import {IGovernance} from "contracts/governance/IGovernance.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA} from "contracts/common/Config.sol"; +import {L2_DEPLOYER_SYSTEM_CONTRACT_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; + +library Utils { + // Cheatcodes address, 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D. + address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); + Vm internal constant vm = Vm(VM_ADDRESS); + // Create2Factory deterministic bytecode. + // https://github.com/Arachnid/deterministic-deployment-proxy + bytes internal constant CREATE2_FACTORY_BYTECODE = + hex"604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"; + + address internal constant ADDRESS_ONE = 0x0000000000000000000000000000000000000001; + uint256 internal constant MAX_PRIORITY_TX_GAS = 72000000; + + /** + * @dev Get all selectors from the bytecode. + * + * Selectors are extracted by calling `cast selectors ` from foundry. + * Then, the result is parsed to extract the selectors, removing + * the `getName()` selector if existing. + */ + function getAllSelectors(bytes memory bytecode) internal returns (bytes4[] memory) { + string[] memory input = new string[](3); + input[0] = "cast"; + input[1] = "selectors"; + input[2] = vm.toString(bytecode); + bytes memory result = vm.ffi(input); + string memory stringResult = string(abi.encodePacked(result)); + + // Extract selectors from the result + string[] memory parts = vm.split(stringResult, "\n"); + uint256 partsLength = parts.length; + bytes4[] memory selectors = new bytes4[](partsLength); + for (uint256 i = 0; i < partsLength; ++i) { + bytes memory part = bytes(parts[i]); + bytes memory extractedSelector = new bytes(10); + // Selector length 10 is 0x + 4 bytes + for (uint256 j = 0; j < 10; ++j) { + extractedSelector[j] = part[j]; + } + bytes4 selector = bytes4(vm.parseBytes(string(extractedSelector))); + selectors[i] = selector; + } + + // Remove `getName()` selector if existing + bool hasGetName = false; + uint256 selectorsLength = selectors.length; + for (uint256 i = 0; i < selectorsLength; ++i) { + if (selectors[i] == bytes4(keccak256("getName()"))) { + selectors[i] = selectors[selectors.length - 1]; + hasGetName = true; + break; + } + } + if (hasGetName) { + bytes4[] memory newSelectors = new bytes4[](selectorsLength - 1); + for (uint256 i = 0; i < selectorsLength - 1; ++i) { + newSelectors[i] = selectors[i]; + } + return newSelectors; + } + + return selectors; + } + + /** + * @dev Extract an address from bytes. + */ + function bytesToAddress(bytes memory bys) internal pure returns (address addr) { + assembly { + addr := mload(add(bys, 20)) + } + } + + /** + * @dev Extract a uint256 from bytes. + */ + function bytesToUint256(bytes memory bys) internal pure returns (uint256 value) { + // Add left padding to 32 bytes if needed + uint256 bysLength = bys.length; + if (bysLength < 32) { + bytes memory padded = new bytes(32); + for (uint256 i = 0; i < bysLength; ++i) { + padded[i + 32 - bysLength] = bys[i]; + } + bys = padded; + } + + assembly { + value := mload(add(bys, 0x20)) + } + } + + /** + * @dev Returns the bytecode hash of the batch bootloader. + */ + function getBatchBootloaderBytecodeHash() internal view returns (bytes memory) { + return vm.readFileBinary("../system-contracts/bootloader/build/artifacts/proved_batch.yul.zbin"); + } + + /** + * @dev Returns the bytecode of a given system contract. + */ + function readSystemContractsBytecode(string memory filename) internal view returns (bytes memory) { + string memory file = vm.readFile( + // solhint-disable-next-line func-named-parameters + string.concat( + "../system-contracts/artifacts-zk/contracts-preprocessed/", + filename, + ".sol/", + filename, + ".json" + ) + ); + bytes memory bytecode = vm.parseJson(file, "$.bytecode"); + return bytecode; + } + + /** + * @dev Deploy a Create2Factory contract. + */ + function deployCreate2Factory() internal returns (address) { + address child; + bytes memory bytecode = CREATE2_FACTORY_BYTECODE; + vm.startBroadcast(); + assembly { + child := create(0, add(bytecode, 0x20), mload(bytecode)) + } + vm.stopBroadcast(); + require(child != address(0), "Failed to deploy Create2Factory"); + require(child.code.length > 0, "Failed to deploy Create2Factory"); + return child; + } + + /** + * @dev Deploys contract using CREATE2. + */ + function deployViaCreate2(bytes memory _bytecode, bytes32 _salt, address _factory) internal returns (address) { + if (_bytecode.length == 0) { + revert("Bytecode is not set"); + } + address contractAddress = vm.computeCreate2Address(_salt, keccak256(_bytecode), _factory); + if (contractAddress.code.length != 0) { + return contractAddress; + } + + vm.broadcast(); + (bool success, bytes memory data) = _factory.call(abi.encodePacked(_salt, _bytecode)); + contractAddress = bytesToAddress(data); + + if (!success || contractAddress == address(0) || contractAddress.code.length == 0) { + revert("Failed to deploy contract via create2"); + } + + return contractAddress; + } + + /** + * @dev Deploy l2 contracts through l1 + */ + function deployThroughL1( + bytes memory bytecode, + bytes memory constructorargs, + bytes32 create2salt, + uint256 l2GasLimit, + bytes[] memory factoryDeps, + uint256 chainId, + address bridgehubAddress, + address l1SharedBridgeProxy + ) internal returns (address) { + bytes32 bytecodeHash = L2ContractHelper.hashL2Bytecode(bytecode); + + bytes memory deployData = abi.encodeWithSignature( + "create2(bytes32,bytes32,bytes)", + create2salt, + bytecodeHash, + constructorargs + ); + + address contractAddress = L2ContractHelper.computeCreate2Address( + msg.sender, + create2salt, + bytecodeHash, + keccak256(constructorargs) + ); + + uint256 factoryDepsLength = factoryDeps.length; + + bytes[] memory _factoryDeps = new bytes[](factoryDepsLength + 1); + + for (uint256 i = 0; i < factoryDepsLength; ++i) { + _factoryDeps[i] = factoryDeps[i]; + } + _factoryDeps[factoryDepsLength] = bytecode; + + runL1L2Transaction({ + l2Calldata: deployData, + l2GasLimit: l2GasLimit, + factoryDeps: _factoryDeps, + dstAddress: L2_DEPLOYER_SYSTEM_CONTRACT_ADDR, + chainId: chainId, + bridgehubAddress: bridgehubAddress, + l1SharedBridgeProxy: l1SharedBridgeProxy + }); + return contractAddress; + } + + /** + * @dev Run the l2 l1 transaction + */ + function runL1L2Transaction( + bytes memory l2Calldata, + uint256 l2GasLimit, + bytes[] memory factoryDeps, + address dstAddress, + uint256 chainId, + address bridgehubAddress, + address l1SharedBridgeProxy + ) internal { + Bridgehub bridgehub = Bridgehub(bridgehubAddress); + uint256 gasPrice = bytesToUint256(vm.rpc("eth_gasPrice", "[]")); + + uint256 requiredValueToDeploy = bridgehub.l2TransactionBaseCost( + chainId, + gasPrice, + l2GasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA + ) * 2; + + L2TransactionRequestDirect memory l2TransactionRequestDirect = L2TransactionRequestDirect({ + chainId: chainId, + mintValue: requiredValueToDeploy, + l2Contract: dstAddress, + l2Value: 0, + l2Calldata: l2Calldata, + l2GasLimit: l2GasLimit, + l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + factoryDeps: factoryDeps, + refundRecipient: msg.sender + }); + + address baseTokenAddress = bridgehub.baseToken(chainId); + if (ADDRESS_ONE != baseTokenAddress) { + IERC20 baseToken = IERC20(baseTokenAddress); + vm.broadcast(); + baseToken.approve(l1SharedBridgeProxy, requiredValueToDeploy); + requiredValueToDeploy = 0; + } + + vm.broadcast(); + bridgehub.requestL2TransactionDirect{value: requiredValueToDeploy}(l2TransactionRequestDirect); + } + + /** + * @dev Publish bytecodes to l2 through l1 + */ + function publishBytecodes( + bytes[] memory factoryDeps, + uint256 chainId, + address bridgehubAddress, + address l1SharedBridgeProxy + ) internal { + runL1L2Transaction({ + l2Calldata: "", + l2GasLimit: MAX_PRIORITY_TX_GAS, + factoryDeps: factoryDeps, + dstAddress: 0x0000000000000000000000000000000000000000, + chainId: chainId, + bridgehubAddress: bridgehubAddress, + l1SharedBridgeProxy: l1SharedBridgeProxy + }); + } + + /** + * @dev Read hardhat bytecodes + */ + function readHardhatBytecode(string memory artifactPath) internal view returns (bytes memory) { + string memory root = vm.projectRoot(); + string memory path = string.concat(root, artifactPath); + string memory json = vm.readFile(path); + bytes memory bytecode = vm.parseJsonBytes(json, ".bytecode"); + return bytecode; + } + + function executeUpgrade( + address _governor, + bytes32 _salt, + address _target, + bytes memory _data, + uint256 _value, + uint256 _delay + ) internal { + IGovernance governance = IGovernance(_governor); + + IGovernance.Call[] memory calls = new IGovernance.Call[](1); + calls[0] = IGovernance.Call({target: _target, value: _value, data: _data}); + + IGovernance.Operation memory operation = IGovernance.Operation({ + calls: calls, + predecessor: bytes32(0), + salt: _salt + }); + + vm.startBroadcast(); + governance.scheduleTransparent(operation, _delay); + if (_delay == 0) { + governance.execute{value: _value}(operation); + } + vm.stopBroadcast(); + } +} diff --git a/l1-contracts-foundry/script/ZkSyncScriptErrors.sol b/l1-contracts/deploy-scripts/ZkSyncScriptErrors.sol similarity index 100% rename from l1-contracts-foundry/script/ZkSyncScriptErrors.sol rename to l1-contracts/deploy-scripts/ZkSyncScriptErrors.sol diff --git a/l1-contracts/foundry.toml b/l1-contracts/foundry.toml index 97b165ced..6f29a31cc 100644 --- a/l1-contracts/foundry.toml +++ b/l1-contracts/foundry.toml @@ -2,6 +2,20 @@ src = 'contracts' out = 'out' libs = ['node_modules', 'lib'] +remappings = [ + "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", + "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/", + "l2-contracts/=../l2-contracts/contracts/" +] +allow_paths = ["../l2-contracts/contracts"] +fs_permissions = [ + { access = "read", path = "../system-contracts/bootloader/build/artifacts" }, + { access = "read", path = "../system-contracts/artifacts-zk/contracts-preprocessed" }, + { access = "read", path = "../l2-contracts/artifacts-zk/" }, + { access = "read", path = "./script-config" }, + { access = "read-write", path = "./script-out" }, + { access = "read", path = "./out" } +] cache_path = 'cache-forge' test = 'test/foundry' solc_version = "0.8.24" diff --git a/l1-contracts/lib/forge-std b/l1-contracts/lib/forge-std index 705263c95..52715a217 160000 --- a/l1-contracts/lib/forge-std +++ b/l1-contracts/lib/forge-std @@ -1 +1 @@ -Subproject commit 705263c95892a906d7af65f0f73ce8a4a0c80b80 +Subproject commit 52715a217dc51d0de15877878ab8213f6cbbbab5 diff --git a/l1-contracts-foundry/lib/openzeppelin-contracts b/l1-contracts/lib/openzeppelin-contracts similarity index 100% rename from l1-contracts-foundry/lib/openzeppelin-contracts rename to l1-contracts/lib/openzeppelin-contracts diff --git a/l1-contracts/lib/openzeppelin-contracts-upgradeable b/l1-contracts/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 000000000..2d081f24c --- /dev/null +++ b/l1-contracts/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit 2d081f24cac1a867f6f73d512f2022e1fa987854 diff --git a/l1-contracts/package.json b/l1-contracts/package.json index 51e702943..e7dc65632 100644 --- a/l1-contracts/package.json +++ b/l1-contracts/package.json @@ -78,7 +78,10 @@ "hyperchain-upgrade-1": "ts-node scripts/hyperchain-upgrade-1.ts", "hyperchain-upgrade-2": "ts-node scripts/hyperchain-upgrade-2.ts", "hyperchain-upgrade-3": "ts-node scripts/hyperchain-upgrade-3.ts", - "setup-legacy-bridge-era": "ts-node scripts/setup-legacy-bridge-era.ts" + "token-migration": "ts-node scripts/token-migration.ts", + "setup-legacy-bridge-era": "ts-node scripts/setup-legacy-bridge-era.ts", + "upgrade-consistency-checker": "ts-node scripts/upgrade-consistency-checker.ts", + "governance-accept-ownership": "ts-node scripts/governance-accept-ownership.ts" }, "dependencies": { "dotenv": "^16.0.3", diff --git a/l1-contracts/remappings.txt b/l1-contracts/remappings.txt index fa456851c..d866a0c22 100644 --- a/l1-contracts/remappings.txt +++ b/l1-contracts/remappings.txt @@ -1,5 +1,4 @@ @ensdomains/=node_modules/@ensdomains/ -@openzeppelin/=node_modules/@openzeppelin/ ds-test/=lib/forge-std/lib/ds-test/src/ eth-gas-reporter/=node_modules/eth-gas-reporter/ forge-std/=lib/forge-std/src/ diff --git a/l1-contracts-foundry/script-out/.gitkeep b/l1-contracts/script-out/.gitkeep similarity index 100% rename from l1-contracts-foundry/script-out/.gitkeep rename to l1-contracts/script-out/.gitkeep diff --git a/l1-contracts/scripts/governance-accept-ownership.ts b/l1-contracts/scripts/governance-accept-ownership.ts new file mode 100644 index 000000000..1c366f684 --- /dev/null +++ b/l1-contracts/scripts/governance-accept-ownership.ts @@ -0,0 +1,141 @@ +/// This script is needed to migrate the ownership for key contracts to the governance multisig + +// hardhat import should be the first import in the file +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import * as hardhat from "hardhat"; +import { Command } from "commander"; +import { Wallet, ethers } from "ethers"; +import { formatUnits, parseUnits, Interface } from "ethers/lib/utils"; +import { web3Provider, GAS_MULTIPLIER } from "./utils"; +import { ethTestConfig } from "../src.ts/utils"; + +const ownable2StepInterface = new Interface(hardhat.artifacts.readArtifactSync("ValidatorTimelock").abi); +const governanceInterface = new Interface(hardhat.artifacts.readArtifactSync("Governance").abi); + +const provider = web3Provider(); + +async function main() { + const program = new Command(); + + program.version("0.1.0").name("upgrade-shared-bridge-era").description("upgrade shared bridge for era diamond proxy"); + + program + .command("transfer-ownership") + .option("--private-key ") + .option("--gas-price ") + .option("--nonce ") + .option("--owner-address ") + .option("--validator-timelock-addr ") + .option("--stm-addr ") + .option("--l1-shared-bridge-addr ") + .option("--bridgehub-addr ") + .option("--proxy-admin-addr ") + .option("--only-verifier") + .action(async (cmd) => { + const deployWallet = cmd.privateKey + ? new Wallet(cmd.privateKey, provider) + : Wallet.fromMnemonic( + process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, + "m/44'/60'/0'/0/1" + ).connect(provider); + console.log(`Using deployer wallet: ${deployWallet.address}`); + + const ownerAddress = ethers.utils.getAddress(cmd.ownerAddress); + console.log(`Using owner address: ${ownerAddress}`); + + const gasPrice = cmd.gasPrice + ? parseUnits(cmd.gasPrice, "gwei") + : (await provider.getGasPrice()).mul(GAS_MULTIPLIER); + console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); + + const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); + console.log(`Using nonce: ${nonce}`); + + // Moving ownership for ValidatorTimelock + const validatorTimelockAddr = ethers.utils.getAddress(cmd.validatorTimelockAddr); + console.log(`Using ValidatorTimelock address: ${validatorTimelockAddr}`); + const stmAddr = ethers.utils.getAddress(cmd.stmAddr); + console.log("Using STM address: ", stmAddr); + const l1SharedBridgeAddr = ethers.utils.getAddress(cmd.l1SharedBridgeAddr); + console.log("Using L1 Shared Bridge address: ", l1SharedBridgeAddr); + const bridgehubAddr = ethers.utils.getAddress(cmd.bridgehubAddr); + console.log("Using Bridgehub address: ", bridgehubAddr); + const proxyAdminAddr = ethers.utils.getAddress(cmd.proxyAdminAddr); + console.log("Using Proxy Admin address: ", proxyAdminAddr); + + await transferOwnership1StepTo(deployWallet, validatorTimelockAddr, ownerAddress, true); + await transferOwnership1StepTo(deployWallet, stmAddr, ownerAddress, true); + await transferOwnership1StepTo(deployWallet, l1SharedBridgeAddr, ownerAddress, true); + await transferOwnership1StepTo(deployWallet, bridgehubAddr, ownerAddress, true); + await transferOwnership1StepTo(deployWallet, proxyAdminAddr, ownerAddress, false); + }); + + program + .command("accept-ownership") + .option("--validator-timelock-addr ") + .option("--stm-addr ") + .option("--l1-shared-bridge-addr ") + .option("--bridgehub-addr ") + .action(async (cmd) => { + // Moving ownership for ValidatorTimelock + const validatorTimelockAddr = ethers.utils.getAddress(cmd.validatorTimelockAddr); + console.log(`Using ValidatorTimelock address: ${validatorTimelockAddr}`); + const stmAddr = ethers.utils.getAddress(cmd.stmAddr); + console.log("Using STM address: ", stmAddr); + const l1SharedBridgeAddr = ethers.utils.getAddress(cmd.l1SharedBridgeAddr); + console.log("Using L1 Shared Bridge address: ", l1SharedBridgeAddr); + const bridgehubAddr = ethers.utils.getAddress(cmd.bridgehubAddr); + console.log("Using Bridgehub address: ", bridgehubAddr); + + const addresses = [validatorTimelockAddr, stmAddr, l1SharedBridgeAddr, bridgehubAddr]; + + const govCalls = addresses.map(acceptOwnershipCall); + + const govOperation = { + calls: govCalls, + predecessor: ethers.constants.HashZero, + salt: ethers.constants.HashZero, + }; + + const scheduleData = governanceInterface.encodeFunctionData("scheduleTransparent", [govOperation, 0]); + const executeData = governanceInterface.encodeFunctionData("execute", [govOperation]); + + console.log("Calldata for scheduling: ", scheduleData); + console.log("Calldata for execution: ", executeData); + }); + + await program.parseAsync(process.argv); +} + +main() + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); + +async function transferOwnership1StepTo( + wallet: ethers.Wallet, + contractAddress: string, + newOwner: string, + printPendingOwner: boolean = true +) { + const contract = new ethers.Contract(contractAddress, ownable2StepInterface, wallet); + console.log("Transferring ownership of contract: ", contractAddress, " to: ", newOwner); + const tx = await contract.transferOwnership(newOwner); + console.log("Tx hash", tx.hash); + await tx.wait(); + if (printPendingOwner) { + const newPendingOwner = await contract.pendingOwner(); + console.log("New pending owner: ", newPendingOwner); + } +} + +function acceptOwnershipCall(target: string) { + const data = ownable2StepInterface.encodeFunctionData("acceptOwnership", []); + return { + target, + value: 0, + data, + }; +} diff --git a/l1-contracts/scripts/hyperchain-upgrade-1.ts b/l1-contracts/scripts/hyperchain-upgrade-1.ts index e99d84a8c..90a398ee3 100644 --- a/l1-contracts/scripts/hyperchain-upgrade-1.ts +++ b/l1-contracts/scripts/hyperchain-upgrade-1.ts @@ -2,7 +2,7 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars import * as hardhat from "hardhat"; import { Command } from "commander"; -import { Wallet, ethers } from "ethers"; +import { Wallet } from "ethers"; import { Deployer } from "../src.ts/deploy"; import { formatUnits, parseUnits } from "ethers/lib/utils"; import { web3Provider, GAS_MULTIPLIER } from "./utils"; @@ -46,7 +46,9 @@ async function main() { const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployWallet.getTransactionCount(); console.log(`Using nonce: ${nonce}`); - const create2Salt = cmd.create2Salt ? cmd.create2Salt : ethers.utils.hexlify(ethers.utils.randomBytes(32)); + const create2Salt = cmd.create2Salt + ? cmd.create2Salt + : "0x0000000000000000000000000000000000000000000000000000000000000000"; const deployer = new Deployer({ deployWallet, diff --git a/l1-contracts/scripts/hyperchain-upgrade-2.ts b/l1-contracts/scripts/hyperchain-upgrade-2.ts index edef79bf0..ae8fd93ad 100644 --- a/l1-contracts/scripts/hyperchain-upgrade-2.ts +++ b/l1-contracts/scripts/hyperchain-upgrade-2.ts @@ -24,6 +24,7 @@ async function main() { .option("--nonce ") .option("--owner-address ") .option("--create2-salt ") + .option("--print-file-path ") .option("--diamond-upgrade-init ") .option("--only-verifier") .action(async (cmd) => { @@ -53,7 +54,7 @@ async function main() { verbose: true, }); - await upgradeToHyperchains2(deployer, gasPrice); + await upgradeToHyperchains2(deployer, gasPrice, cmd.printFilePath); }); await program.parseAsync(process.argv); diff --git a/l1-contracts/scripts/hyperchain-upgrade-3.ts b/l1-contracts/scripts/hyperchain-upgrade-3.ts index d232a80d8..e66065d94 100644 --- a/l1-contracts/scripts/hyperchain-upgrade-3.ts +++ b/l1-contracts/scripts/hyperchain-upgrade-3.ts @@ -24,6 +24,7 @@ async function main() { .option("--nonce ") .option("--owner-address ") .option("--create2-salt ") + .option("--print-file-path ") .option("--diamond-upgrade-init ") .option("--only-verifier") .action(async (cmd) => { @@ -33,7 +34,7 @@ async function main() { process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, "m/44'/60'/0'/0/1" ).connect(provider); - console.log(`Using deployer wallet: ${deployWallet.address}, ${deployWallet.privateKey}`); + console.log(`Using deployer wallet: ${deployWallet.address}`); const ownerAddress = cmd.ownerAddress ? cmd.ownerAddress : deployWallet.address; console.log(`Using owner address: ${ownerAddress}`); @@ -53,7 +54,7 @@ async function main() { verbose: true, }); - await upgradeToHyperchains3(deployer); + await upgradeToHyperchains3(deployer, cmd.printFilePath); }); await program.parseAsync(process.argv); diff --git a/l1-contracts/scripts/register-hyperchain.ts b/l1-contracts/scripts/register-hyperchain.ts index bb21aeca5..755352f9b 100644 --- a/l1-contracts/scripts/register-hyperchain.ts +++ b/l1-contracts/scripts/register-hyperchain.ts @@ -69,6 +69,7 @@ async function main() { .option("--validium-mode") .option("--base-token-name ") .option("--base-token-address ") + .option("--use-governance ") .action(async (cmd) => { const deployWallet = cmd.privateKey ? new Wallet(cmd.privateKey, provider) @@ -99,11 +100,14 @@ async function main() { await checkTokenAddress(baseTokenAddress); console.log(`Using base token address: ${baseTokenAddress}`); + const useGovernance = !!cmd.useGovernance && cmd.useGovernance === "true"; + if (!(await deployer.bridgehubContract(deployWallet).tokenIsRegistered(baseTokenAddress))) { - await deployer.registerToken(baseTokenAddress); + await deployer.registerToken(baseTokenAddress, useGovernance); } - await deployer.registerHyperchain(baseTokenAddress, cmd.validiumMode, null, gasPrice); + await deployer.registerHyperchain(baseTokenAddress, cmd.validiumMode, null, gasPrice, useGovernance); + await deployer.transferAdminFromDeployerToGovernance(); }); await program.parseAsync(process.argv); diff --git a/l1-contracts/scripts/token-migration.ts b/l1-contracts/scripts/token-migration.ts new file mode 100644 index 000000000..d9ec8757a --- /dev/null +++ b/l1-contracts/scripts/token-migration.ts @@ -0,0 +1,220 @@ +// hardhat import should be the first import in the file +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import * as hardhat from "hardhat"; +import { Command } from "commander"; +import { web3Url } from "./utils"; +import { ethers } from "ethers"; +import { Provider, utils } from "zksync-ethers"; + +async function main() { + const program = new Command(); + + program.version("0.1.0").name("upgrade-shared-bridge-era").description("upgrade shared bridge for era diamond proxy"); + + program + .command("get-confirmed-tokens") + .description("Returns the list of tokens that are registered on the bridge and should be migrated") + .option("--use-l1") + .option("--start-from-block ") + .action(async (cmd) => { + const l2Provider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); + const l1Provider = new ethers.providers.JsonRpcProvider(web3Url()); + + let confirmedFromAPI; + + if (cmd.useL1) { + const block = cmd.startFromBlock; + if (!block) { + throw new Error("For L1 the starting block should be provided"); + } + + console.log("Fetching confirmed tokens from the L1"); + console.log("This will take a long time"); + + const bridge = (await l2Provider.getDefaultBridgeAddresses()).erc20L1; + console.log("Using L1 ERC20 bridge ", bridge); + + const confirmedFromL1 = await loadAllConfirmedTokensFromL1(l1Provider, bridge, +block); + console.log(JSON.stringify(confirmedFromL1, null, 2)); + } else { + console.log("Fetching confirmed tokens from the L2 API..."); + confirmedFromAPI = await loadAllConfirmedTokensFromAPI(l2Provider); + + console.log(JSON.stringify(confirmedFromAPI, null, 2)); + } + }); + + program + .command("merge-confirmed-tokens") + .description("Merges two lists of confirmed tokens") + .option("--from-l1 ") + .option("--from-l2 ") + .action(async (cmd) => { + const l2Provider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); + const bridge = (await l2Provider.getDefaultBridgeAddresses()).erc20L1; + console.log("Using L1 ERC20 bridge ", bridge); + + const allTokens = {}; + const tokensFromL1: string[] = JSON.parse(cmd.fromL1).map((token) => token.toLowerCase()); + const tokensFromL2: string[] = JSON.parse(cmd.fromL2).map((token) => token.toLowerCase()); + + tokensFromL1.forEach((token) => (allTokens[token] = true)); + tokensFromL2.forEach((token) => (allTokens[token] = true)); + + const erc20Abi = ["function balanceOf(address) view returns (uint256)"]; + + const result = []; + + const l1Provider = new ethers.providers.JsonRpcProvider(web3Url()); + for (const token of Object.keys(allTokens)) { + const contract = new ethers.Contract(token, erc20Abi, l1Provider); + const balanceL1 = await contract.balanceOf(bridge); + if (balanceL1.gt(0)) { + console.log("Token ", token, " has balance in the bridge ", balanceL1.toString()); + result.push(token); + } + } + + console.log(JSON.stringify(result, null, 2)); + }); + + program + .command("prepare-migration-calldata") + .description("Prepare the calldata to be signed by the governance to migrate the funds from the legacy bridge") + .option("--tokens-list ") + .option("--gas-per-token ") + .option("--tokens-per-signature ") + .option("--shared-bridge-addr ") + .option("--legacy-bridge-addr ") + .option("--era-chain-addr ") + .option("--era-chain-id ") + .option("--delay ") + + .action(async (cmd) => { + const allTokens: string[] = JSON.parse(cmd.tokensList); + // Appending the ETH token to be migrated + allTokens.push("0x0000000000000000000000000000000000000001"); + + const tokensPerSignature = +cmd.tokensPerSignature; + + const scheduleCalldatas = []; + const executeCalldatas = []; + + for (let i = 0; i < allTokens.length; i += tokensPerSignature) { + const tokens = allTokens.slice(i, Math.min(i + tokensPerSignature, allTokens.length)); + const { scheduleCalldata, executeCalldata } = await prepareGovernanceTokenMigrationCall( + tokens, + cmd.sharedBridgeAddr, + cmd.legacyBridgeAddr, + cmd.eraChainAddr, + cmd.eraChainId, + +cmd.gasPerToken, + +cmd.delay + ); + + scheduleCalldatas.push(scheduleCalldata); + executeCalldatas.push(executeCalldata); + } + + console.log("Schedule operations to sign: "); + scheduleCalldatas.forEach((calldata) => console.log(calldata + "\n")); + + console.log("Execute operations to sign: "); + executeCalldatas.forEach((calldata) => console.log(calldata + "\n")); + }); + + await program.parseAsync(process.argv); +} + +main() + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); + +async function loadAllConfirmedTokensFromAPI(l2Provider: Provider) { + const limit = 50; + const result = []; + let offset = 0; + + // eslint-disable-next-line no-constant-condition + while (true) { + const tokens = await l2Provider.send("zks_getConfirmedTokens", [offset, limit]); + if (!tokens.length) { + return result; + } + + tokens.forEach((token) => result.push(token.l1Address)); + offset += limit; + } +} + +async function loadAllConfirmedTokensFromL1( + l1Provider: ethers.providers.JsonRpcProvider, + bridgeAddress: string, + startBlock: number +) { + const blocksRange = 50000; + const endBlock = await l1Provider.getBlockNumber(); + const abi = (await hardhat.artifacts.readArtifact("IL1ERC20Bridge")).abi; + const contract = new ethers.Contract(bridgeAddress, abi, l1Provider); + const filter = contract.filters.DepositInitiated(); + + const tokens = {}; + + while (startBlock <= endBlock) { + console.log("Querying blocks ", startBlock, " - ", Math.min(startBlock + blocksRange, endBlock)); + const logs = await l1Provider.getLogs({ + ...filter, + fromBlock: startBlock, + toBlock: Math.min(startBlock + blocksRange, endBlock), + }); + const deposits = logs.map((log) => contract.interface.parseLog(log)); + deposits.forEach((dep) => { + if (!tokens[dep.args.l1Token]) { + console.log(dep.args.l1Token, " found!"); + } + tokens[dep.args.l1Token] = true; + }); + + startBlock += blocksRange; + } + + return Object.keys(tokens); +} + +async function prepareGovernanceTokenMigrationCall( + tokens: string[], + l1SharedBridgeAddr: string, + l1LegacyBridgeAddr: string, + eraChainAddress: string, + eraChainId: number, + gasPerToken: number, + delay: number +) { + const governanceAbi = new ethers.utils.Interface((await hardhat.artifacts.readArtifact("IGovernance")).abi); + const sharedBridgeAbi = new ethers.utils.Interface((await hardhat.artifacts.readArtifact("L1SharedBridge")).abi); + const calls = tokens.map((token) => { + const target = token == utils.ETH_ADDRESS_IN_CONTRACTS ? eraChainAddress : l1LegacyBridgeAddr; + + return { + target: l1SharedBridgeAddr, + value: 0, + data: sharedBridgeAbi.encodeFunctionData("safeTransferFundsFromLegacy", [token, target, eraChainId, gasPerToken]), + }; + }); + const governanceOp = { + calls, + predecessor: ethers.constants.HashZero, + salt: ethers.constants.HashZero, + }; + + const scheduleCalldata = governanceAbi.encodeFunctionData("scheduleTransparent", [governanceOp, delay]); + const executeCalldata = governanceAbi.encodeFunctionData("execute", [governanceOp]); + + return { + scheduleCalldata, + executeCalldata, + }; +} diff --git a/l1-contracts/scripts/upgrade-consistency-checker.ts b/l1-contracts/scripts/upgrade-consistency-checker.ts new file mode 100644 index 000000000..3972c8695 --- /dev/null +++ b/l1-contracts/scripts/upgrade-consistency-checker.ts @@ -0,0 +1,506 @@ +/// This is the script to double check the consistency of the upgrade +/// It is yet to be refactored. + +// hardhat import should be the first import in the file +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import * as hardhat from "hardhat"; +import { Command } from "commander"; +import { web3Url } from "./utils"; +import { BigNumber, ethers } from "ethers"; +import { utils } from "zksync-ethers"; +import type { FacetCut } from "../src.ts/diamondCut"; +import { getCurrentFacetCutsForAdd } from "../src.ts/diamondCut"; + +// Things that still have to be manually double checked: +// 1. Contracts must be verified. +// 2. Getter methods in STM. + +// List the contracts that should become the upgrade targets +const genesisUpgrade = "0xc6aB8b3b93f3E47fb4163eB9Dc7A61E1a5D86369"; +const validatorTimelockDeployTx = "0x420e0dddae4a1565fee430ecafa8f5ddbc3eebee2666d0c91f97a47bf054eeb4"; +const validatorTimelock = "0xc2d7a7Bd59a548249e64C1a587220c0E4F6F439E"; +const upgradeHyperchains = "0xb2963DDc6694a989B527AED0B1E19f9F0675AE4d"; + +const verifier = "0x9D6c59D9A234F585B367b4ba3C62e5Ec7A6179FD"; +const proxyAdmin = "0xf2c1d17441074FFb18E9A918db81A17dB1752146"; + +const bridgeHubImpl = "0xF9D2E98Ed518eC6Daac0579a9707d83da55D5f89"; +const bridgeHub = "0x5B5c82f4Da996e118B127880492a23391376F65c"; + +const executorFacet = "0x1a451d9bFBd176321966e9bc540596Ca9d39B4B1"; +const adminFacet = "0x342a09385E9BAD4AD32a6220765A6c333552e565"; +const mailboxFacetDeployTx = "0x2fa6af6e9317089be2734ffae73771c8099382d390d4edbb6c35e2db7f73b152"; +const mailboxFacet = "0x7814399116C17F2750Ca99cBFD2b75bA9a0793d7"; +const gettersFacet = "0x345c6ca2F3E08445614f4299001418F125AD330a"; + +const diamondInit = "0x05D865AE297d236Bc5C7988328d02A00b3D38a4F"; + +const stmImplDeployTx = "0x7a077accd4ee39d14b6c23ef31ece4a84c87aff41cd64fd4d2ac23a3885dd4f8"; +const stmImpl = "0x3060D61538fC91B6580e34C5b5D09651CBB9c609"; +const stmDeployTx = "0x30138b826e8f8f855e7fe9e6153d49376b53bce71c34cb2a78e186b12156c966"; +const stm = "0x280372beAAf440C52a2ed893daa14CDACc0422b8"; + +const sharedBridgeImplDeployTx = "0xd24d38cab0beb62f6de9a83cd0a5d7e339e985ba84ac6ef07a336efd79ae333a"; +const sharedBridgeImpl = "0x3819200C978d8A589a1e28A2e8fEb9a0CAD700F7"; +const sharedBridgeProxy = "0x241F19eA8CcD04515b309f1C9953A322F51891FC"; + +const legacyBridgeImplDeployTx = "0xd8cca5843318ca176afd1075ca6fbb941837a641324300d3719f9189e49fd62c"; +const legacyBridgeImpl = "0xbf3d4109D65A66c629D1999fb630bE2eE16d7038"; + +const expectedL1WethAddress = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; +const initialOwner = "0x71d84c3404a6ae258E6471d4934B96a2033F9438"; +const expectedOwner = "0x71d84c3404a6ae258E6471d4934B96a2033F9438"; +const expectedDelay = 0; +const eraChainId = 324; +const expectedSalt = "0x0000000000000000000000000000000000000000000000000000000000000000"; +const expectedHyperchainAddr = "0x32400084c286cf3e17e7b677ea9583e60a000324"; +const maxNumberOfHyperchains = 100; +const expectedStoredBatchHashZero = "0x1574fa776dec8da2071e5f20d71840bfcbd82c2bca9ad68680edfedde1710bc4"; +const expectedL2BridgeAddress = "0x11f943b2c77b743AB90f4A0Ae7d5A4e7FCA3E102"; +const expectedL1LegacyBridge = "0x57891966931Eb4Bb6FB81430E6cE0A03AAbDe063"; +const expectedGenesisBatchCommitment = "0x2d00e5f8d77afcebf58a6b82ae56ba967566fe7dfbcb6760319fb0d215d18ffd"; +const expectedIndexRepeatedStorageChanges = BigNumber.from(54); +const expectedProtocolVersion = 24; +const expectedGenesisRoot = "0xabdb766b18a479a5c783a4b80e12686bc8ea3cc2d8a3050491b701d72370ebb5"; +const expectedRecursionNodeLevelVkHash = "0xf520cd5b37e74e19fdb369c8d676a04dce8a19457497ac6686d2bb95d94109c8"; +const expectedRecursionLeafLevelVkHash = "0x435202d277dd06ef3c64ddd99fda043fc27c2bd8b7c66882966840202c27f4f6"; +const expectedRecursionCircuitsSetVksHash = "0x0000000000000000000000000000000000000000000000000000000000000000"; +const expectedBootloaderHash = "0x010008e742608b21bf7eb23c1a9d0602047e3618b464c9b59c0fba3b3d7ab66e"; +const expectedDefaultAccountHash = "0x01000563374c277a2c1e34659a2a1e87371bb6d852ce142022d497bfb50b9e32"; + +const validatorOne = "0x0D3250c3D5FAcb74Ac15834096397a3Ef790ec99"; +const validatorTwo = "0x3527439923a63F8C13CF72b8Fe80a77f6e572092"; + +const l1Provider = new ethers.providers.JsonRpcProvider(web3Url()); + +async function checkIdenticalBytecode(addr: string, contract: string) { + const correctCode = (await hardhat.artifacts.readArtifact(contract)).deployedBytecode; + const currentCode = await l1Provider.getCode(addr); + + if (ethers.utils.keccak256(currentCode) == ethers.utils.keccak256(correctCode)) { + console.log(contract, "bytecode is correct"); + } else { + throw new Error(contract + " bytecode is not correct"); + } +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +async function checkCorrectInitCode(txHash: string, contract: ethers.Contract, bytecode: string, params: any[]) { + const deployTx = await l1Provider.getTransaction(txHash); + const usedInitCode = await extractInitCode(deployTx.data); + const correctConstructorData = contract.interface.encodeDeploy(params); + const correctInitCode = ethers.utils.hexConcat([bytecode, correctConstructorData]); + if (usedInitCode.toLowerCase() !== correctInitCode.toLowerCase()) { + throw new Error("Init code is not correct"); + } +} + +async function extractInitCode(data: string) { + const create2FactoryAbi = (await hardhat.artifacts.readArtifact("SingletonFactory")).abi; + + const iface = new ethers.utils.Interface(create2FactoryAbi); + const initCode = iface.parseTransaction({ data }).args._initCode; + const salt = iface.parseTransaction({ data }).args._salt; + if (salt !== expectedSalt) { + throw new Error("Salt is not correct"); + } + + return initCode; +} + +async function extractProxyInitializationData(contract: ethers.Contract, data: string) { + const initCode = await extractInitCode(data); + + const artifact = await hardhat.artifacts.readArtifact("TransparentUpgradeableProxy"); + + // Deployment tx is a concatenation of the init code and the constructor data + // constructor has the following type `constructor(address _logic, address admin_, bytes memory _data)` + + const constructorData = "0x" + initCode.slice(artifact.bytecode.length); + + const [, , initializeCode] = ethers.utils.defaultAbiCoder.decode(["address", "address", "bytes"], constructorData); + + // Now time to parse the initialize code + const parsedData = contract.interface.parseTransaction({ data: initializeCode }); + const initializeData = { + ...parsedData.args._initializeData, + }; + + const usedInitialOwner = initializeData.owner; + if (usedInitialOwner.toLowerCase() !== initialOwner.toLowerCase()) { + throw new Error("Initial owner is not correct"); + } + + const usedValidatorTimelock = initializeData.validatorTimelock; + if (usedValidatorTimelock.toLowerCase() !== validatorTimelock.toLowerCase()) { + throw new Error("Validator timelock is not correct"); + } + const usedGenesisUpgrade = initializeData.genesisUpgrade; + if (usedGenesisUpgrade.toLowerCase() !== genesisUpgrade.toLowerCase()) { + throw new Error("Genesis upgrade is not correct"); + } + const usedGenesisBatchHash = initializeData.genesisBatchHash; + if (usedGenesisBatchHash.toLowerCase() !== expectedGenesisRoot.toLowerCase()) { + throw new Error("Genesis batch hash is not correct"); + } + const usedGenesisIndexRepeatedStorageChanges = initializeData.genesisIndexRepeatedStorageChanges; + if (!usedGenesisIndexRepeatedStorageChanges.eq(expectedIndexRepeatedStorageChanges)) { + throw new Error("Genesis index repeated storage changes is not correct"); + } + + const usedGenesisBatchCommitment = initializeData.genesisBatchCommitment; + if (usedGenesisBatchCommitment.toLowerCase() !== expectedGenesisBatchCommitment.toLowerCase()) { + throw new Error("Genesis batch commitment is not correct"); + } + + const usedProtocolVersion = initializeData.protocolVersion; + if (!usedProtocolVersion.eq(expectedProtocolVersion)) { + throw new Error("Protocol version is not correct"); + } + + const diamondCut = initializeData.diamondCut; + + if (diamondCut.initAddress.toLowerCase() !== diamondInit.toLowerCase()) { + throw new Error("Diamond init address is not correct"); + } + + const expectedFacetCuts: FacetCut[] = Object.values( + await getCurrentFacetCutsForAdd(adminFacet, gettersFacet, mailboxFacet, executorFacet) + ); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const usedFacetCuts = diamondCut.facetCuts.map((fc: any) => { + return { + facet: fc.facet, + selectors: fc.selectors, + action: fc.action, + isFreezable: fc.isFreezable, + }; + }); + + // Now sort to compare + expectedFacetCuts.sort((a, b) => a.facet.localeCompare(b.facet)); + usedFacetCuts.sort((a, b) => a.facet.localeCompare(b.facet)); + + if (expectedFacetCuts.length !== usedFacetCuts.length) { + throw new Error("Facet cuts length is not correct"); + } + + for (let i = 0; i < expectedFacetCuts.length; i++) { + const used = usedFacetCuts[i]; + const expected = expectedFacetCuts[i]; + + if (used.facet !== expected.facet) { + throw new Error(`Facet ${i} is not correct`); + } + + // For the array of selectors it is just easier to hexconcat them and compare + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const usedSelectors = ethers.utils.hexConcat(Array.from(used.selectors).sort() as any[]); + const expectedSelectors = ethers.utils.hexConcat(expected.selectors.sort()); + if (usedSelectors !== expectedSelectors) { + throw new Error(`Facet ${i} selectors are not correct`); + } + + if (used.action !== expected.action) { + throw new Error(`Facet ${i} action is not correct`); + } + + if (used.isFreezable !== expected.isFreezable) { + throw new Error(`Facet ${i} isFreezable is not correct`); + } + } + + const [ + usedVerifier, + // We just unpack verifier params here + recursionNodeLevelVkHash, + recursionLeafLevelVkHash, + recursionCircuitsSetVksHash, + l2BootloaderBytecodeHash, + l2DefaultAccountBytecodeHash, + // priorityTxMaxGasLimit, + + // // We unpack fee params + // pubdataPricingMode, + // batchOverheadL1Gas, + // maxPubdataPerBatch, + // priorityTxMaxPubdata, + // maxL2GasPerBatch, + // minimalL2GasPrice, + + // blobVersionedHashRetriever + ] = ethers.utils.defaultAbiCoder.decode( + [ + "address", + "bytes32", + "bytes32", + "bytes32", + "bytes32", + "bytes32", + "uint256", + "uint256", + "uint256", + "uint256", + "uint256", + "uint256", + "uint256", + "address", + ], + diamondCut.initCalldata + ); + + if (usedVerifier.toLowerCase() !== verifier.toLowerCase()) { + throw new Error("Verifier is not correct"); + } + + if (recursionNodeLevelVkHash.toLowerCase() !== expectedRecursionNodeLevelVkHash.toLowerCase()) { + throw new Error("Recursion node level vk hash is not correct"); + } + + if (recursionLeafLevelVkHash.toLowerCase() !== expectedRecursionLeafLevelVkHash.toLowerCase()) { + throw new Error("Recursion leaf level vk hash is not correct"); + } + + if (recursionCircuitsSetVksHash.toLowerCase() !== expectedRecursionCircuitsSetVksHash.toLowerCase()) { + throw new Error("Recursion circuits set vks hash is not correct"); + } + + if (l2BootloaderBytecodeHash.toLowerCase() !== expectedBootloaderHash.toLowerCase()) { + throw new Error("L2 bootloader bytecode hash is not correct"); + } + + if (l2DefaultAccountBytecodeHash.toLowerCase() !== expectedDefaultAccountHash.toLowerCase()) { + throw new Error("L2 default account bytecode hash is not correct"); + } + + console.log("STM init data correct!"); +} + +async function checkValidatorTimelock() { + const artifact = await hardhat.artifacts.readArtifact("ValidatorTimelock"); + const contract = new ethers.Contract(validatorTimelock, artifact.abi, l1Provider); + + const owner = await contract.owner(); + if (owner.toLowerCase() != expectedOwner.toLowerCase()) { + throw new Error("ValidatorTimelock owner is not correct"); + } + + const usedStm = await contract.stateTransitionManager(); + if (usedStm.toLowerCase() != stm.toLowerCase()) { + throw new Error("ValidatorTimelock stateTransitionManager is not correct"); + } + + const validatorOneIsSet = await contract.validators(eraChainId, validatorOne); + if (!validatorOneIsSet) { + throw new Error("ValidatorTimelock validatorOne is not correct"); + } + + const validatorTwoIsSet = await contract.validators(eraChainId, validatorTwo); + if (!validatorTwoIsSet) { + throw new Error("ValidatorTimelock validatorTwo is not correct"); + } + + await checkCorrectInitCode(validatorTimelockDeployTx, contract, artifact.bytecode, [ + initialOwner, + expectedDelay, + eraChainId, + ]); + + console.log("ValidatorTimelock is correct!"); +} + +async function checkBridgehub() { + const artifact = await hardhat.artifacts.readArtifact("Bridgehub"); + const contract = new ethers.Contract(bridgeHub, artifact.abi, l1Provider); + + const owner = await contract.owner(); + if (owner.toLowerCase() != expectedOwner.toLowerCase()) { + throw new Error("ValidatorTimelock owner is not correct"); + } + + const baseToken = await contract.baseToken(eraChainId); + if (baseToken.toLowerCase() != utils.ETH_ADDRESS_IN_CONTRACTS) { + throw new Error("Bridgehub baseToken is not correct"); + } + + const hyperchain = await contract.getHyperchain(eraChainId); + if (hyperchain.toLowerCase() != expectedHyperchainAddr.toLowerCase()) { + throw new Error("Bridgehub hyperchain is not correct"); + } + + const sharedBridge = await contract.sharedBridge(); + if (sharedBridge.toLowerCase() != sharedBridgeProxy.toLowerCase()) { + throw new Error("Bridgehub sharedBridge is not correct"); + } + + const usedSTM = await contract.stateTransitionManager(eraChainId); + if (usedSTM.toLowerCase() != stm.toLowerCase()) { + throw new Error("Bridgehub stateTransitionManager is not correct"); + } + + const isRegistered = await contract.stateTransitionManagerIsRegistered(usedSTM); + if (!isRegistered) { + throw new Error("Bridgehub stateTransitionManager is not registered"); + } + + const tokenIsRegistered = await contract.tokenIsRegistered(utils.ETH_ADDRESS_IN_CONTRACTS); + if (!tokenIsRegistered) { + throw new Error("Bridgehub token is not registered"); + } + + console.log("Bridgehub is correct!"); +} + +async function checkMailbox() { + const artifact = await hardhat.artifacts.readArtifact("MailboxFacet"); + const contract = new ethers.Contract(mailboxFacet, artifact.abi, l1Provider); + + await checkCorrectInitCode(mailboxFacetDeployTx, contract, artifact.bytecode, [eraChainId]); + console.log("Mailbox is correct!"); +} + +async function checkSTMImpl() { + const artifact = await hardhat.artifacts.readArtifact("StateTransitionManager"); + const contract = new ethers.Contract(stmImpl, artifact.abi, l1Provider); + + await checkCorrectInitCode(stmImplDeployTx, contract, artifact.bytecode, [bridgeHub, maxNumberOfHyperchains]); + + console.log("STM impl correct!"); +} + +async function checkSTM() { + const artifact = await hardhat.artifacts.readArtifact("StateTransitionManager"); + + const contract = new ethers.Contract(stm, artifact.abi, l1Provider); + + const usedBH = await contract.BRIDGE_HUB(); + if (usedBH.toLowerCase() != bridgeHub.toLowerCase()) { + throw new Error("STM bridgeHub is not correct"); + } + const usedMaxNumberOfHyperchains = (await contract.MAX_NUMBER_OF_HYPERCHAINS()).toNumber(); + if (usedMaxNumberOfHyperchains != maxNumberOfHyperchains) { + throw new Error("STM maxNumberOfHyperchains is not correct"); + } + + const genUpgrade = await contract.genesisUpgrade(); + if (genUpgrade.toLowerCase() != genesisUpgrade.toLowerCase()) { + throw new Error("STM genesisUpgrade is not correct"); + } + + const storedBatchHashZero = await contract.storedBatchZero(); + if (storedBatchHashZero.toLowerCase() != expectedStoredBatchHashZero.toLowerCase()) { + throw new Error("STM storedBatchHashZero is not correct"); + } + + const currentOwner = await contract.owner(); + if (currentOwner.toLowerCase() != expectedOwner.toLowerCase()) { + throw new Error("STM owner is not correct"); + } + + console.log("STM is correct!"); + + await extractProxyInitializationData(contract, (await l1Provider.getTransaction(stmDeployTx)).data); +} + +async function checkL1SharedBridgeImpl() { + const artifact = await hardhat.artifacts.readArtifact("L1SharedBridge"); + const contract = new ethers.Contract(sharedBridgeImpl, artifact.abi, l1Provider); + + await checkCorrectInitCode(sharedBridgeImplDeployTx, contract, artifact.bytecode, [ + expectedL1WethAddress, + bridgeHub, + eraChainId, + expectedHyperchainAddr, + ]); + + console.log("L1 shared bridge impl correct!"); +} + +async function checkSharedBridge() { + const artifact = await hardhat.artifacts.readArtifact("L1SharedBridge"); + const contract = new ethers.Contract(sharedBridgeProxy, artifact.abi, l1Provider); + + const l2BridgeAddr = await contract.l2BridgeAddress(eraChainId); + if (l2BridgeAddr.toLowerCase() != expectedL2BridgeAddress.toLowerCase()) { + throw new Error("Shared bridge l2BridgeAddress is not correct"); + } + + const usedLegacyBridge = await contract.legacyBridge(); + if (usedLegacyBridge.toLowerCase() != expectedL1LegacyBridge.toLowerCase()) { + throw new Error("Shared bridge legacyBridge is not correct"); + } + + const usedOwner = await contract.owner(); + if (usedOwner.toLowerCase() != expectedOwner.toLowerCase()) { + throw new Error("Shared bridge owner is not correct"); + } + + console.log("L1 shared bridge correct!"); +} + +async function checkLegacyBridge() { + const artifact = await hardhat.artifacts.readArtifact("L1ERC20Bridge"); + const contract = new ethers.Contract(legacyBridgeImpl, artifact.abi, l1Provider); + + await checkCorrectInitCode(legacyBridgeImplDeployTx, contract, artifact.bytecode, [sharedBridgeProxy]); + + console.log("L1 shared bridge impl correct!"); +} + +async function checkProxyAdmin() { + await checkIdenticalBytecode(proxyAdmin, "ProxyAdmin"); + + const artifact = await hardhat.artifacts.readArtifact("ProxyAdmin"); + const contract = new ethers.Contract(proxyAdmin, artifact.abi, l1Provider); + + const currentOwner = await contract.owner(); + if (currentOwner.toLowerCase() != expectedOwner.toLowerCase()) { + throw new Error(`ProxyAdmin owner is not correct ${currentOwner}, ${expectedOwner}`); + } + + console.log("ProxyAdmin is correct!"); +} + +async function main() { + const program = new Command(); + + program + .version("0.1.0") + .name("upgrade-consistency-checker") + .description("upgrade shared bridge for era diamond proxy"); + + program.action(async () => { + await checkIdenticalBytecode(genesisUpgrade, "GenesisUpgrade"); + await checkIdenticalBytecode(upgradeHyperchains, "UpgradeHyperchains"); + await checkIdenticalBytecode(executorFacet, "ExecutorFacet"); + await checkIdenticalBytecode(gettersFacet, "GettersFacet"); + await checkIdenticalBytecode(adminFacet, "AdminFacet"); + await checkIdenticalBytecode(bridgeHubImpl, "Bridgehub"); + await checkIdenticalBytecode(verifier, eraChainId == 324 ? "Verifier" : "TestnetVerifier"); + await checkIdenticalBytecode(diamondInit, "DiamondInit"); + + await checkMailbox(); + + await checkProxyAdmin(); + + await checkValidatorTimelock(); + await checkBridgehub(); + + await checkL1SharedBridgeImpl(); + await checkSharedBridge(); + + await checkLegacyBridge(); + + await checkSTMImpl(); + await checkSTM(); + }); + + await program.parseAsync(process.argv); +} + +main() + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); diff --git a/l1-contracts/scripts/utils.ts b/l1-contracts/scripts/utils.ts index 3a972b64c..5ae1bceac 100644 --- a/l1-contracts/scripts/utils.ts +++ b/l1-contracts/scripts/utils.ts @@ -10,6 +10,9 @@ const warning = chalk.bold.yellow; export const L1_TO_L2_ALIAS_OFFSET = "0x1111000000000000000000000000000000001111"; export const GAS_MULTIPLIER = 1; +// Bit shift by 32 does not work in JS, so we have to multiply by 2^32 +export const SEMVER_MINOR_VERSION_MULTIPLIER = 4294967296; + interface SystemConfig { requiredL2GasPricePerPubdata: number; priorityTxMinimalGasPrice: number; @@ -75,3 +78,29 @@ export function print(name: string, data: any) { export function getLowerCaseAddress(address: string) { return ethers.utils.getAddress(address).toLowerCase(); } + +export function unpackStringSemVer(semver: string): [number, number, number] { + const [major, minor, patch] = semver.split("."); + return [parseInt(major), parseInt(minor), parseInt(patch)]; +} + +function unpackNumberSemVer(semver: number): [number, number, number] { + const major = 0; + const minor = Math.floor(semver / SEMVER_MINOR_VERSION_MULTIPLIER); + const patch = semver % SEMVER_MINOR_VERSION_MULTIPLIER; + return [major, minor, patch]; +} + +// The major version is always 0 for now +export function packSemver(major: number, minor: number, patch: number) { + if (major !== 0) { + throw new Error("Major version must be 0"); + } + + return minor * SEMVER_MINOR_VERSION_MULTIPLIER + patch; +} + +export function addToProtocolVersion(packedProtocolVersion: number, minor: number, patch: number) { + const [major, minorVersion, patchVersion] = unpackNumberSemVer(packedProtocolVersion); + return packSemver(major, minorVersion + minor, patchVersion + patch); +} diff --git a/l1-contracts/scripts/verify.ts b/l1-contracts/scripts/verify.ts index d70c34663..82e17283b 100644 --- a/l1-contracts/scripts/verify.ts +++ b/l1-contracts/scripts/verify.ts @@ -1,17 +1,31 @@ // hardhat import should be the first import in the file import * as hardhat from "hardhat"; import { deployedAddressesFromEnv } from "../src.ts/deploy-utils"; +import { getNumberFromEnv, getHashFromEnv, getAddressFromEnv, ethTestConfig } from "../src.ts/utils"; + +import { Interface } from "ethers/lib/utils"; +import { Deployer } from "../src.ts/deploy"; +import { Wallet } from "ethers"; +import { packSemver, unpackStringSemVer, web3Provider } from "./utils"; +import { getTokens } from "../src.ts/deploy-token"; + +const provider = web3Provider(); // eslint-disable-next-line @typescript-eslint/no-explicit-any function verifyPromise(address: string, constructorArguments?: Array, libraries?: object): Promise { return new Promise((resolve, reject) => { hardhat - .run("verify:verify", { address, constructorArguments, libraries }) + .run("verify:verify", { + address, + constructorArguments, + libraries, + }) .then(() => resolve(`Successfully verified ${address}`)) .catch((e) => reject(`Failed to verify ${address}\nError: ${e.message}`)); }); } +// Note: running all verifications in parallel might be too much for etherscan, comment out some of them if needed async function main() { if (process.env.CHAIN_ETH_NETWORK == "localhost") { console.log("Skip contract verification on localhost"); @@ -24,25 +38,21 @@ async function main() { const addresses = deployedAddressesFromEnv(); const promises = []; - // Contracts without constructor parameters - for (const address of [ - addresses.StateTransition.GettersFacet, - addresses.StateTransition.DiamondInit, - addresses.StateTransition.AdminFacet, - addresses.StateTransition.MailboxFacet, - addresses.StateTransition.ExecutorFacet, - addresses.StateTransition.Verifier, - ]) { - const promise = verifyPromise(address); - promises.push(promise); - } + const deployWalletAddress = "0x71d84c3404a6ae258E6471d4934B96a2033F9438"; + const deployWallet = Wallet.fromMnemonic(ethTestConfig.mnemonic, "m/44'/60'/0'/0/1").connect(provider); + const deployer = new Deployer({ + deployWallet, + addresses: deployedAddressesFromEnv(), + ownerAddress: deployWalletAddress, + verbose: true, + }); // TODO: Restore after switching to hardhat tasks (SMA-1711). // promises.push(verifyPromise(addresses.AllowList, [governor])); - // // Proxy + // Proxy // { - // // Create dummy deployer to get constructor parameters for diamond proxy + // Create dummy deployer to get constructor parameters for diamond proxy // const deployer = new Deployer({ // deployWallet: ethers.Wallet.createRandom(), // governorAddress: governor @@ -54,10 +64,112 @@ async function main() { // promises.push(promise); // } - // Bridges - const promise = verifyPromise(addresses.Bridges.ERC20BridgeImplementation, [addresses.StateTransition.DiamondProxy]); + const promise1 = verifyPromise(addresses.StateTransition.GenesisUpgrade); + promises.push(promise1); + + const executionDelay = getNumberFromEnv("CONTRACTS_VALIDATOR_TIMELOCK_EXECUTION_DELAY"); + const eraChainId = getNumberFromEnv("CONTRACTS_ERA_CHAIN_ID"); + const promise2 = verifyPromise(addresses.ValidatorTimeLock, [deployWalletAddress, executionDelay, eraChainId]); + promises.push(promise2); + + console.log("CONTRACTS_HYPERCHAIN_UPGRADE_ADDR", process.env.CONTRACTS_HYPERCHAIN_UPGRADE_ADDR); + const promise3 = verifyPromise(process.env.CONTRACTS_HYPERCHAIN_UPGRADE_ADDR); + promises.push(promise3); + + const promise5 = verifyPromise(addresses.TransparentProxyAdmin); + promises.push(promise5); + + // bridgehub + + const promise6 = verifyPromise(addresses.Bridgehub.BridgehubImplementation); + promises.push(promise6); + + const bridgehub = new Interface(hardhat.artifacts.readArtifactSync("Bridgehub").abi); + const initCalldata1 = bridgehub.encodeFunctionData("initialize", [deployWalletAddress]); + const promise7 = verifyPromise(addresses.Bridgehub.BridgehubProxy, [ + addresses.Bridgehub.BridgehubImplementation, + addresses.TransparentProxyAdmin, + initCalldata1, + ]); + promises.push(promise7); + + // stm + + // Contracts without constructor parameters + for (const address of [ + addresses.StateTransition.GettersFacet, + addresses.StateTransition.DiamondInit, + addresses.StateTransition.AdminFacet, + addresses.StateTransition.ExecutorFacet, + addresses.StateTransition.Verifier, + ]) { + const promise = verifyPromise(address); + promises.push(promise); + } + + const promise = verifyPromise(addresses.StateTransition.MailboxFacet, [eraChainId]); promises.push(promise); + const promise8 = verifyPromise(addresses.StateTransition.StateTransitionImplementation, [ + addresses.Bridgehub.BridgehubProxy, + getNumberFromEnv("CONTRACTS_MAX_NUMBER_OF_HYPERCHAINS"), + ]); + promises.push(promise8); + + const stateTransitionManager = new Interface(hardhat.artifacts.readArtifactSync("StateTransitionManager").abi); + const genesisBatchHash = getHashFromEnv("CONTRACTS_GENESIS_ROOT"); // TODO: confusing name + const genesisRollupLeafIndex = getNumberFromEnv("CONTRACTS_GENESIS_ROLLUP_LEAF_INDEX"); + const genesisBatchCommitment = getHashFromEnv("CONTRACTS_GENESIS_BATCH_COMMITMENT"); + const diamondCut = await deployer.initialZkSyncHyperchainDiamondCut([]); + const protocolVersion = packSemver(...unpackStringSemVer(process.env.CONTRACTS_GENESIS_PROTOCOL_SEMANTIC_VERSION)); + + const initCalldata2 = stateTransitionManager.encodeFunctionData("initialize", [ + { + owner: addresses.Governance, + validatorTimelock: addresses.ValidatorTimeLock, + genesisUpgrade: addresses.StateTransition.GenesisUpgrade, + genesisBatchHash, + genesisIndexRepeatedStorageChanges: genesisRollupLeafIndex, + genesisBatchCommitment, + diamondCut, + protocolVersion, + }, + ]); + + const promise9 = verifyPromise(addresses.StateTransition.StateTransitionProxy, [ + addresses.StateTransition.StateTransitionImplementation, + addresses.TransparentProxyAdmin, + initCalldata2, + ]); + promises.push(promise9); + + // bridges + // Note: do this manually and pass in to verify:verify the following: contract:"contracts/bridge/L1ERC20Bridge.sol:L1ERC20Bridge" + const promise10 = verifyPromise(addresses.Bridges.ERC20BridgeImplementation, [addresses.Bridges.SharedBridgeProxy]); + promises.push(promise10); + + const eraDiamondProxy = getAddressFromEnv("CONTRACTS_ERA_DIAMOND_PROXY_ADDR"); + const tokens = getTokens(); + const l1WethToken = tokens.find((token: { symbol: string }) => token.symbol == "WETH")!.address; + + const promise12 = verifyPromise(addresses.Bridges.SharedBridgeImplementation, [ + l1WethToken, + addresses.Bridgehub.BridgehubProxy, + eraChainId, + eraDiamondProxy, + ]); + promises.push(promise12); + const initCalldata4 = new Interface(hardhat.artifacts.readArtifactSync("L1SharedBridge").abi).encodeFunctionData( + "initialize", + [deployWalletAddress] + ); + const promise13 = verifyPromise(addresses.Bridges.SharedBridgeProxy, [ + addresses.Bridges.SharedBridgeImplementation, + addresses.TransparentProxyAdmin, + initCalldata4, + ]); + promises.push(promise13); + const messages = await Promise.allSettled(promises); for (const message of messages) { console.log(message.status == "fulfilled" ? message.value : message.reason); diff --git a/l1-contracts/src.ts/deploy-process.ts b/l1-contracts/src.ts/deploy-process.ts index 90cb030ef..f495bb69a 100644 --- a/l1-contracts/src.ts/deploy-process.ts +++ b/l1-contracts/src.ts/deploy-process.ts @@ -76,7 +76,8 @@ export async function registerHyperchain( extraFacets: FacetCut[], gasPrice: BigNumberish, baseTokenName?: string, - chainId?: string + chainId?: string, + useGovernance: boolean = false ) { const testnetTokens = getTokens(); @@ -85,7 +86,15 @@ export async function registerHyperchain( : ADDRESS_ONE; if (!(await deployer.bridgehubContract(deployer.deployWallet).tokenIsRegistered(baseTokenAddress))) { - await deployer.registerToken(baseTokenAddress); + await deployer.registerToken(baseTokenAddress, useGovernance); } - await deployer.registerHyperchain(baseTokenAddress, validiumMode, extraFacets, gasPrice, null, chainId); + await deployer.registerHyperchain( + baseTokenAddress, + validiumMode, + extraFacets, + gasPrice, + null, + chainId, + useGovernance + ); } diff --git a/l1-contracts/src.ts/deploy-test-process.ts b/l1-contracts/src.ts/deploy-test-process.ts index a831197b6..fe383e755 100644 --- a/l1-contracts/src.ts/deploy-test-process.ts +++ b/l1-contracts/src.ts/deploy-test-process.ts @@ -38,15 +38,14 @@ const addressConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/addresses.js const testnetTokenPath = `${testConfigPath}/hardhat.json`; export async function loadDefaultEnvVarsForTests(deployWallet: Wallet) { - process.env.CONTRACTS_GENESIS_PROTOCOL_VERSION = (21).toString(); - process.env.CONTRACTS_GENESIS_ROOT = ethers.constants.HashZero; - process.env.CONTRACTS_GENESIS_ROLLUP_LEAF_INDEX = "0"; - process.env.CONTRACTS_GENESIS_BATCH_COMMITMENT = ethers.constants.HashZero; + process.env.CONTRACTS_GENESIS_PROTOCOL_SEMANTIC_VERSION = "0.21.0"; + process.env.CONTRACTS_GENESIS_ROOT = "0x0000000000000000000000000000000000000000000000000000000000000001"; + process.env.CONTRACTS_GENESIS_ROLLUP_LEAF_INDEX = "1"; + process.env.CONTRACTS_GENESIS_BATCH_COMMITMENT = "0x0000000000000000000000000000000000000000000000000000000000000001"; // process.env.CONTRACTS_GENESIS_UPGRADE_ADDR = ADDRESS_ONE; process.env.CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT = "72000000"; - process.env.CONTRACTS_RECURSION_NODE_LEVEL_VK_HASH = ethers.constants.HashZero; - process.env.CONTRACTS_RECURSION_LEAF_LEVEL_VK_HASH = ethers.constants.HashZero; - process.env.CONTRACTS_RECURSION_CIRCUITS_SET_VKS_HASH = ethers.constants.HashZero; + process.env.CONTRACTS_FRI_RECURSION_NODE_LEVEL_VK_HASH = ethers.constants.HashZero; + process.env.CONTRACTS_FRI_RECURSION_LEAF_LEVEL_VK_HASH = ethers.constants.HashZero; // process.env.CONTRACTS_SHARED_BRIDGE_UPGRADE_STORAGE_SWITCH = "1"; process.env.ETH_CLIENT_CHAIN_ID = (await deployWallet.getChainId()).toString(); process.env.CONTRACTS_ERA_CHAIN_ID = "270"; @@ -261,18 +260,11 @@ export class EraDeployer extends Deployer { ); facetCuts = facetCuts.concat(extraFacets ?? []); - const verifierParams = - process.env["CONTRACTS_PROVER_AT_GENESIS"] == "fri" - ? { - recursionNodeLevelVkHash: getHashFromEnv("CONTRACTS_FRI_RECURSION_NODE_LEVEL_VK_HASH"), - recursionLeafLevelVkHash: getHashFromEnv("CONTRACTS_FRI_RECURSION_LEAF_LEVEL_VK_HASH"), - recursionCircuitsSetVksHash: "0x0000000000000000000000000000000000000000000000000000000000000000", - } - : { - recursionNodeLevelVkHash: getHashFromEnv("CONTRACTS_RECURSION_NODE_LEVEL_VK_HASH"), - recursionLeafLevelVkHash: getHashFromEnv("CONTRACTS_RECURSION_LEAF_LEVEL_VK_HASH"), - recursionCircuitsSetVksHash: getHashFromEnv("CONTRACTS_RECURSION_CIRCUITS_SET_VKS_HASH"), - }; + const verifierParams = { + recursionNodeLevelVkHash: getHashFromEnv("CONTRACTS_FRI_RECURSION_NODE_LEVEL_VK_HASH"), + recursionLeafLevelVkHash: getHashFromEnv("CONTRACTS_FRI_RECURSION_LEAF_LEVEL_VK_HASH"), + recursionCircuitsSetVksHash: "0x0000000000000000000000000000000000000000000000000000000000000000", + }; const priorityTxMaxGasLimit = getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT"); const DiamondInit = new Interface(hardhat.artifacts.readArtifactSync("DiamondInit").abi); diff --git a/l1-contracts/src.ts/deploy-utils.ts b/l1-contracts/src.ts/deploy-utils.ts index 8e810e652..80c41b195 100644 --- a/l1-contracts/src.ts/deploy-utils.ts +++ b/l1-contracts/src.ts/deploy-utils.ts @@ -71,7 +71,9 @@ export async function deployBytecodeViaCreate2( const receipt = await tx.wait(); const gasUsed = receipt.gasUsed; - log(`${contractName} deployed, gasUsed: ${gasUsed.toString()}`); + log( + `${contractName} deployed, gasUsed: ${gasUsed.toString()}, tx hash: ${tx.hash}, expected address: ${expectedAddress}` + ); const deployedBytecodeAfter = await deployWallet.provider.getCode(expectedAddress); if (ethers.utils.hexDataLength(deployedBytecodeAfter) == 0) { diff --git a/l1-contracts/src.ts/deploy.ts b/l1-contracts/src.ts/deploy.ts index 3039e2c49..4ff958560 100644 --- a/l1-contracts/src.ts/deploy.ts +++ b/l1-contracts/src.ts/deploy.ts @@ -6,7 +6,12 @@ import { ethers } from "ethers"; import { hexlify, Interface } from "ethers/lib/utils"; import type { DeployedAddresses } from "./deploy-utils"; import { deployedAddressesFromEnv, deployBytecodeViaCreate2, deployViaCreate2 } from "./deploy-utils"; -import { readBatchBootloaderBytecode, readSystemContractsBytecode, SYSTEM_CONFIG } from "../scripts/utils"; +import { + packSemver, + readBatchBootloaderBytecode, + readSystemContractsBytecode, + unpackStringSemVer, +} from "../scripts/utils"; import { getTokens } from "./deploy-token"; import { ADDRESS_ONE, @@ -16,6 +21,7 @@ import { PubdataPricingMode, hashL2Bytecode, DIAMOND_CUT_DATA_ABI_STRING, + compileInitialCutHash, } from "./utils"; import { IBridgehubFactory } from "../typechain/IBridgehubFactory"; import { IGovernanceFactory } from "../typechain/IGovernanceFactory"; @@ -29,9 +35,10 @@ import { L1SharedBridgeFactory } from "../typechain/L1SharedBridgeFactory"; import { SingletonFactoryFactory } from "../typechain/SingletonFactoryFactory"; import { ValidatorTimelockFactory } from "../typechain/ValidatorTimelockFactory"; import type { FacetCut } from "./diamondCut"; -import { diamondCut, getCurrentFacetCutsForAdd } from "./diamondCut"; +import { getCurrentFacetCutsForAdd } from "./diamondCut"; import { ERC20Factory } from "../typechain"; +import type { Contract, Overrides } from "@ethersproject/contracts"; let L2_BOOTLOADER_BYTECODE_HASH: string; let L2_DEFAULT_ACCOUNT_BYTECODE_HASH: string; @@ -45,6 +52,14 @@ export interface DeployerConfig { defaultAccountBytecodeHash?: string; } +export interface Operation { + calls: { target: string; value: BigNumberish; data: string }[]; + predecessor: string; + salt: string; +} + +export type OperationOrString = Operation | string; + export class Deployer { public addresses: DeployedAddresses; public deployWallet: Wallet; @@ -77,56 +92,23 @@ export class Deployer { ); facetCuts = facetCuts.concat(extraFacets ?? []); - const verifierParams = - process.env["CONTRACTS_PROVER_AT_GENESIS"] == "fri" - ? { - recursionNodeLevelVkHash: getHashFromEnv("CONTRACTS_FRI_RECURSION_NODE_LEVEL_VK_HASH"), - recursionLeafLevelVkHash: getHashFromEnv("CONTRACTS_FRI_RECURSION_LEAF_LEVEL_VK_HASH"), - recursionCircuitsSetVksHash: "0x0000000000000000000000000000000000000000000000000000000000000000", - } - : { - recursionNodeLevelVkHash: getHashFromEnv("CONTRACTS_RECURSION_NODE_LEVEL_VK_HASH"), - recursionLeafLevelVkHash: getHashFromEnv("CONTRACTS_RECURSION_LEAF_LEVEL_VK_HASH"), - recursionCircuitsSetVksHash: getHashFromEnv("CONTRACTS_RECURSION_CIRCUITS_SET_VKS_HASH"), - }; - const priorityTxMaxGasLimit = getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT"); - const DiamondInit = new Interface(hardhat.artifacts.readArtifactSync("DiamondInit").abi); - - const feeParams = { - pubdataPricingMode: PubdataPricingMode.Rollup, - batchOverheadL1Gas: SYSTEM_CONFIG.priorityTxBatchOverheadL1Gas, - maxPubdataPerBatch: SYSTEM_CONFIG.priorityTxPubdataPerBatch, - priorityTxMaxPubdata: SYSTEM_CONFIG.priorityTxMaxPubdata, - maxL2GasPerBatch: SYSTEM_CONFIG.priorityTxMaxGasPerBatch, - minimalL2GasPrice: SYSTEM_CONFIG.priorityTxMinimalGasPrice, + const verifierParams = { + recursionNodeLevelVkHash: getHashFromEnv("CONTRACTS_FRI_RECURSION_NODE_LEVEL_VK_HASH"), + recursionLeafLevelVkHash: getHashFromEnv("CONTRACTS_FRI_RECURSION_LEAF_LEVEL_VK_HASH"), + recursionCircuitsSetVksHash: "0x0000000000000000000000000000000000000000000000000000000000000000", }; + const priorityTxMaxGasLimit = getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT"); - const diamondInitCalldata = DiamondInit.encodeFunctionData("initialize", [ - // these first values are set in the contract - { - chainId: "0x0000000000000000000000000000000000000000000000000000000000000001", - bridgehub: "0x0000000000000000000000000000000000001234", - stateTransitionManager: "0x0000000000000000000000000000000000002234", - protocolVersion: "0x0000000000000000000000000000000000002234", - admin: "0x0000000000000000000000000000000000003234", - validatorTimelock: "0x0000000000000000000000000000000000004234", - baseToken: "0x0000000000000000000000000000000000004234", - baseTokenBridge: "0x0000000000000000000000000000000000004234", - storedBatchZero: "0x0000000000000000000000000000000000000000000000000000000000005432", - verifier: this.addresses.StateTransition.Verifier, - verifierParams, - l2BootloaderBytecodeHash: L2_BOOTLOADER_BYTECODE_HASH, - l2DefaultAccountBytecodeHash: L2_DEFAULT_ACCOUNT_BYTECODE_HASH, - priorityTxMaxGasLimit, - feeParams, - blobVersionedHashRetriever: this.addresses.BlobVersionedHashRetriever, - }, - ]); - - return diamondCut( + return compileInitialCutHash( facetCuts, + verifierParams, + L2_BOOTLOADER_BYTECODE_HASH, + L2_DEFAULT_ACCOUNT_BYTECODE_HASH, + this.addresses.StateTransition.Verifier, + this.addresses.BlobVersionedHashRetriever, + +priorityTxMaxGasLimit, this.addresses.StateTransition.DiamondInit, - "0x" + diamondInitCalldata.slice(2 + (4 + 9 * 32) * 2) + false ); } @@ -222,9 +204,9 @@ export class Deployer { public async deployTransparentProxyAdmin(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { ethTxOptions.gasLimit ??= 10_000_000; if (this.verbose) { - console.log("Deploying Proxy Admin factory"); + console.log("Deploying Proxy Admin"); } - + // Note: we cannot deploy using Create2, as the owner of the ProxyAdmin is msg.sender const contractFactory = await hardhat.ethers.getContractFactory("ProxyAdmin", { signer: this.deployWallet, }); @@ -233,8 +215,12 @@ export class Deployer { const rec = await proxyAdmin.deployTransaction.wait(); if (this.verbose) { + console.log( + `Proxy admin deployed, gasUsed: ${rec.gasUsed.toString()}, tx hash ${rec.transactionHash}, expected address: ${ + proxyAdmin.address + }` + ); console.log(`CONTRACTS_TRANSPARENT_PROXY_ADMIN_ADDR=${proxyAdmin.address}`); - console.log(`Proxy admin deployed, gasUsed: ${rec.gasUsed.toString()}`); } this.addresses.TransparentProxyAdmin = proxyAdmin.address; @@ -301,19 +287,23 @@ export class Deployer { const genesisRollupLeafIndex = getNumberFromEnv("CONTRACTS_GENESIS_ROLLUP_LEAF_INDEX"); const genesisBatchCommitment = getHashFromEnv("CONTRACTS_GENESIS_BATCH_COMMITMENT"); const diamondCut = await this.initialZkSyncHyperchainDiamondCut(extraFacets); - const protocolVersion = getNumberFromEnv("CONTRACTS_GENESIS_PROTOCOL_VERSION"); + const protocolVersion = packSemver(...unpackStringSemVer(process.env.CONTRACTS_GENESIS_PROTOCOL_SEMANTIC_VERSION)); const stateTransitionManager = new Interface(hardhat.artifacts.readArtifactSync("StateTransitionManager").abi); + const chainCreationParams = { + genesisUpgrade: this.addresses.StateTransition.GenesisUpgrade, + genesisBatchHash, + genesisIndexRepeatedStorageChanges: genesisRollupLeafIndex, + genesisBatchCommitment, + diamondCut, + }; + const initCalldata = stateTransitionManager.encodeFunctionData("initialize", [ { owner: this.addresses.Governance, validatorTimelock: this.addresses.ValidatorTimeLock, - genesisUpgrade: this.addresses.StateTransition.GenesisUpgrade, - genesisBatchHash, - genesisIndexRepeatedStorageChanges: genesisRollupLeafIndex, - genesisBatchCommitment, - diamondCut, + chainCreationParams, protocolVersion, }, ]); @@ -445,21 +435,57 @@ export class Deployer { } } + public async executeDirectOrGovernance( + useGovernance: boolean, + contract: Contract, + fname: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + fargs: any[], + value: BigNumberish, + overrides?: Overrides, + printOperation: boolean = false + ): Promise { + if (useGovernance) { + const cdata = contract.interface.encodeFunctionData(fname, fargs); + return this.executeUpgrade(contract.address, value, cdata, printOperation); + } else { + const tx: ethers.ContractTransaction = await contract[fname](...fargs, ...(overrides ? [overrides] : [])); + return await tx.wait(); + } + } + /// this should be only use for local testing - public async executeUpgrade(targetAddress: string, value: BigNumberish, callData: string) { + public async executeUpgrade( + targetAddress: string, + value: BigNumberish, + callData: string, + printOperation: boolean = false + ) { const governance = IGovernanceFactory.connect(this.addresses.Governance, this.deployWallet); const operation = { calls: [{ target: targetAddress, value: value, data: callData }], predecessor: ethers.constants.HashZero, salt: ethers.utils.hexlify(ethers.utils.randomBytes(32)), }; + if (printOperation) { + console.log("Operation:", operation); + console.log( + "Schedule operation: ", + governance.interface.encodeFunctionData("scheduleTransparent", [operation, 0]) + ); + console.log( + `Execute operation value: ${value}, calldata`, + governance.interface.encodeFunctionData("execute", [operation]) + ); + return; + } const scheduleTx = await governance.scheduleTransparent(operation, 0); await scheduleTx.wait(); if (this.verbose) { console.log("Upgrade scheduled"); } const executeTX = await governance.execute(operation, { value: value }); - await executeTX.wait(); + const receipt = await executeTX.wait(); if (this.verbose) { console.log( "Upgrade with target ", @@ -468,6 +494,7 @@ export class Deployer { await governance.isOperationDone(await governance.hashOperation(operation)) ); } + return receipt; } // used for testing, mimics original deployment process. @@ -643,11 +670,13 @@ export class Deployer { public async registerStateTransitionManager() { const bridgehub = this.bridgehubContract(this.deployWallet); - const tx = await bridgehub.addStateTransitionManager(this.addresses.StateTransition.StateTransitionProxy); + if (!(await bridgehub.stateTransitionManagerIsRegistered(this.addresses.StateTransition.StateTransitionProxy))) { + const tx = await bridgehub.addStateTransitionManager(this.addresses.StateTransition.StateTransitionProxy); - const receipt = await tx.wait(); - if (this.verbose) { - console.log(`StateTransition System registered, gas used: ${receipt.gasUsed.toString()}`); + const receipt = await tx.wait(); + if (this.verbose) { + console.log(`StateTransition System registered, gas used: ${receipt.gasUsed.toString()}`); + } } } @@ -657,7 +686,8 @@ export class Deployer { extraFacets?: FacetCut[], gasPrice?: BigNumberish, nonce?, - predefinedChainId?: string + predefinedChainId?: string, + useGovernance: boolean = false ) { const gasLimit = 10_000_000; @@ -671,24 +701,34 @@ export class Deployer { const diamondCutData = await this.initialZkSyncHyperchainDiamondCut(extraFacets); const initialDiamondCut = new ethers.utils.AbiCoder().encode([DIAMOND_CUT_DATA_ABI_STRING], [diamondCutData]); - const tx = await bridgehub.createNewChain( - inputChainId, - this.addresses.StateTransition.StateTransitionProxy, - baseTokenAddress, - Date.now(), - admin, - initialDiamondCut, + const receipt = await this.executeDirectOrGovernance( + useGovernance, + bridgehub, + "createNewChain", + [ + inputChainId, + this.addresses.StateTransition.StateTransitionProxy, + baseTokenAddress, + Date.now(), + admin, + initialDiamondCut, + ], + 0, { gasPrice, nonce, gasLimit, } ); - const receipt = await tx.wait(); + const chainId = receipt.logs.find((log) => log.topics[0] == bridgehub.interface.getEventTopic("NewChain")) .topics[1]; nonce++; + if (useGovernance) { + // deploying through governance requires two transactions + nonce++; + } this.addresses.BaseToken = baseTokenAddress; @@ -750,7 +790,7 @@ export class Deployer { } if (validiumMode) { - const tx5 = await diamondProxy.setValidiumMode(PubdataPricingMode.Validium); + const tx5 = await diamondProxy.setPubdataPricingMode(PubdataPricingMode.Validium); const receipt5 = await tx5.wait(); if (this.verbose) { console.log(`Validium mode set, gas used: ${receipt5.gasUsed.toString()}`); @@ -758,11 +798,28 @@ export class Deployer { } } - public async registerToken(tokenAddress: string) { + public async transferAdminFromDeployerToGovernance() { + const stm = this.stateTransitionManagerContract(this.deployWallet); + const diamondProxyAddress = await stm.getHyperchain(this.chainId); + const hyperchain = IZkSyncHyperchainFactory.connect(diamondProxyAddress, this.deployWallet); + + const receipt = await (await hyperchain.setPendingAdmin(this.addresses.Governance)).wait(); + if (this.verbose) { + console.log(`Governance set as pending admin, gas used: ${receipt.gasUsed.toString()}`); + } + + await this.executeUpgrade(hyperchain.address, 0, hyperchain.interface.encodeFunctionData("acceptAdmin"), false); + + if (this.verbose) { + console.log("Pending admin successfully accepted"); + } + } + + public async registerToken(tokenAddress: string, useGovernance: boolean = false) { const bridgehub = this.bridgehubContract(this.deployWallet); - const tx = await bridgehub.addToken(tokenAddress); - const receipt = await tx.wait(); + const receipt = await this.executeDirectOrGovernance(useGovernance, bridgehub, "addToken", [tokenAddress], 0); + if (this.verbose) { console.log(`Token ${tokenAddress} was registered, gas used: ${receipt.gasUsed.toString()}`); } diff --git a/l1-contracts/src.ts/hyperchain-upgrade.ts b/l1-contracts/src.ts/hyperchain-upgrade.ts index 9b37db572..56e8573a9 100644 --- a/l1-contracts/src.ts/hyperchain-upgrade.ts +++ b/l1-contracts/src.ts/hyperchain-upgrade.ts @@ -1,27 +1,21 @@ // hardhat import should be the first import in the file // eslint-disable-next-line @typescript-eslint/no-unused-vars import * as hardhat from "hardhat"; +import * as path from "path"; import "@nomiclabs/hardhat-ethers"; -// import * as path from "path"; import type { BigNumberish } from "ethers"; -import { BigNumber, ethers } from "ethers"; +import { ethers } from "ethers"; -import type { DiamondCut } from "./diamondCut"; -import { getFacetCutsForUpgrade } from "./diamondCut"; - -import { getTokens } from "./deploy-token"; import type { Deployer } from "./deploy"; import type { ITransparentUpgradeableProxy } from "../typechain/ITransparentUpgradeableProxy"; import { ITransparentUpgradeableProxyFactory } from "../typechain/ITransparentUpgradeableProxyFactory"; - -import { L1SharedBridgeFactory, StateTransitionManagerFactory } from "../typechain"; +import { StateTransitionManagerFactory, L1SharedBridgeFactory, ValidatorTimelockFactory } from "../typechain"; import { Interface } from "ethers/lib/utils"; -import { ADDRESS_ONE, getAddressFromEnv } from "./utils"; -import type { L2CanonicalTransaction, ProposedUpgrade, VerifierParams } from "./utils"; +import { ADDRESS_ONE, getAddressFromEnv, readBytecode } from "./utils"; import { REQUIRED_L2_GAS_PRICE_PER_PUBDATA, @@ -31,22 +25,22 @@ import { } from "../../l2-contracts/src/utils"; import { ETH_ADDRESS_IN_CONTRACTS } from "zksync-ethers/build/src/utils"; -const SYSTEM_UPGRADE_TX_TYPE = 254; -const FORCE_DEPLOYER_ADDRESS = "0x0000000000000000000000000000000000008007"; - -const BEACON_PROXY_BYTECODE = ethers.constants.HashZero; +const contractArtifactsPath = path.join(process.env.ZKSYNC_HOME as string, "contracts/l2-contracts/artifacts-zk/"); +const openzeppelinBeaconProxyArtifactsPath = path.join(contractArtifactsPath, "@openzeppelin/contracts/proxy/beacon"); +export const BEACON_PROXY_BYTECODE = readBytecode(openzeppelinBeaconProxyArtifactsPath, "BeaconProxy"); /// In the hardhat tests we do the upgrade all at once. /// On localhost/stage/.. we will call the components and send the calldata to Governance manually export async function upgradeToHyperchains( deployer: Deployer, gasPrice: BigNumberish, + printFileName?: string, create2Salt?: string, nonce?: number ) { await upgradeToHyperchains1(deployer, gasPrice, create2Salt, nonce); - await upgradeToHyperchains2(deployer, gasPrice); - await upgradeToHyperchains3(deployer); + await upgradeToHyperchains2(deployer, gasPrice, printFileName); + await upgradeToHyperchains3(deployer, printFileName); } /// this just deploys the contract ( we do it here instead of using the protocol-upgrade tool, since we are deploying more than just facets, the Bridgehub, STM, etc.) @@ -56,67 +50,163 @@ export async function upgradeToHyperchains1( create2Salt?: string, nonce?: number ) { + /// we manually override the governance address so that we can set the variables + deployer.addresses.Governance = deployer.deployWallet.address; // does not interfere with existing system // note other contract were already deployed if (deployer.verbose) { console.log("Deploying new contracts"); } await deployNewContracts(deployer, gasPrice, create2Salt, nonce); + + // register Era in Bridgehub, STM + const stateTransitionManager = deployer.stateTransitionManagerContract(deployer.deployWallet); + + if (deployer.verbose) { + console.log("Registering Era in stateTransitionManager"); + } + const txRegister = await stateTransitionManager.registerAlreadyDeployedHyperchain( + deployer.chainId, + deployer.addresses.StateTransition.DiamondProxy + ); + + await txRegister.wait(); + + const bridgehub = deployer.bridgehubContract(deployer.deployWallet); + if (deployer.verbose) { + console.log("Registering Era in Bridgehub"); + } + + const tx = await bridgehub.createNewChain( + deployer.chainId, + deployer.addresses.StateTransition.StateTransitionProxy, + ETH_ADDRESS_IN_CONTRACTS, + ethers.constants.HashZero, + deployer.addresses.Governance, + ethers.constants.HashZero, + { gasPrice } + ); + await tx.wait(); + + if (deployer.verbose) { + console.log("Setting L1Erc20Bridge data in shared bridge"); + } + const sharedBridge = L1SharedBridgeFactory.connect( + deployer.addresses.Bridges.SharedBridgeProxy, + deployer.deployWallet + ); + const tx1 = await sharedBridge.setL1Erc20Bridge(deployer.addresses.Bridges.ERC20BridgeProxy); + await tx1.wait(); + + if (deployer.verbose) { + console.log("Initializing l2 bridge in shared bridge", deployer.addresses.Bridges.L2SharedBridgeProxy); + } + const tx2 = await sharedBridge.initializeChainGovernance( + deployer.chainId, + deployer.addresses.Bridges.L2SharedBridgeProxy + ); + await tx2.wait(); + + if (deployer.verbose) { + console.log("Setting Validator timelock in STM"); + } + const stm = StateTransitionManagerFactory.connect( + deployer.addresses.StateTransition.StateTransitionProxy, + deployer.deployWallet + ); + const tx3 = await stm.setValidatorTimelock(deployer.addresses.ValidatorTimeLock); + await tx3.wait(); + + if (deployer.verbose) { + console.log("Setting dummy STM in Validator timelock"); + } + + const ethTxOptions: ethers.providers.TransactionRequest = {}; + ethTxOptions.gasLimit ??= 10_000_000; + const migrationSTMAddress = await deployer.deployViaCreate2( + "MigrationSTM", + [deployer.deployWallet.address], + create2Salt, + ethTxOptions + ); + console.log("Migration STM address", migrationSTMAddress); + + const validatorTimelock = ValidatorTimelockFactory.connect( + deployer.addresses.ValidatorTimeLock, + deployer.deployWallet + ); + const tx4 = await validatorTimelock.setStateTransitionManager(migrationSTMAddress); + await tx4.wait(); + + const validatorOneAddress = getAddressFromEnv("ETH_SENDER_SENDER_OPERATOR_COMMIT_ETH_ADDR"); + const validatorTwoAddress = getAddressFromEnv("ETH_SENDER_SENDER_OPERATOR_BLOBS_ETH_ADDR"); + const tx5 = await validatorTimelock.addValidator(deployer.chainId, validatorOneAddress, { gasPrice }); + const receipt5 = await tx5.wait(); + const tx6 = await validatorTimelock.addValidator(deployer.chainId, validatorTwoAddress, { gasPrice }); + const receipt6 = await tx6.wait(); + + const tx7 = await validatorTimelock.setStateTransitionManager( + deployer.addresses.StateTransition.StateTransitionProxy + ); + const receipt7 = await tx7.wait(); + if (deployer.verbose) { + console.log( + "Validators added, stm transferred back", + receipt5.transactionHash, + receipt6.transactionHash, + receipt7.transactionHash + ); + } } -// this simulates the main part of the upgrade, the diamond cut, registration into the Bridgehub and STM, and the bridge upgrade -// before we call this we need to generate the facet cuts using the protocol upgrade tool, on hardhat we test the dummy diamondCut -export async function upgradeToHyperchains2(deployer: Deployer, gasPrice: BigNumberish) { +// this should be called after the diamond cut has been proposed and executed +// this simulates the main part of the upgrade, registration into the Bridgehub and STM, and the bridge upgrade +export async function upgradeToHyperchains2(deployer: Deployer, gasPrice: BigNumberish, printFileName?: string) { // upgrading system contracts on Era only adds setChainId in systemContext, does not interfere with anything // we first upgrade the DiamondProxy. the Mailbox is backwards compatible, so the L1ERC20 and other bridges should still work. // this requires the sharedBridge to be deployed. // In theory, the L1SharedBridge deposits should be disabled until the L2Bridge is upgraded. // However, without the Portal, UI being upgraded it does not matter (nobody will call it, they will call the legacy bridge) - if (deployer.verbose) { - console.log("Integrating Era into Bridgehub and upgrading L2 system contract"); - } - await integrateEraIntoBridgehubAndUpgradeL2SystemContract(deployer, gasPrice); // details for L2 system contract upgrade are part of the infrastructure/protocol_upgrade tool // the L2Bridge and L1ERC20Bridge should be updated relatively in sync, as new messages might not be parsed correctly by the old bridge. // however new bridges can parse old messages. L1->L2 messages are faster, so L2 side is upgraded first. if (deployer.verbose) { console.log("Upgrading L2 bridge"); } - await upgradeL2Bridge(deployer); + await upgradeL2Bridge(deployer, gasPrice, printFileName); + + if (deployer.verbose) { + console.log("Transferring L1 ERC20 bridge to proxy admin"); + } + await transferERC20BridgeToProxyAdmin(deployer, gasPrice, printFileName); - if (process.env.CHAIN_ETH_NETWORK === "localhost") { + if (process.env.CHAIN_ETH_NETWORK != "hardhat") { if (deployer.verbose) { console.log("Upgrading L1 ERC20 bridge"); } - await upgradeL1ERC20Bridge(deployer); + await upgradeL1ERC20Bridge(deployer, gasPrice, printFileName); } - - // note, withdrawals will not work until this step, but deposits will - if (deployer.verbose) { - console.log("Migrating assets from L1 ERC20 bridge and ChainBalance"); - } - await migrateAssets(deployer); } // This sets the Shared Bridge parameters. We need to do this separately, as these params will be known after the upgrade -export async function upgradeToHyperchains3(deployer: Deployer) { +export async function upgradeToHyperchains3(deployer: Deployer, printFileName?: string) { const sharedBridge = L1SharedBridgeFactory.connect( deployer.addresses.Bridges.SharedBridgeProxy, deployer.deployWallet ); const data2 = sharedBridge.interface.encodeFunctionData("setEraPostDiamondUpgradeFirstBatch", [ - process.env.CONTRACTS_ERA_POST_DIAMOND_UPGRADE_FIRST_BATCH ?? 1, + process.env.CONTRACTS_ERA_POST_DIAMOND_UPGRADE_FIRST_BATCH, ]); const data3 = sharedBridge.interface.encodeFunctionData("setEraPostLegacyBridgeUpgradeFirstBatch", [ - process.env.CONTRACTS_ERA_POST_LEGACY_BRIDGE_UPGRADE_FIRST_BATCH ?? 1, + process.env.CONTRACTS_ERA_POST_LEGACY_BRIDGE_UPGRADE_FIRST_BATCH, ]); const data4 = sharedBridge.interface.encodeFunctionData("setEraLegacyBridgeLastDepositTime", [ - process.env.CONTRACTS_ERA_LEGACY_UPGRADE_LAST_DEPOSIT_BATCH ?? 1, - process.env.CONTRACTS_ERA_LEGACY_UPGRADE_LAST_DEPOSIT_TX_NUMBER ?? 0, + process.env.CONTRACTS_ERA_LEGACY_UPGRADE_LAST_DEPOSIT_BATCH, + process.env.CONTRACTS_ERA_LEGACY_UPGRADE_LAST_DEPOSIT_TX_NUMBER, ]); - await deployer.executeUpgrade(deployer.addresses.Bridges.SharedBridgeProxy, 0, data2); - await deployer.executeUpgrade(deployer.addresses.Bridges.SharedBridgeProxy, 0, data3); - await deployer.executeUpgrade(deployer.addresses.Bridges.SharedBridgeProxy, 0, data4); + await deployer.executeUpgrade(deployer.addresses.Bridges.SharedBridgeProxy, 0, data2, printFileName); + await deployer.executeUpgrade(deployer.addresses.Bridges.SharedBridgeProxy, 0, data3, printFileName); + await deployer.executeUpgrade(deployer.addresses.Bridges.SharedBridgeProxy, 0, data4, printFileName); } async function deployNewContracts(deployer: Deployer, gasPrice: BigNumberish, create2Salt?: string, nonce?: number) { @@ -129,21 +219,19 @@ async function deployNewContracts(deployer: Deployer, gasPrice: BigNumberish, cr gasPrice, nonce, }); - nonce++; - await deployer.deployValidatorTimelock(create2Salt, { gasPrice, nonce }); - nonce++; + await deployer.deployValidatorTimelock(create2Salt, { gasPrice }); await deployer.deployHyperchainsUpgrade(create2Salt, { gasPrice, - nonce, }); - nonce++; - await deployer.deployVerifier(create2Salt, { gasPrice, nonce }); + await deployer.deployVerifier(create2Salt, { gasPrice }); if (process.env.CHAIN_ETH_NETWORK != "hardhat") { await deployer.deployTransparentProxyAdmin(create2Salt, { gasPrice }); } + // console.log("Proxy admin is already deployed (not via Create2)", deployer.addresses.TransparentProxyAdmin); + // console.log("CONTRACTS_TRANSPARENT_PROXY_ADMIN_ADDR=0xf2c1d17441074FFb18E9A918db81A17dB1752146"); await deployer.deployBridgehubContract(create2Salt, gasPrice); await deployer.deployStateTransitionManagerContract(create2Salt, [], gasPrice); @@ -151,180 +239,16 @@ async function deployNewContracts(deployer: Deployer, gasPrice: BigNumberish, cr await deployer.deploySharedBridgeContracts(create2Salt, gasPrice); await deployer.deployERC20BridgeImplementation(create2Salt, { gasPrice }); - await deployer.deployERC20BridgeProxy(create2Salt, { gasPrice }); -} - -async function integrateEraIntoBridgehubAndUpgradeL2SystemContract(deployer: Deployer, gasPrice: BigNumberish) { - // publish L2 system contracts - if (process.env.CHAIN_ETH_NETWORK === "hardhat") { - // era facet cut - const newProtocolVersion = 24; - const toAddress: string = ethers.constants.AddressZero; - const calldata: string = ethers.constants.HashZero; - const l2ProtocolUpgradeTx: L2CanonicalTransaction = { - txType: SYSTEM_UPGRADE_TX_TYPE, - from: FORCE_DEPLOYER_ADDRESS, - to: toAddress, - gasLimit: 72_000_000, - gasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - maxFeePerGas: 0, - maxPriorityFeePerGas: 0, - paymaster: 0, - nonce: newProtocolVersion, - value: 0, - reserved: [0, 0, 0, 0], - data: calldata, - signature: "0x", - factoryDeps: [], - paymasterInput: "0x", - reservedDynamic: "0x", - }; - const upgradeTimestamp = BigNumber.from(100); - const verifierParams: VerifierParams = { - recursionNodeLevelVkHash: ethers.constants.HashZero, - recursionLeafLevelVkHash: ethers.constants.HashZero, - recursionCircuitsSetVksHash: ethers.constants.HashZero, - }; - const postUpgradeCalldata = new ethers.utils.AbiCoder().encode( - ["uint256", "address", "address", "address"], - [ - deployer.chainId, - deployer.addresses.Bridgehub.BridgehubProxy, - deployer.addresses.StateTransition.StateTransitionProxy, - deployer.addresses.Bridges.SharedBridgeProxy, - ] - ); - const proposedUpgrade: ProposedUpgrade = { - l2ProtocolUpgradeTx, - factoryDeps: [], - bootloaderHash: ethers.constants.HashZero, - defaultAccountHash: ethers.constants.HashZero, - verifier: ethers.constants.AddressZero, - verifierParams: verifierParams, - l1ContractsUpgradeCalldata: ethers.constants.HashZero, - postUpgradeCalldata: postUpgradeCalldata, - upgradeTimestamp: upgradeTimestamp, - newProtocolVersion: 24, - }; - const upgradeHyperchains = new Interface(hardhat.artifacts.readArtifactSync("UpgradeHyperchains").abi); - const defaultUpgradeData = upgradeHyperchains.encodeFunctionData("upgrade", [proposedUpgrade]); - - const facetCuts = await getFacetCutsForUpgrade( - deployer.deployWallet, - deployer.addresses.StateTransition.DiamondProxy, - deployer.addresses.StateTransition.AdminFacet, - deployer.addresses.StateTransition.GettersFacet, - deployer.addresses.StateTransition.MailboxFacet, - deployer.addresses.StateTransition.ExecutorFacet - ); - const diamondCut: DiamondCut = { - facetCuts, - initAddress: deployer.addresses.StateTransition.DefaultUpgrade, - initCalldata: defaultUpgradeData, - }; - const adminFacet = new Interface(hardhat.artifacts.readArtifactSync("DummyAdminFacetNoOverlap").abi); - - const data = adminFacet.encodeFunctionData("executeUpgradeNoOverlap", [diamondCut]); - await deployer.executeUpgrade(deployer.addresses.StateTransition.DiamondProxy, 0, data); - } - // register Era in Bridgehub, STM - const stateTransitionManager = deployer.stateTransitionManagerContract(deployer.deployWallet); - - if (deployer.verbose) { - console.log("Registering Era in stateTransitionManager"); - } - const registerData = stateTransitionManager.interface.encodeFunctionData("registerAlreadyDeployedHyperchain", [ - deployer.chainId, - deployer.addresses.StateTransition.DiamondProxy, - ]); - await deployer.executeUpgrade(deployer.addresses.StateTransition.StateTransitionProxy, 0, registerData); - const bridgehub = deployer.bridgehubContract(deployer.deployWallet); - if (deployer.verbose) { - console.log("Registering Era in Bridgehub"); - } - const tx = await bridgehub.createNewChain( - deployer.chainId, - deployer.addresses.StateTransition.StateTransitionProxy, - ETH_ADDRESS_IN_CONTRACTS, - ethers.constants.HashZero, - deployer.addresses.Governance, - ethers.constants.HashZero, - { gasPrice } - ); - - await tx.wait(); - if (deployer.verbose) { - console.log("Setting L1Erc20Bridge data in shared bridge"); - } - const sharedBridge = L1SharedBridgeFactory.connect( - deployer.addresses.Bridges.SharedBridgeProxy, - deployer.deployWallet - ); - const data1 = sharedBridge.interface.encodeFunctionData("setL1Erc20Bridge", [ - deployer.addresses.Bridges.ERC20BridgeProxy, - ]); - await deployer.executeUpgrade(deployer.addresses.Bridges.SharedBridgeProxy, 0, data1); - if (process.env.CHAIN_ETH_NETWORK != "hardhat") { - if (deployer.verbose) { - console.log("Initializing l2 bridge in shared bridge"); - } - const data2 = sharedBridge.interface.encodeFunctionData("initializeChainGovernance", [ - deployer.chainId, - deployer.addresses.Bridges.L2SharedBridgeProxy, - ]); - await deployer.executeUpgrade(deployer.addresses.Bridges.SharedBridgeProxy, 0, data2); - } - if (deployer.verbose) { - console.log("Setting validators in hyperchain"); - } - // we have to set it via the STM - const stm = StateTransitionManagerFactory.connect( - deployer.addresses.StateTransition.DiamondProxy, - deployer.deployWallet - ); - const data3 = stm.interface.encodeFunctionData("setValidator", [ - deployer.chainId, - deployer.addresses.ValidatorTimeLock, - true, - ]); - await deployer.executeUpgrade(deployer.addresses.StateTransition.StateTransitionProxy, 0, data3); - - if (deployer.verbose) { - console.log("Setting validators in validator timelock"); - } - - // adding to validator timelock - const validatorOneAddress = getAddressFromEnv("ETH_SENDER_SENDER_OPERATOR_COMMIT_ETH_ADDR"); - const validatorTwoAddress = getAddressFromEnv("ETH_SENDER_SENDER_OPERATOR_BLOBS_ETH_ADDR"); - const validatorTimelock = deployer.validatorTimelock(deployer.deployWallet); - const txRegisterValidator = await validatorTimelock.addValidator(deployer.chainId, validatorOneAddress, { - gasPrice, - }); - const receiptRegisterValidator = await txRegisterValidator.wait(); - if (deployer.verbose) { - console.log( - `Validator registered, gas used: ${receiptRegisterValidator.gasUsed.toString()}, tx hash: ${ - txRegisterValidator.hash - }` - ); - } - - const tx3 = await validatorTimelock.addValidator(deployer.chainId, validatorTwoAddress, { - gasPrice, - }); - const receipt3 = await tx3.wait(); - if (deployer.verbose) { - console.log(`Validator 2 registered, gas used: ${receipt3.gasUsed.toString()}`); - } } -async function upgradeL2Bridge(deployer: Deployer) { +async function upgradeL2Bridge(deployer: Deployer, gasPrice: BigNumberish, printFileName?: string) { const l2BridgeImplementationAddress = process.env.CONTRACTS_L2_SHARED_BRIDGE_IMPL_ADDR!; // upgrade from L1 governance. This has to come from governacne on L1. const l2BridgeAbi = ["function initialize(address, address, bytes32, address)"]; const l2BridgeContract = new ethers.Contract(ADDRESS_ONE, l2BridgeAbi, deployer.deployWallet); const l2Bridge = l2BridgeContract.interface; //L2_SHARED_BRIDGE_INTERFACE; + const l2BridgeCalldata = l2Bridge.encodeFunctionData("initialize", [ deployer.addresses.Bridges.SharedBridgeProxy, deployer.addresses.Bridges.ERC20BridgeProxy, @@ -341,48 +265,33 @@ async function upgradeL2Bridge(deployer: Deployer) { l2BridgeImplementationAddress, l2BridgeCalldata, ]); + // console.log("kl todo", l2BridgeImplementationAddress, l2BridgeCalldata) const factoryDeps = []; - const gasPrice = await deployer.deployWallet.getGasPrice(); const requiredValueForL2Tx = await deployer .bridgehubContract(deployer.deployWallet) .l2TransactionBaseCost(deployer.chainId, gasPrice, priorityTxMaxGasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA); //"1000000000000000000"; - if (process.env.CHAIN_ETH_NETWORK === "localhost") { - // on the main branch the l2SharedBridge governor is incorrectly set to deploy wallet, so we can just make the call - const hyperchain = deployer.stateTransitionContract(deployer.deployWallet); - const tx = await hyperchain.requestL2Transaction( - process.env.CONTRACTS_L2_ERC20_BRIDGE_ADDR, - 0, - l2ProxyCalldata, - priorityTxMaxGasLimit, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - factoryDeps, - deployer.deployWallet.address, - { value: requiredValueForL2Tx.mul(10) } - ); - await tx.wait(); - } else { - const mailboxFacet = new Interface(hardhat.artifacts.readArtifactSync("MailboxFacet").abi); - const mailboxCalldata = mailboxFacet.encodeFunctionData("requestL2Transaction", [ - process.env.CONTRACTS_L2_ERC20_BRIDGE_ADDR, - 0, - l2ProxyCalldata, - priorityTxMaxGasLimit, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - factoryDeps, - deployer.deployWallet.address, - ]); + const mailboxFacet = new Interface(hardhat.artifacts.readArtifactSync("MailboxFacet").abi); + const mailboxCalldata = mailboxFacet.encodeFunctionData("requestL2Transaction", [ + process.env.CONTRACTS_L2_ERC20_BRIDGE_ADDR, + 0, + l2ProxyCalldata, + priorityTxMaxGasLimit, + REQUIRED_L2_GAS_PRICE_PER_PUBDATA, + factoryDeps, + deployer.deployWallet.address, + ]); - await deployer.executeUpgrade( - deployer.addresses.StateTransition.DiamondProxy, - requiredValueForL2Tx.mul(10), - mailboxCalldata - ); - } + await deployer.executeUpgrade( + deployer.addresses.StateTransition.DiamondProxy, + requiredValueForL2Tx, + mailboxCalldata, + printFileName + ); } -async function upgradeL1ERC20Bridge(deployer: Deployer) { - if (process.env.CHAIN_ETH_NETWORK === "localhost") { +async function upgradeL1ERC20Bridge(deployer: Deployer, gasPrice: BigNumberish, printFileName?: string) { + if (process.env.CHAIN_ETH_NETWORK != "hardhat") { // we need to wait here for a new block await new Promise((resolve) => setTimeout(resolve, 5000)); // upgrade ERC20. @@ -397,7 +306,7 @@ async function upgradeL1ERC20Bridge(deployer: Deployer) { deployer.addresses.Bridges.ERC20BridgeImplementation, ]); - await deployer.executeUpgrade(deployer.addresses.TransparentProxyAdmin, 0, data1); + await deployer.executeUpgrade(deployer.addresses.TransparentProxyAdmin, 0, data1, printFileName); if (deployer.verbose) { console.log("L1ERC20Bridge upgrade sent"); @@ -405,39 +314,35 @@ async function upgradeL1ERC20Bridge(deployer: Deployer) { } } -async function migrateAssets(deployer: Deployer) { - // migrate assets from L1 ERC20 bridge - if (deployer.verbose) { - console.log("transferring Eth"); - } - const sharedBridge = deployer.defaultSharedBridge(deployer.deployWallet); - const ethTransferData = sharedBridge.interface.encodeFunctionData("transferFundsFromLegacy", [ - ADDRESS_ONE, - deployer.addresses.StateTransition.DiamondProxy, - deployer.chainId, +export async function transferERC20BridgeToProxyAdmin( + deployer: Deployer, + gasPrice: BigNumberish, + printFileName?: string +) { + const bridgeProxy: ITransparentUpgradeableProxy = ITransparentUpgradeableProxyFactory.connect( + deployer.addresses.Bridges.ERC20BridgeProxy, + deployer.deployWallet + ); + const data1 = await bridgeProxy.interface.encodeFunctionData("changeAdmin", [ + deployer.addresses.TransparentProxyAdmin, ]); - await deployer.executeUpgrade(deployer.addresses.Bridges.SharedBridgeProxy, 0, ethTransferData); - const tokens = getTokens(); - const altTokenAddress = tokens.find((token: { symbol: string }) => token.symbol == "DAI")!.address; + await deployer.executeUpgrade(deployer.addresses.Bridges.ERC20BridgeProxy, 0, data1, printFileName); + if (deployer.verbose) { - console.log("transferring Dai, ", altTokenAddress); + console.log("ERC20Bridge ownership transfer sent"); } +} - // Mint some tokens - const l1Erc20ABI = ["function mint(address to, uint256 amount)"]; - const l1Erc20Contract = new ethers.Contract(altTokenAddress, l1Erc20ABI, deployer.deployWallet); - const mintTx = await l1Erc20Contract.mint( +export async function transferTokens(deployer: Deployer, token: string) { + const sharedBridge = deployer.defaultSharedBridge(deployer.deployWallet); + const tx = await sharedBridge.safeTransferFundsFromLegacy( + token, deployer.addresses.Bridges.ERC20BridgeProxy, - ethers.utils.parseEther("10000.0") + "324", + "300000", + { gasLimit: 25_000_000 } ); - await mintTx.wait(); - - const daiTransferData = sharedBridge.interface.encodeFunctionData("transferFundsFromLegacy", [ - altTokenAddress, - deployer.addresses.Bridges.ERC20BridgeProxy, - deployer.chainId, - ]); - // daiTransferData; - await deployer.executeUpgrade(deployer.addresses.Bridges.SharedBridgeProxy, 0, daiTransferData); + await tx.wait(); + console.log("Receipt", tx.hash); } diff --git a/l1-contracts/src.ts/utils.ts b/l1-contracts/src.ts/utils.ts index e370e4499..ca18bc7e4 100644 --- a/l1-contracts/src.ts/utils.ts +++ b/l1-contracts/src.ts/utils.ts @@ -6,6 +6,10 @@ import type { BytesLike, BigNumberish } from "ethers"; import { ethers } from "ethers"; import * as fs from "fs"; import * as path from "path"; +import { DiamondInitFactory } from "../typechain"; +import type { DiamondCut, FacetCut } from "./diamondCut"; +import { diamondCut } from "./diamondCut"; +import { SYSTEM_CONFIG } from "../scripts/utils"; export const testConfigPath = process.env.ZKSYNC_ENV ? path.join(process.env.ZKSYNC_HOME as string, "etc/test_config/constant") @@ -174,3 +178,112 @@ export interface L2CanonicalTransaction { // But it is still here, just in case we want to enable some additional functionality. reservedDynamic: BytesLike; } + +// Checks that the initial cut hash params are valid. +// Sometimes it makes sense to allow dummy values for testing purposes, but in production +// these values should be set correctly. +function checkValidInitialCutHashParams( + facetCuts: FacetCut[], + verifierParams: VerifierParams, + l2BootloaderBytecodeHash: string, + l2DefaultAccountBytecodeHash: string, + verifier: string, + blobVersionedHashRetriever: string, + priorityTxMaxGasLimit: number +) { + // We do not fetch the following numbers from the environment because they are very rarely changed + // and we want to avoid the risk of accidentally changing them. + const EXPECTED_FACET_CUTS = 4; + const EXPECTED_PRIORITY_TX_MAX_GAS_LIMIT = 72_000_000; + + if (facetCuts.length != EXPECTED_FACET_CUTS) { + throw new Error(`Expected ${EXPECTED_FACET_CUTS} facet cuts, got ${facetCuts.length}`); + } + + if (verifierParams.recursionNodeLevelVkHash === ethers.constants.HashZero) { + throw new Error("Recursion node level vk hash is zero"); + } + if (verifierParams.recursionLeafLevelVkHash === ethers.constants.HashZero) { + throw new Error("Recursion leaf level vk hash is zero"); + } + if (verifierParams.recursionCircuitsSetVksHash !== ethers.constants.HashZero) { + throw new Error("Recursion circuits set vks hash must be zero"); + } + if (l2BootloaderBytecodeHash === ethers.constants.HashZero) { + throw new Error("L2 bootloader bytecode hash is zero"); + } + if (l2DefaultAccountBytecodeHash === ethers.constants.HashZero) { + throw new Error("L2 default account bytecode hash is zero"); + } + if (verifier === ethers.constants.AddressZero) { + throw new Error("Verifier address is zero"); + } + if (blobVersionedHashRetriever === ethers.constants.AddressZero) { + throw new Error("Blob versioned hash retriever address is zero"); + } + if (priorityTxMaxGasLimit !== EXPECTED_PRIORITY_TX_MAX_GAS_LIMIT) { + throw new Error( + `Expected priority tx max gas limit to be ${EXPECTED_PRIORITY_TX_MAX_GAS_LIMIT}, got ${priorityTxMaxGasLimit}` + ); + } +} + +// We should either reuse code or add a test for this function. +export function compileInitialCutHash( + facetCuts: FacetCut[], + verifierParams: VerifierParams, + l2BootloaderBytecodeHash: string, + l2DefaultAccountBytecodeHash: string, + verifier: string, + blobVersionedHashRetriever: string, + priorityTxMaxGasLimit: number, + diamondInit: string, + strictMode: boolean = true +): DiamondCut { + if (strictMode) { + checkValidInitialCutHashParams( + facetCuts, + verifierParams, + l2BootloaderBytecodeHash, + l2DefaultAccountBytecodeHash, + verifier, + blobVersionedHashRetriever, + priorityTxMaxGasLimit + ); + } + + const factory = new DiamondInitFactory(); + + const feeParams = { + pubdataPricingMode: PubdataPricingMode.Rollup, + batchOverheadL1Gas: SYSTEM_CONFIG.priorityTxBatchOverheadL1Gas, + maxPubdataPerBatch: SYSTEM_CONFIG.priorityTxPubdataPerBatch, + priorityTxMaxPubdata: SYSTEM_CONFIG.priorityTxMaxPubdata, + maxL2GasPerBatch: SYSTEM_CONFIG.priorityTxMaxGasPerBatch, + minimalL2GasPrice: SYSTEM_CONFIG.priorityTxMinimalGasPrice, + }; + + const diamondInitCalldata = factory.interface.encodeFunctionData("initialize", [ + // these first values are set in the contract + { + chainId: "0x0000000000000000000000000000000000000000000000000000000000000001", + bridgehub: "0x0000000000000000000000000000000000001234", + stateTransitionManager: "0x0000000000000000000000000000000000002234", + protocolVersion: "0x0000000000000000000000000000000000002234", + admin: "0x0000000000000000000000000000000000003234", + validatorTimelock: "0x0000000000000000000000000000000000004234", + baseToken: "0x0000000000000000000000000000000000004234", + baseTokenBridge: "0x0000000000000000000000000000000000004234", + storedBatchZero: "0x0000000000000000000000000000000000000000000000000000000000005432", + verifier, + verifierParams, + l2BootloaderBytecodeHash, + l2DefaultAccountBytecodeHash, + priorityTxMaxGasLimit, + feeParams, + blobVersionedHashRetriever, + }, + ]); + + return diamondCut(facetCuts, diamondInit, "0x" + diamondInitCalldata.slice(2 + (4 + 9 * 32) * 2)); +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol index 3dcc863fc..9ef3ae0fb 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol @@ -7,6 +7,7 @@ import {stdStorage, StdStorage, Test} from "forge-std/Test.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {TestnetERC20Token} from "contracts/dev-contracts/TestnetERC20Token.sol"; import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; +import {ChainCreationParams} from "contracts/state-transition/IStateTransitionManager.sol"; import {L2TransactionRequestDirect, L2TransactionRequestTwoBridgesOuter} from "contracts/bridgehub/IBridgehub.sol"; import {DummyStateTransitionManagerWBH} from "contracts/dev-contracts/test/DummyStateTransitionManagerWithBridgeHubAddress.sol"; import {DummyHyperchain} from "contracts/dev-contracts/test/DummyHyperchain.sol"; @@ -861,7 +862,16 @@ contract ExperimentalBridgeTest is Test { diamondCutData.initAddress = address(0); diamondCutData.initCalldata = ""; - mockSTM.setInitialCutHash(diamondCutData); + ChainCreationParams memory params = ChainCreationParams({ + diamondCut: diamondCutData, + // Just some dummy values: + genesisUpgrade: address(0x01), + genesisBatchHash: bytes32(uint256(0x01)), + genesisIndexRepeatedStorageChanges: uint64(0x01), + genesisBatchCommitment: bytes32(uint256(0x01)) + }); + + mockSTM.setChainCreationParams(params); return abi.encode(diamondCutData); } diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/RevertBatches.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/RevertBatches.t.sol index 7e277c1ec..2113f3467 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/RevertBatches.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/RevertBatches.t.sol @@ -41,13 +41,13 @@ contract revertBatchesTest is StateTransitionManagerTest { genesisStoredBatchInfo = IExecutor.StoredBatchInfo({ batchNumber: 0, - batchHash: bytes32(""), - indexRepeatedStorageChanges: 0, + batchHash: bytes32(uint256(0x01)), + indexRepeatedStorageChanges: 1, numberOfLayer1Txs: 0, priorityOperationsHash: EMPTY_STRING_KECCAK, l2LogsTreeRoot: DEFAULT_L2_LOGS_TREE_ROOT_HASH, timestamp: 0, - commitment: bytes32("") + commitment: bytes32(uint256(0x01)) }); adminFacet.setTokenMultiplier(1, 1); @@ -65,7 +65,7 @@ contract revertBatchesTest is StateTransitionManagerTest { newCommitBatchInfo = IExecutor.CommitBatchInfo({ batchNumber: 1, timestamp: uint64(currentTimestamp), - indexRepeatedStorageChanges: 0, + indexRepeatedStorageChanges: 1, newStateRoot: Utils.randomBytes32("newStateRoot"), numberOfLayer1Txs: 0, priorityOperationsHash: keccak256(""), @@ -91,6 +91,13 @@ contract revertBatchesTest is StateTransitionManagerTest { Utils.packBatchTimestampAndBlockTimestamp(currentTimestamp, currentTimestamp) ); + correctL2Logs[uint256(uint256(SystemLogKey.PREV_BATCH_HASH_KEY))] = Utils.constructL2Log( + true, + L2_SYSTEM_CONTEXT_ADDRESS, + uint256(SystemLogKey.PREV_BATCH_HASH_KEY), + bytes32(uint256(0x01)) + ); + l2Logs = Utils.encodePacked(correctL2Logs); newCommitBatchInfo.timestamp = uint64(currentTimestamp); newCommitBatchInfo.systemLogs = l2Logs; @@ -107,7 +114,7 @@ contract revertBatchesTest is StateTransitionManagerTest { newStoredBatchInfo = IExecutor.StoredBatchInfo({ batchNumber: 1, batchHash: entries[0].topics[2], - indexRepeatedStorageChanges: 0, + indexRepeatedStorageChanges: 1, numberOfLayer1Txs: 0, priorityOperationsHash: keccak256(""), l2LogsTreeRoot: 0, diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetChainCreationParams.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetChainCreationParams.t.sol new file mode 100644 index 000000000..85fa1a316 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetChainCreationParams.t.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {StateTransitionManagerTest} from "./_StateTransitionManager_Shared.t.sol"; +import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; +import {ChainCreationParams} from "contracts/state-transition/IStateTransitionManager.sol"; +import {IExecutor} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; +import {EMPTY_STRING_KECCAK, DEFAULT_L2_LOGS_TREE_ROOT_HASH} from "contracts/common/Config.sol"; + +contract SetChainCreationParamsTest is StateTransitionManagerTest { + function test_SettingInitialCutHash() public { + bytes32 initialCutHash = keccak256(abi.encode(getDiamondCutData(address(diamondInit)))); + address randomDiamondInit = address(0x303030303030303030303); + + assertEq(chainContractAddress.initialCutHash(), initialCutHash, "Initial cut hash is not correct"); + + Diamond.DiamondCutData memory newDiamondCutData = getDiamondCutData(address(randomDiamondInit)); + bytes32 newCutHash = keccak256(abi.encode(newDiamondCutData)); + + address newGenesisUpgrade = address(0x02); + bytes32 genesisBatchHash = bytes32(uint256(0x02)); + uint64 genesisIndexRepeatedStorageChanges = 2; + bytes32 genesisBatchCommitment = bytes32(uint256(0x02)); + + ChainCreationParams memory newChainCreationParams = ChainCreationParams({ + genesisUpgrade: newGenesisUpgrade, + genesisBatchHash: genesisBatchHash, + genesisIndexRepeatedStorageChanges: genesisIndexRepeatedStorageChanges, + genesisBatchCommitment: genesisBatchCommitment, + diamondCut: newDiamondCutData + }); + + chainContractAddress.setChainCreationParams(newChainCreationParams); + + assertEq(chainContractAddress.initialCutHash(), newCutHash, "Initial cut hash update was not successful"); + assertEq(chainContractAddress.genesisUpgrade(), newGenesisUpgrade, "Genesis upgrade was not set correctly"); + + // We need to initialize the state hash because it is used in the commitment of the next batch + IExecutor.StoredBatchInfo memory newBatchZero = IExecutor.StoredBatchInfo({ + batchNumber: 0, + batchHash: genesisBatchHash, + indexRepeatedStorageChanges: genesisIndexRepeatedStorageChanges, + numberOfLayer1Txs: 0, + priorityOperationsHash: EMPTY_STRING_KECCAK, + l2LogsTreeRoot: DEFAULT_L2_LOGS_TREE_ROOT_HASH, + timestamp: 0, + commitment: genesisBatchCommitment + }); + bytes32 expectedStoredBatchZero = keccak256(abi.encode(newBatchZero)); + + assertEq( + chainContractAddress.storedBatchZero(), + expectedStoredBatchZero, + "Stored batch zero was not set correctly" + ); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetInitialCutHash.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetInitialCutHash.t.sol deleted file mode 100644 index f0fc37858..000000000 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetInitialCutHash.t.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -import {StateTransitionManagerTest} from "./_StateTransitionManager_Shared.t.sol"; -import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; - -contract setInitialCutHashTest is StateTransitionManagerTest { - function test_SettingInitialCutHash() public { - bytes32 initialCutHash = keccak256(abi.encode(getDiamondCutData(address(diamondInit)))); - address randomDiamondInit = address(0x303030303030303030303); - - assertEq(chainContractAddress.initialCutHash(), initialCutHash, "Initial cut hash is not correct"); - - Diamond.DiamondCutData memory newDiamondCutData = getDiamondCutData(address(randomDiamondInit)); - bytes32 newCutHash = keccak256(abi.encode(newDiamondCutData)); - - chainContractAddress.setInitialCutHash(newDiamondCutData); - - assertEq(chainContractAddress.initialCutHash(), newCutHash, "Initial cut hash update was not successful"); - } -} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/StateTransitionOwnerZero.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/StateTransitionOwnerZero.t.sol index 60c606a3d..8fae0aa1e 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/StateTransitionOwnerZero.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/StateTransitionOwnerZero.t.sol @@ -4,18 +4,22 @@ pragma solidity 0.8.24; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {StateTransitionManagerTest} from "./_StateTransitionManager_Shared.t.sol"; import {StateTransitionManager} from "contracts/state-transition/StateTransitionManager.sol"; -import {StateTransitionManagerInitializeData} from "contracts/state-transition/IStateTransitionManager.sol"; +import {StateTransitionManagerInitializeData, ChainCreationParams} from "contracts/state-transition/IStateTransitionManager.sol"; contract initializingSTMOwnerZeroTest is StateTransitionManagerTest { function test_InitializingSTMWithGovernorZeroShouldRevert() public { + ChainCreationParams memory chainCreationParams = ChainCreationParams({ + genesisUpgrade: address(genesisUpgradeContract), + genesisBatchHash: bytes32(uint256(0x01)), + genesisIndexRepeatedStorageChanges: 1, + genesisBatchCommitment: bytes32(uint256(0x01)), + diamondCut: getDiamondCutData(address(diamondInit)) + }); + StateTransitionManagerInitializeData memory stmInitializeDataNoOwner = StateTransitionManagerInitializeData({ owner: address(0), validatorTimelock: validator, - genesisUpgrade: address(genesisUpgradeContract), - genesisBatchHash: bytes32(""), - genesisIndexRepeatedStorageChanges: 0, - genesisBatchCommitment: bytes32(""), - diamondCut: getDiamondCutData(address(diamondInit)), + chainCreationParams: chainCreationParams, protocolVersion: 0 }); diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/_StateTransitionManager_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/_StateTransitionManager_Shared.t.sol index 7f7b7dfad..1cd596436 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/_StateTransitionManager_Shared.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/_StateTransitionManager_Shared.t.sol @@ -16,7 +16,7 @@ import {DiamondInit} from "contracts/state-transition/chain-deps/DiamondInit.sol import {GenesisUpgrade} from "contracts/upgrades/GenesisUpgrade.sol"; import {InitializeDataNewChain} from "contracts/state-transition/chain-interfaces/IDiamondInit.sol"; import {StateTransitionManager} from "contracts/state-transition/StateTransitionManager.sol"; -import {StateTransitionManagerInitializeData} from "contracts/state-transition/IStateTransitionManager.sol"; +import {StateTransitionManagerInitializeData, ChainCreationParams} from "contracts/state-transition/IStateTransitionManager.sol"; import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; contract StateTransitionManagerTest is Test { @@ -78,14 +78,18 @@ contract StateTransitionManagerTest is Test { }) ); + ChainCreationParams memory chainCreationParams = ChainCreationParams({ + genesisUpgrade: address(genesisUpgradeContract), + genesisBatchHash: bytes32(uint256(0x01)), + genesisIndexRepeatedStorageChanges: 0x01, + genesisBatchCommitment: bytes32(uint256(0x01)), + diamondCut: getDiamondCutData(address(diamondInit)) + }); + StateTransitionManagerInitializeData memory stmInitializeDataNoGovernor = StateTransitionManagerInitializeData({ owner: address(0), validatorTimelock: validator, - genesisUpgrade: address(genesisUpgradeContract), - genesisBatchHash: bytes32(""), - genesisIndexRepeatedStorageChanges: 0, - genesisBatchCommitment: bytes32(""), - diamondCut: getDiamondCutData(address(diamondInit)), + chainCreationParams: chainCreationParams, protocolVersion: 0 }); @@ -99,11 +103,7 @@ contract StateTransitionManagerTest is Test { StateTransitionManagerInitializeData memory stmInitializeData = StateTransitionManagerInitializeData({ owner: governor, validatorTimelock: validator, - genesisUpgrade: address(genesisUpgradeContract), - genesisBatchHash: bytes32(""), - genesisIndexRepeatedStorageChanges: 0, - genesisBatchCommitment: bytes32(""), - diamondCut: getDiamondCutData(address(diamondInit)), + chainCreationParams: chainCreationParams, protocolVersion: 0 }); diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/ExecuteUpgrade.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/ExecuteUpgrade.t.sol index d90d00df5..95c6f54af 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/ExecuteUpgrade.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/ExecuteUpgrade.t.sol @@ -4,8 +4,13 @@ pragma solidity 0.8.24; import {AdminTest} from "./_Admin_Shared.t.sol"; import {ERROR_ONLY_STATE_TRANSITION_MANAGER} from "../Base/_Base_Shared.t.sol"; +import {Utils} from "../../../../Utils/Utils.sol"; +import {VerifierParams} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; +import {DefaultUpgrade} from "contracts/upgrades/DefaultUpgrade.sol"; +import {SemVer} from "contracts/common/libraries/SemVer.sol"; +import {ProposedUpgrade} from "contracts/upgrades/BaseZkSyncUpgrade.sol"; contract ExecuteUpgradeTest is AdminTest { event ExecuteUpgrade(Diamond.DiamondCutData diamondCut); @@ -23,6 +28,44 @@ contract ExecuteUpgradeTest is AdminTest { vm.startPrank(nonStateTransitionManager); adminFacet.executeUpgrade(diamondCutData); } + + /// TODO: This test should be removed after the migration to the semver is complete everywhere. + function test_migrateToSemVerApproach() public { + // Setting minor protocol version manually + utilsFacet.util_setProtocolVersion(22); + + VerifierParams memory verifierParams = VerifierParams({ + recursionNodeLevelVkHash: bytes32(0), + recursionLeafLevelVkHash: bytes32(0), + recursionCircuitsSetVksHash: bytes32(0) + }); + + ProposedUpgrade memory proposedUpgrade = ProposedUpgrade({ + l2ProtocolUpgradeTx: Utils.makeEmptyL2CanonicalTransaction(), + factoryDeps: new bytes[](0), + bootloaderHash: bytes32(0), + defaultAccountHash: bytes32(0), + verifier: address(0), + verifierParams: verifierParams, + l1ContractsUpgradeCalldata: hex"", + postUpgradeCalldata: hex"", + upgradeTimestamp: 0, + newProtocolVersion: SemVer.packSemVer(0, 22, 0) + }); + + DefaultUpgrade upgrade = new DefaultUpgrade(); + + Diamond.DiamondCutData memory diamondCutData = Diamond.DiamondCutData({ + facetCuts: new Diamond.FacetCut[](0), + initAddress: address(upgrade), + initCalldata: abi.encodeCall(upgrade.upgrade, (proposedUpgrade)) + }); + + address stm = utilsFacet.util_getStateTransitionManager(); + vm.startPrank(stm); + + adminFacet.executeUpgrade(diamondCutData); + } } interface IDiamondLibrary { diff --git a/l1-contracts/test/test_config/constant/hardhat.json b/l1-contracts/test/test_config/constant/hardhat.json index 0b550819d..0e63431f0 100644 --- a/l1-contracts/test/test_config/constant/hardhat.json +++ b/l1-contracts/test/test_config/constant/hardhat.json @@ -3,96 +3,96 @@ "name": "DAI", "symbol": "DAI", "decimals": 18, - "address": "0x3202935CA01eADd2F0B4e7aA23EFA52315121172" + "address": "0xD6E49dd4fb0CA1549566869725d1820aDEb92Ae9" }, { "name": "wBTC", "symbol": "wBTC", "decimals": 8, - "address": "0xe73a1c05498e492f182f9B3E3cd65b2F9f52eE94" + "address": "0xcee1f75F30B6908286Cd003C4228A5D9a2851FA4" }, { "name": "BAT", "symbol": "BAT", "decimals": 18, - "address": "0x7aAE2ee8317b384aDE246664933A147411b60045" + "address": "0x0Bc76A4EfE0748f1697F237fB100741ea6Ceda2d" }, { "name": "GNT", "symbol": "GNT", "decimals": 18, - "address": "0x02a344d1e31e92e39c2681937645F1d668C37d4e" + "address": "0x51ae50BcCEE10ac5BEFFA1E4a64106a5f83bc3F8" }, { "name": "MLTT", "symbol": "MLTT", "decimals": 18, - "address": "0x28033f8EdB2F43747E55401C4a3E3b4b2cF5146C" + "address": "0xa9c7fEEf8586E17D93A05f873BA65f28f48ED259" }, { "name": "DAIK", "symbol": "DAIK", "decimals": 18, - "address": "0x147dCc3a8E99794C2Ec79EaF1a142324D6778255" + "address": "0x99Efb27598804Aa408A1066550e9d01c45f21b05" }, { "name": "wBTCK", "symbol": "wBTCK", "decimals": 8, - "address": "0x74cA8715E29196Bfbcd7444B09203d22dDaF7d1a" + "address": "0x4B701928Da6B3e72775b462A15b8b76ba2d16BbD" }, { "name": "BATK", "symbol": "BATS", "decimals": 18, - "address": "0x2eAf3eac597d23db3A8427329482aE47935141d9" + "address": "0xf7B03c921dfefB4286b13075BA0335099708368D" }, { "name": "GNTK", "symbol": "GNTS", "decimals": 18, - "address": "0x2B1b32d23f2be391280bFbD2B51daB8Ad2a69B9e" + "address": "0xc0581Ee28c519533B06cc0aAC1ace98cF63C817b" }, { "name": "MLTTK", "symbol": "MLTTS", "decimals": 18, - "address": "0xc2Ca10940Ad80Cd98512B767457bd44713232B5a" + "address": "0xeB6394F2E8DA607b94dBa2Cf345A965d6D9b3aCD" }, { "name": "DAIL", "symbol": "DAIL", "decimals": 18, - "address": "0xd6b4CDa9F0Ef6d9F6b35Ab5424df0dD95E6a6D1b" + "address": "0x4311643C5eD7cD0813B4E3Ff5428de71c7d7b8bB" }, { "name": "wBTCL", "symbol": "wBTCP", "decimals": 8, - "address": "0x15CD4a1C10AE2D3727Dad680a1966947931588C9" + "address": "0x6b3fbfC9Bb89Ab5F11BE782a1f67c1615c2A5fc3" }, { "name": "BATL", "symbol": "BATW", "decimals": 18, - "address": "0x45f430CFD5Bf38eCE39f1B9A2930B3fD494619e8" + "address": "0xE003698b7831829843B69D3fB4f9a3133d97b257" }, { "name": "GNTL", "symbol": "GNTW", "decimals": 18, - "address": "0x801Ab08819573537C0B256d139473eF13482B3Dd" + "address": "0x2417626170675Ccf6022d9db1eFC8f3c59836368" }, { "name": "MLTTL", "symbol": "MLTTW", "decimals": 18, - "address": "0xf55F3af54B9a507Bd0260e5844C1648921214745" + "address": "0x28106C39BE5E51C31D9a289313361D86C9bb7C8E" }, { "name": "Wrapped Ether", "symbol": "WETH", "decimals": 18, - "address": "0xed01DF970925Cc0BB427f9E8014449C8a529D303" + "address": "0x51E83b811930bb4a3aAb3494894ec237Cb6cEc49" } ] diff --git a/l1-contracts/test/unit_tests/hyperchain_migration_test.spec.ts b/l1-contracts/test/unit_tests/hyperchain_migration_test.spec.ts deleted file mode 100644 index 216bc6fbc..000000000 --- a/l1-contracts/test/unit_tests/hyperchain_migration_test.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import * as ethers from "ethers"; -import { Wallet } from "ethers"; -import * as hardhat from "hardhat"; - -import type { EraDeployer } from "../../src.ts/deploy-test-process"; -import { initialPreUpgradeContractsDeployment } from "../../src.ts/deploy-test-process"; -import { ethTestConfig } from "../../src.ts/utils"; - -import { upgradeToHyperchains } from "../../src.ts/hyperchain-upgrade"; - -// note this test presumes that it is ok to start out with the new contracts, and upgrade them to themselves -describe("Hyperchain migration test", function () { - let owner: ethers.Signer; - let deployer: EraDeployer; - let gasPrice; - - before(async () => { - [owner] = await hardhat.ethers.getSigners(); - - const deployWallet = Wallet.fromMnemonic(ethTestConfig.test_mnemonic3, "m/44'/60'/0'/0/1").connect(owner.provider); - const ownerAddress = await deployWallet.getAddress(); - - gasPrice = await owner.provider.getGasPrice(); - - const tx = { - from: await owner.getAddress(), - to: deployWallet.address, - value: ethers.utils.parseEther("1000"), - nonce: owner.getTransactionCount(), - gasLimit: 100000, - gasPrice: gasPrice, - }; - await owner.sendTransaction(tx); - - deployer = await initialPreUpgradeContractsDeployment(deployWallet, ownerAddress, gasPrice, []); - }); - - it("Start upgrade", async () => { - await upgradeToHyperchains(deployer, gasPrice); - }); -}); diff --git a/l1-contracts/test/unit_tests/l2-upgrade.test.spec.ts b/l1-contracts/test/unit_tests/l2-upgrade.test.spec.ts index cda99ac34..7352ce8cc 100644 --- a/l1-contracts/test/unit_tests/l2-upgrade.test.spec.ts +++ b/l1-contracts/test/unit_tests/l2-upgrade.test.spec.ts @@ -40,8 +40,9 @@ import { makeExecutedEqualCommitted, getBatchStoredInfo, } from "./utils"; +import { packSemver, unpackStringSemVer, addToProtocolVersion } from "../../scripts/utils"; -describe("L2 upgrade test", function () { +describe.only("L2 upgrade test", function () { let proxyExecutor: ExecutorFacet; let proxyAdmin: AdminFacet; let proxyGetters: GettersFacet; @@ -59,8 +60,8 @@ describe("L2 upgrade test", function () { let verifier: string; const noopUpgradeTransaction = buildL2CanonicalTransaction({ txType: 0 }); let chainId = process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID || 270; - // let priorityOperationsHash: string; let initialProtocolVersion = 0; + let initialMinorProtocolVersion = 0; before(async () => { [owner] = await hardhat.ethers.getSigners(); @@ -92,7 +93,14 @@ describe("L2 upgrade test", function () { const transferOwnershipTx = await ownable.acceptOwnership(); await transferOwnershipTx.wait(); - initialProtocolVersion = parseInt(process.env.CONTRACTS_GENESIS_PROTOCOL_VERSION); + const [initialMajor, initialMinor, initialPatch] = unpackStringSemVer( + process.env.CONTRACTS_GENESIS_PROTOCOL_SEMANTIC_VERSION + ); + if (initialMajor !== 0 || initialPatch !== 0) { + throw new Error("Initial protocol version must be 0.x.0"); + } + initialProtocolVersion = packSemver(initialMajor, initialMinor, initialPatch); + initialMinorProtocolVersion = initialMinor; chainId = deployer.chainId; verifier = deployer.addresses.StateTransition.Verifier; @@ -149,18 +157,75 @@ describe("L2 upgrade test", function () { await ( await executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { - newProtocolVersion: 1 + initialProtocolVersion, + newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 1, 0), l2ProtocolUpgradeTx: noopUpgradeTransaction, }) ).wait(); - expect(await proxyGetters.getProtocolVersion()).to.equal(1 + initialProtocolVersion); + expect(await proxyGetters.getProtocolVersion()).to.equal(addToProtocolVersion(initialProtocolVersion, 1, 0)); storedBatch2Info = getBatchStoredInfo(batch2Info, commitment); await makeExecutedEqualCommitted(proxyExecutor, storedBatch1InfoChainIdUpgrade, [storedBatch2Info], []); }); + it("Should not allow base system contract changes during patch upgrade", async () => { + const { 0: major, 1: minor, 2: patch } = await proxyGetters.getSemverProtocolVersion(); + + const bootloaderRevertReason = await getCallRevertReason( + executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { + newProtocolVersion: packSemver(major, minor, patch + 1), + bootloaderHash: ethers.utils.hexlify(hashBytecode(ethers.utils.randomBytes(32))), + l2ProtocolUpgradeTx: noopUpgradeTransaction, + }) + ); + expect(bootloaderRevertReason).to.equal("Patch only upgrade can not set new bootloader"); + + const defaultAccountRevertReason = await getCallRevertReason( + executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { + newProtocolVersion: packSemver(major, minor, patch + 1), + defaultAccountHash: ethers.utils.hexlify(hashBytecode(ethers.utils.randomBytes(32))), + l2ProtocolUpgradeTx: noopUpgradeTransaction, + }) + ); + expect(defaultAccountRevertReason).to.equal("Patch only upgrade can not set new default account"); + }); + + it("Should not allow upgrade transaction during patch upgrade", async () => { + const { 0: major, 1: minor, 2: patch } = await proxyGetters.getSemverProtocolVersion(); + + const someTx = buildL2CanonicalTransaction({ + txType: 254, + nonce: 0, + }); + + const bootloaderRevertReason = await getCallRevertReason( + executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { + newProtocolVersion: packSemver(major, minor, patch + 1), + l2ProtocolUpgradeTx: someTx, + }) + ); + expect(bootloaderRevertReason).to.equal("Patch only upgrade can not set upgrade transaction"); + }); + + it("Should not allow major version change", async () => { + // 2**64 is the offset for a major version change + const newVersion = ethers.BigNumber.from(2).pow(64); + + const someTx = buildL2CanonicalTransaction({ + txType: 254, + nonce: 0, + }); + + const bootloaderRevertReason = await getCallRevertReason( + executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { + newProtocolVersion: newVersion, + l2ProtocolUpgradeTx: someTx, + }) + ); + expect(bootloaderRevertReason).to.equal("Major must always be 0"); + }); + it("Timestamp should behave correctly", async () => { // Upgrade was scheduled for now should work fine const timeNow = (await hardhat.ethers.provider.getBlock("latest")).timestamp; @@ -186,7 +251,7 @@ describe("L2 upgrade test", function () { const revertReason = await getCallRevertReason( executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, - newProtocolVersion: 3 + initialProtocolVersion, + newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 3, 0), }) ); @@ -202,7 +267,7 @@ describe("L2 upgrade test", function () { const revertReason = await getCallRevertReason( executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, - newProtocolVersion: 3 + 1 + initialProtocolVersion, + newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 4, 0), }) ); @@ -234,7 +299,7 @@ describe("L2 upgrade test", function () { const revertReason = await getCallRevertReason( executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, - newProtocolVersion: 100000, + newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 10000, 0), }) ); @@ -250,7 +315,7 @@ describe("L2 upgrade test", function () { const revertReason = await getCallRevertReason( executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, - newProtocolVersion: 3 + 1 + initialProtocolVersion, + newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 4, 0), }) ); @@ -266,7 +331,7 @@ describe("L2 upgrade test", function () { const revertReason = await getCallRevertReason( executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, - newProtocolVersion: 3 + 1 + initialProtocolVersion, + newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 4, 0), }) ); @@ -283,7 +348,7 @@ describe("L2 upgrade test", function () { const revertReason = await getCallRevertReason( executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, - newProtocolVersion: 3 + 1 + initialProtocolVersion, + newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 4, 0), }) ); @@ -295,14 +360,14 @@ describe("L2 upgrade test", function () { const wrongFactoryDepHash = ethers.utils.hexlify(hashBytecode(ethers.utils.randomBytes(32))); const wrongTx = buildL2CanonicalTransaction({ factoryDeps: [wrongFactoryDepHash], - nonce: 3 + 1 + initialProtocolVersion, + nonce: 4 + initialMinorProtocolVersion, }); const revertReason = await getCallRevertReason( executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, factoryDeps: [myFactoryDep], - newProtocolVersion: 3 + 1 + initialProtocolVersion, + newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 4, 0), }) ); @@ -313,14 +378,14 @@ describe("L2 upgrade test", function () { const myFactoryDep = ethers.utils.hexlify(ethers.utils.randomBytes(32)); const wrongTx = buildL2CanonicalTransaction({ factoryDeps: [], - nonce: 3 + 1 + initialProtocolVersion, + nonce: 4 + initialMinorProtocolVersion, }); const revertReason = await getCallRevertReason( executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, factoryDeps: [myFactoryDep], - newProtocolVersion: 3 + 1 + initialProtocolVersion, + newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 4, 0), }) ); @@ -333,14 +398,14 @@ describe("L2 upgrade test", function () { const wrongTx = buildL2CanonicalTransaction({ factoryDeps: Array(33).fill(randomDepHash), - nonce: 3 + 1 + initialProtocolVersion, + nonce: 4 + initialMinorProtocolVersion, }); const revertReason = await getCallRevertReason( executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { l2ProtocolUpgradeTx: wrongTx, factoryDeps: Array(33).fill(myFactoryDep), - newProtocolVersion: 3 + 1 + initialProtocolVersion, + newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 4, 0), }) ); @@ -364,7 +429,7 @@ describe("L2 upgrade test", function () { const myFactoryDepHash = hashBytecode(myFactoryDep); const upgradeTx = buildL2CanonicalTransaction({ factoryDeps: [myFactoryDepHash], - nonce: 4 + 1 + initialProtocolVersion, + nonce: 5 + initialMinorProtocolVersion, }); const upgrade = { @@ -375,7 +440,7 @@ describe("L2 upgrade test", function () { executeUpgradeTx: true, l2ProtocolUpgradeTx: upgradeTx, factoryDeps: [myFactoryDep], - newProtocolVersion: 4 + 1 + initialProtocolVersion, + newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 5, 0), }; const upgradeReceipt = await ( @@ -402,7 +467,7 @@ describe("L2 upgrade test", function () { expect(await proxyGetters.getL2BootloaderBytecodeHash()).to.equal(bootloaderHash); expect(await proxyGetters.getL2DefaultAccountBytecodeHash()).to.equal(defaultAccountHash); expect((await proxyGetters.getVerifier()).toLowerCase()).to.equal(newVerifier.toLowerCase()); - expect(await proxyGetters.getProtocolVersion()).to.equal(4 + 1 + initialProtocolVersion); + expect(await proxyGetters.getProtocolVersion()).to.equal(addToProtocolVersion(initialProtocolVersion, 5, 0)); const newVerifierParams = await proxyGetters.getVerifierParams(); expect(newVerifierParams.recursionNodeLevelVkHash).to.equal(newerVerifierParams.recursionNodeLevelVkHash); @@ -410,8 +475,12 @@ describe("L2 upgrade test", function () { expect(newVerifierParams.recursionCircuitsSetVksHash).to.equal(newerVerifierParams.recursionCircuitsSetVksHash); expect(upgradeEvents[0].name).to.eq("NewProtocolVersion"); - expect(upgradeEvents[0].args.previousProtocolVersion.toString()).to.eq((2 + initialProtocolVersion).toString()); - expect(upgradeEvents[0].args.newProtocolVersion.toString()).to.eq((4 + 1 + initialProtocolVersion).toString()); + expect(upgradeEvents[0].args.previousProtocolVersion.toString()).to.eq( + addToProtocolVersion(initialProtocolVersion, 2, 0).toString() + ); + expect(upgradeEvents[0].args.newProtocolVersion.toString()).to.eq( + addToProtocolVersion(initialProtocolVersion, 5, 0).toString() + ); expect(upgradeEvents[1].name).to.eq("NewVerifier"); expect(upgradeEvents[1].args.oldVerifier.toLowerCase()).to.eq(verifier.toLowerCase()); @@ -434,6 +503,84 @@ describe("L2 upgrade test", function () { expect(upgradeEvents[4].args.newBytecodeHash).to.eq(defaultAccountHash); }); + it("Should successfully perform a patch upgrade even if there is a pending minor upgrade", async () => { + const currentVerifier = await proxyGetters.getVerifier(); + const currentVerifierParams = await proxyGetters.getVerifierParams(); + const currentBootloaderHash = await proxyGetters.getL2BootloaderBytecodeHash(); + const currentL2DefaultAccountBytecodeHash = await proxyGetters.getL2DefaultAccountBytecodeHash(); + + const testnetVerifierFactory = await hardhat.ethers.getContractFactory("TestnetVerifier"); + const testnetVerifierContract = await testnetVerifierFactory.deploy(); + const newVerifier = testnetVerifierContract.address; + const newerVerifierParams = buildVerifierParams({ + recursionNodeLevelVkHash: ethers.utils.hexlify(ethers.utils.randomBytes(32)), + recursionLeafLevelVkHash: ethers.utils.hexlify(ethers.utils.randomBytes(32)), + recursionCircuitsSetVksHash: ethers.utils.hexlify(ethers.utils.randomBytes(32)), + }); + + const emptyTx = buildL2CanonicalTransaction({ + txType: 0, + nonce: 0, + }); + + const upgrade = { + verifier: newVerifier, + verifierParams: newerVerifierParams, + newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 5, 1), + l2ProtocolUpgradeTx: emptyTx, + }; + + const upgradeReceipt = await ( + await executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, upgrade) + ).wait(); + + const defaultUpgradeFactory = await hardhat.ethers.getContractFactory("DefaultUpgrade"); + const upgradeEvents = upgradeReceipt.logs.map((log) => { + // Not all events can be parsed there, but we don't care about them + try { + const event = defaultUpgradeFactory.interface.parseLog(log); + const parsedArgs = event.args; + return { + name: event.name, + args: parsedArgs, + }; + } catch (_) { + // lint no-empty + } + }); + + // Now, we check that all the data was set as expected + expect(await proxyGetters.getL2BootloaderBytecodeHash()).to.equal(currentBootloaderHash); + expect(await proxyGetters.getL2DefaultAccountBytecodeHash()).to.equal(currentL2DefaultAccountBytecodeHash); + expect((await proxyGetters.getVerifier()).toLowerCase()).to.equal(newVerifier.toLowerCase()); + expect(await proxyGetters.getProtocolVersion()).to.equal(addToProtocolVersion(initialProtocolVersion, 5, 1)); + + const newVerifierParams = await proxyGetters.getVerifierParams(); + expect(newVerifierParams.recursionNodeLevelVkHash).to.equal(newerVerifierParams.recursionNodeLevelVkHash); + expect(newVerifierParams.recursionLeafLevelVkHash).to.equal(newerVerifierParams.recursionLeafLevelVkHash); + expect(newVerifierParams.recursionCircuitsSetVksHash).to.equal(newerVerifierParams.recursionCircuitsSetVksHash); + + expect(upgradeEvents[0].name).to.eq("NewProtocolVersion"); + expect(upgradeEvents[0].args.previousProtocolVersion.toString()).to.eq( + addToProtocolVersion(initialProtocolVersion, 5, 0).toString() + ); + expect(upgradeEvents[0].args.newProtocolVersion.toString()).to.eq( + addToProtocolVersion(initialProtocolVersion, 5, 1).toString() + ); + + expect(upgradeEvents[1].name).to.eq("NewVerifier"); + expect(upgradeEvents[1].args.oldVerifier.toLowerCase()).to.eq(currentVerifier.toLowerCase()); + expect(upgradeEvents[1].args.newVerifier.toLowerCase()).to.eq(newVerifier.toLowerCase()); + + expect(upgradeEvents[2].name).to.eq("NewVerifierParams"); + expect(upgradeEvents[2].args.oldVerifierParams[0]).to.eq(currentVerifierParams.recursionNodeLevelVkHash); + expect(upgradeEvents[2].args.oldVerifierParams[1]).to.eq(currentVerifierParams.recursionLeafLevelVkHash); + expect(upgradeEvents[2].args.oldVerifierParams[2]).to.eq(currentVerifierParams.recursionCircuitsSetVksHash); + expect(upgradeEvents[2].args.newVerifierParams[0]).to.eq(newerVerifierParams.recursionNodeLevelVkHash); + expect(upgradeEvents[2].args.newVerifierParams[1]).to.eq(newerVerifierParams.recursionLeafLevelVkHash); + expect(upgradeEvents[2].args.newVerifierParams[2]).to.eq(newerVerifierParams.recursionCircuitsSetVksHash); + }); + it("Should fail to upgrade when there is already a pending upgrade", async () => { const bootloaderHash = ethers.utils.hexlify(hashBytecode(ethers.utils.randomBytes(32))); const defaultAccountHash = ethers.utils.hexlify(hashBytecode(ethers.utils.randomBytes(32))); @@ -448,7 +595,7 @@ describe("L2 upgrade test", function () { const myFactoryDepHash = hashBytecode(myFactoryDep); const upgradeTx = buildL2CanonicalTransaction({ factoryDeps: [myFactoryDepHash], - nonce: 5 + 1 + initialProtocolVersion, + nonce: 5 + 1 + initialMinorProtocolVersion, }); const upgrade = { @@ -459,12 +606,16 @@ describe("L2 upgrade test", function () { executeUpgradeTx: true, l2ProtocolUpgradeTx: upgradeTx, factoryDeps: [myFactoryDep], - newProtocolVersion: 5 + 1 + initialProtocolVersion, + newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 5 + 1, 0), }; const revertReason = await getCallRevertReason( executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, upgrade) ); - await rollBackToVersion((4 + 1 + initialProtocolVersion).toString(), stateTransitionManager, upgrade); + await rollBackToVersion( + addToProtocolVersion(initialProtocolVersion, 5, 1).toString(), + stateTransitionManager, + upgrade + ); expect(revertReason).to.equal("Previous upgrade has not been finalized"); }); @@ -631,7 +782,7 @@ describe("L2 upgrade test", function () { expect(await proxyGetters.getL2SystemContractsUpgradeBatchNumber()).to.equal(0); await ( await executeUpgrade(chainId, proxyGetters, stateTransitionManager, proxyAdmin, { - newProtocolVersion: 5 + 1 + initialProtocolVersion, + newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 5 + 1, 0), l2ProtocolUpgradeTx: noopUpgradeTransaction, }) ).wait(); @@ -670,7 +821,7 @@ describe("L2 upgrade test", function () { it("Should successfully commit custom upgrade", async () => { const upgradeReceipt = await ( await executeCustomUpgrade(chainId, proxyGetters, proxyAdmin, stateTransitionManager, { - newProtocolVersion: 6 + 1 + initialProtocolVersion, + newProtocolVersion: addToProtocolVersion(initialProtocolVersion, 6 + 1, 0), l2ProtocolUpgradeTx: noopUpgradeTransaction, }) ).wait(); @@ -811,7 +962,8 @@ async function executeUpgrade( contractFactory?: ethers.ethers.ContractFactory ) { if (partialUpgrade.newProtocolVersion == null) { - const newVersion = (await proxyGetters.getProtocolVersion()).add(1); + const { 0: major, 1: minor, 2: patch } = await proxyGetters.getSemverProtocolVersion(); + const newVersion = packSemver(major, minor + 1, patch); partialUpgrade.newProtocolVersion = newVersion; } const upgrade = buildProposeUpgrade(partialUpgrade); diff --git a/l1-contracts/test/unit_tests/utils.ts b/l1-contracts/test/unit_tests/utils.ts index fd34a0e7f..c42b55c19 100644 --- a/l1-contracts/test/unit_tests/utils.ts +++ b/l1-contracts/test/unit_tests/utils.ts @@ -12,8 +12,9 @@ import type { ExecutorFacet } from "../../typechain"; import type { FeeParams, L2CanonicalTransaction } from "../../src.ts/utils"; import { ADDRESS_ONE, PubdataPricingMode, EMPTY_STRING_KECCAK } from "../../src.ts/utils"; +import { packSemver } from "../../scripts/utils"; -export const CONTRACTS_GENESIS_PROTOCOL_VERSION = (21).toString(); +export const CONTRACTS_GENESIS_PROTOCOL_VERSION = packSemver(0, 21, 0).toString(); // eslint-disable-next-line @typescript-eslint/no-var-requires export const IERC20_INTERFACE = require("@openzeppelin/contracts/build/contracts/IERC20"); export const DEFAULT_REVERT_REASON = "VM did not revert"; @@ -252,7 +253,8 @@ export function createSystemLogs( export function createSystemLogsWithUpgrade( chainedPriorityTxHashKey?: BytesLike, numberOfLayer1Txs?: BigNumberish, - upgradeTxHash?: string + upgradeTxHash?: string, + previousBatchHash?: string ) { return [ constructL2Log(true, L2_TO_L1_MESSENGER, SYSTEM_LOG_KEYS.L2_TO_L1_LOGS_TREE_ROOT_KEY, ethers.constants.HashZero), @@ -264,7 +266,12 @@ export function createSystemLogsWithUpgrade( SYSTEM_LOG_KEYS.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY, ethers.constants.HashZero ), - constructL2Log(true, L2_SYSTEM_CONTEXT_ADDRESS, SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, ethers.constants.HashZero), + constructL2Log( + true, + L2_SYSTEM_CONTEXT_ADDRESS, + SYSTEM_LOG_KEYS.PREV_BATCH_HASH_KEY, + previousBatchHash ? previousBatchHash : ethers.constants.HashZero + ), constructL2Log( true, L2_BOOTLOADER_ADDRESS, @@ -310,13 +317,13 @@ export function createSystemLogsWithUpgrade( export function genesisStoredBatchInfo(): StoredBatchInfo { return { batchNumber: 0, - batchHash: ethers.constants.HashZero, - indexRepeatedStorageChanges: 0, + batchHash: "0x0000000000000000000000000000000000000000000000000000000000000001", + indexRepeatedStorageChanges: 1, numberOfLayer1Txs: 0, priorityOperationsHash: EMPTY_STRING_KECCAK, l2LogsTreeRoot: DEFAULT_L2_LOGS_TREE_ROOT_HASH, timestamp: 0, - commitment: ethers.constants.HashZero, + commitment: "0x0000000000000000000000000000000000000000000000000000000000000001", }; } @@ -426,7 +433,12 @@ export async function buildCommitBatchInfoWithUpgrade( upgradeTxHash: string ): Promise { const timestamp = info.timestamp || (await hardhat.ethers.provider.getBlock("latest")).timestamp; - const systemLogs = createSystemLogsWithUpgrade(info.priorityOperationsHash, info.numberOfLayer1Txs, upgradeTxHash); + const systemLogs = createSystemLogsWithUpgrade( + info.priorityOperationsHash, + info.numberOfLayer1Txs, + upgradeTxHash, + ethers.utils.hexlify(prevInfo.batchHash) + ); systemLogs[SYSTEM_LOG_KEYS.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY] = constructL2Log( true, L2_SYSTEM_CONTEXT_ADDRESS, @@ -436,7 +448,7 @@ export async function buildCommitBatchInfoWithUpgrade( return { timestamp, - indexRepeatedStorageChanges: 0, + indexRepeatedStorageChanges: 1, newStateRoot: ethers.utils.randomBytes(32), numberOfLayer1Txs: 0, priorityOperationsHash: EMPTY_STRING_KECCAK, diff --git a/l2-contracts/contracts/bridge/L2SharedBridge.sol b/l2-contracts/contracts/bridge/L2SharedBridge.sol index e8102022b..2a0fe1903 100644 --- a/l2-contracts/contracts/bridge/L2SharedBridge.sol +++ b/l2-contracts/contracts/bridge/L2SharedBridge.sol @@ -22,8 +22,8 @@ import {EmptyAddress, EmptyBytes32, InvalidCaller, AddressMismatch, AmountMustBe /// @notice The "default" bridge implementation for the ERC20 tokens. Note, that it does not /// support any custom token logic, i.e. rebase tokens' functionality is not supported. contract L2SharedBridge is IL2SharedBridge, Initializable { - /// @dev The address of the L1 bridge counterpart. - address public override l1Bridge; + /// @dev The address of the L1 shared bridge counterpart. + address public override l1SharedBridge; /// @dev Contract that stores the implementation address for token. /// @dev For more details see https://docs.openzeppelin.com/contracts/3.x/api/proxy#UpgradeableBeacon. @@ -35,9 +35,11 @@ contract L2SharedBridge is IL2SharedBridge, Initializable { /// @dev A mapping l2 token address => l1 token address mapping(address l2TokenAddress => address l1TokenAddress) public override l1TokenAddress; - address private l1LegacyBridge; + /// @dev The address of the legacy L1 erc20 bridge counterpart. + /// This is non-zero only on Era, and should not be renamed for backward compatibility with the SDKs. + address public override l1Bridge; - uint256 internal immutable ERA_CHAIN_ID; + uint256 public immutable ERA_CHAIN_ID; /// @dev Contract is expected to be used as proxy implementation. /// @dev Disable the initialization to prevent Parity hack. @@ -47,16 +49,17 @@ contract L2SharedBridge is IL2SharedBridge, Initializable { } /// @notice Initializes the bridge contract for later use. Expected to be used in the proxy. - /// @param _l1Bridge The address of the L1 Bridge contract. + /// @param _l1SharedBridge The address of the L1 Bridge contract. + /// @param _l1Bridge The address of the legacy L1 Bridge contract. /// @param _l2TokenProxyBytecodeHash The bytecode hash of the proxy for tokens deployed by the bridge. /// @param _aliasedOwner The address of the governor contract. function initialize( + address _l1SharedBridge, address _l1Bridge, - address _l1LegacyBridge, bytes32 _l2TokenProxyBytecodeHash, address _aliasedOwner ) external reinitializer(2) { - if (_l1Bridge == address(0)) { + if (_l1SharedBridge == address(0)) { revert EmptyAddress(); } @@ -68,7 +71,7 @@ contract L2SharedBridge is IL2SharedBridge, Initializable { revert EmptyAddress(); } - l1Bridge = _l1Bridge; + l1SharedBridge = _l1SharedBridge; if (block.chainid != ERA_CHAIN_ID) { address l2StandardToken = address(new L2StandardERC20{salt: bytes32(0)}()); @@ -76,10 +79,10 @@ contract L2SharedBridge is IL2SharedBridge, Initializable { l2TokenProxyBytecodeHash = _l2TokenProxyBytecodeHash; l2TokenBeacon.transferOwnership(_aliasedOwner); } else { - if (_l1LegacyBridge == address(0)) { + if (_l1Bridge == address(0)) { revert EmptyAddress(); } - l1LegacyBridge = _l1LegacyBridge; + l1Bridge = _l1Bridge; // l2StandardToken and l2TokenBeacon are already deployed on ERA, and stored in the proxy } } @@ -100,7 +103,7 @@ contract L2SharedBridge is IL2SharedBridge, Initializable { // Only the L1 bridge counterpart can initiate and finalize the deposit. if ( AddressAliasHelper.undoL1ToL2Alias(msg.sender) != l1Bridge && - AddressAliasHelper.undoL1ToL2Alias(msg.sender) != l1LegacyBridge + AddressAliasHelper.undoL1ToL2Alias(msg.sender) != l1SharedBridge ) { revert InvalidCaller(msg.sender); } diff --git a/l2-contracts/contracts/bridge/interfaces/IL2SharedBridge.sol b/l2-contracts/contracts/bridge/interfaces/IL2SharedBridge.sol index 0f16e2360..c1aa05102 100644 --- a/l2-contracts/contracts/bridge/interfaces/IL2SharedBridge.sol +++ b/l2-contracts/contracts/bridge/interfaces/IL2SharedBridge.sol @@ -33,4 +33,6 @@ interface IL2SharedBridge { function l2TokenAddress(address _l1Token) external view returns (address); function l1Bridge() external view returns (address); + + function l1SharedBridge() external view returns (address); } diff --git a/l2-contracts/contracts/dev-contracts/DevL2SharedBridge.sol b/l2-contracts/contracts/dev-contracts/DevL2SharedBridge.sol new file mode 100644 index 000000000..12a6a187f --- /dev/null +++ b/l2-contracts/contracts/dev-contracts/DevL2SharedBridge.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {L2SharedBridge} from "../bridge/L2SharedBridge.sol"; +import {L2StandardERC20} from "../bridge/L2StandardERC20.sol"; +import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; + +/// @author Matter Labs +/// @notice The implementation of the shared bridge that allows setting legacy bridge. Must only be used in local testing environments. +contract DevL2SharedBridge is L2SharedBridge { + constructor(uint256 _eraChainId) L2SharedBridge(_eraChainId) {} + + function initializeDevBridge( + address _l1SharedBridge, + address _l1Bridge, + bytes32 _l2TokenProxyBytecodeHash, + address _aliasedOwner + ) external reinitializer(2) { + l1SharedBridge = _l1SharedBridge; + + address l2StandardToken = address(new L2StandardERC20{salt: bytes32(0)}()); + l2TokenBeacon = new UpgradeableBeacon{salt: bytes32(0)}(l2StandardToken); + l2TokenProxyBytecodeHash = _l2TokenProxyBytecodeHash; + l2TokenBeacon.transferOwnership(_aliasedOwner); + + // Unfortunately the `l1Bridge` is not an internal variable in the parent contract. + // To keep the changes to the production code minimal, we'll just manually set the variable here. + assembly { + sstore(4, _l1Bridge) + } + } +} diff --git a/l2-contracts/hardhat.config.ts b/l2-contracts/hardhat.config.ts index 155611b6a..c0aaca03e 100644 --- a/l2-contracts/hardhat.config.ts +++ b/l2-contracts/hardhat.config.ts @@ -43,7 +43,7 @@ export default { // contract verification endpoint verifyURL: "https://explorer.sepolia.era.zksync.dev/contract_verification", }, - zksyncMainnet: { + zkSyncMainnet: { url: "https://mainnet.era.zksync.io", ethNetwork: "mainnet", zksync: true, diff --git a/l2-contracts/package.json b/l2-contracts/package.json index 16138754b..c7fecce29 100644 --- a/l2-contracts/package.json +++ b/l2-contracts/package.json @@ -45,7 +45,8 @@ "publish-bridge-preimages": "ts-node src/publish-bridge-preimages.ts", "deploy-l2-weth": "ts-node src/deploy-l2-weth.ts", "upgrade-bridge-contracts": "ts-node src/upgrade-bridge-impl.ts", - "update-l2-erc20-metadata": "ts-node src/update-l2-erc20-metadata.ts" + "update-l2-erc20-metadata": "ts-node src/update-l2-erc20-metadata.ts", + "upgrade-consistency-checker": "ts-node src/upgrade-consistency-checker.ts" }, "dependencies": { "dotenv": "^16.0.3" diff --git a/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts b/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts index d24fe38f4..5bf18af74 100644 --- a/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts +++ b/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts @@ -19,7 +19,6 @@ import { GAS_MULTIPLIER } from "../../l1-contracts/scripts/utils"; import * as hre from "hardhat"; export const L2_SHARED_BRIDGE_ABI = hre.artifacts.readArtifactSync("L2SharedBridge").abi; -export const L2_SHARED_BRIDGE_IMPLEMENTATION_BYTECODE = hre.artifacts.readArtifactSync("L2SharedBridge").bytecode; export const L2_STANDARD_TOKEN_PROXY_BYTECODE = hre.artifacts.readArtifactSync("BeaconProxy").bytecode; export async function publishL2SharedBridgeDependencyBytecodesOnL2( @@ -60,18 +59,23 @@ export async function deploySharedBridgeImplOnL2ThroughL1( console.log("Deploying L2SharedBridge Implementation"); } const eraChainId = process.env.CONTRACTS_ERA_CHAIN_ID; - if (!L2_SHARED_BRIDGE_IMPLEMENTATION_BYTECODE) { - throw new Error("L2_SHARED_BRIDGE_IMPLEMENTATION_BYTECODE not found"); + + const l2SharedBridgeImplementationBytecode = localLegacyBridgeTesting + ? hre.artifacts.readArtifactSync("DevL2SharedBridge").bytecode + : hre.artifacts.readArtifactSync("L2SharedBridge").bytecode; + + if (!l2SharedBridgeImplementationBytecode) { + throw new Error("l2SharedBridgeImplementationBytecode not found"); } if (deployer.verbose) { - console.log("L2_SHARED_BRIDGE_IMPLEMENTATION_BYTECODE loaded"); + console.log("l2SharedBridgeImplementationBytecode loaded"); console.log("Computing L2SharedBridge Implementation Address"); } const l2SharedBridgeImplAddress = computeL2Create2Address( deployer.deployWallet, - L2_SHARED_BRIDGE_IMPLEMENTATION_BYTECODE, - defaultAbiCoder.encode(["uint256"], [localLegacyBridgeTesting ? 0 : eraChainId]), + l2SharedBridgeImplementationBytecode, + defaultAbiCoder.encode(["uint256"], [eraChainId]), ethers.constants.HashZero ); deployer.addresses.Bridges.L2SharedBridgeImplementation = l2SharedBridgeImplAddress; @@ -88,8 +92,8 @@ export async function deploySharedBridgeImplOnL2ThroughL1( const tx2 = await create2DeployFromL1( chainId, deployer.deployWallet, - L2_SHARED_BRIDGE_IMPLEMENTATION_BYTECODE, - defaultAbiCoder.encode(["uint256"], [localLegacyBridgeTesting ? 0 : eraChainId]), + l2SharedBridgeImplementationBytecode, + defaultAbiCoder.encode(["uint256"], [eraChainId]), ethers.constants.HashZero, priorityTxMaxGasLimit, gasPrice, @@ -106,7 +110,8 @@ export async function deploySharedBridgeImplOnL2ThroughL1( export async function deploySharedBridgeProxyOnL2ThroughL1( deployer: Deployer, chainId: string, - gasPrice: BigNumberish + gasPrice: BigNumberish, + localLegacyBridgeTesting: boolean = false ) { const l1SharedBridge = deployer.defaultSharedBridge(deployer.deployWallet); if (deployer.verbose) { @@ -114,13 +119,25 @@ export async function deploySharedBridgeProxyOnL2ThroughL1( } /// prepare proxyInitializationParams const l2GovernorAddress = applyL1ToL2Alias(deployer.addresses.Governance); - const l2SharedBridgeInterface = new Interface(hre.artifacts.readArtifactSync("L2SharedBridge").abi); - const proxyInitializationParams = l2SharedBridgeInterface.encodeFunctionData("initialize", [ - l1SharedBridge.address, - deployer.addresses.Bridges.ERC20BridgeProxy, - hashL2Bytecode(L2_STANDARD_TOKEN_PROXY_BYTECODE), - l2GovernorAddress, - ]); + + let proxyInitializationParams; + if (localLegacyBridgeTesting) { + const l2SharedBridgeInterface = new Interface(hre.artifacts.readArtifactSync("DevL2SharedBridge").abi); + proxyInitializationParams = l2SharedBridgeInterface.encodeFunctionData("initializeDevBridge", [ + l1SharedBridge.address, + deployer.addresses.Bridges.ERC20BridgeProxy, + hashL2Bytecode(L2_STANDARD_TOKEN_PROXY_BYTECODE), + l2GovernorAddress, + ]); + } else { + const l2SharedBridgeInterface = new Interface(hre.artifacts.readArtifactSync("L2SharedBridge").abi); + proxyInitializationParams = l2SharedBridgeInterface.encodeFunctionData("initialize", [ + l1SharedBridge.address, + deployer.addresses.Bridges.ERC20BridgeProxy, + hashL2Bytecode(L2_STANDARD_TOKEN_PROXY_BYTECODE), + l2GovernorAddress, + ]); + } /// prepare constructor data const l2SharedBridgeProxyConstructorData = ethers.utils.arrayify( @@ -183,12 +200,15 @@ export async function deploySharedBridgeOnL2ThroughL1( deployer: Deployer, chainId: string, gasPrice: BigNumberish, - localLegacyBridgeTesting: boolean = false + localLegacyBridgeTesting: boolean, + skipInitializeChainGovernance: boolean ) { await publishL2SharedBridgeDependencyBytecodesOnL2(deployer, chainId, gasPrice); await deploySharedBridgeImplOnL2ThroughL1(deployer, chainId, gasPrice, localLegacyBridgeTesting); - await deploySharedBridgeProxyOnL2ThroughL1(deployer, chainId, gasPrice); - await initializeChainGovernance(deployer, chainId); + await deploySharedBridgeProxyOnL2ThroughL1(deployer, chainId, gasPrice, localLegacyBridgeTesting); + if (!skipInitializeChainGovernance) { + await initializeChainGovernance(deployer, chainId); + } } async function main() { @@ -203,6 +223,7 @@ async function main() { .option("--gas-price ") .option("--nonce ") .option("--erc20-bridge ") + .option("--skip-initialize-chain-governance ") .action(async (cmd) => { const chainId: string = cmd.chainId ? cmd.chainId : process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID; const deployWallet = cmd.privateKey @@ -227,7 +248,19 @@ async function main() { : (await provider.getGasPrice()).mul(GAS_MULTIPLIER); console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); - await deploySharedBridgeOnL2ThroughL1(deployer, chainId, gasPrice, cmd.localLegacyBridgeTesting); + const skipInitializeChainGovernance = + !!cmd.skipInitializeChainGovernance && cmd.skipInitializeChainGovernance === "true"; + if (skipInitializeChainGovernance) { + console.log("Initialization of the chain governance will be skipped"); + } + + await deploySharedBridgeOnL2ThroughL1( + deployer, + chainId, + gasPrice, + cmd.localLegacyBridgeTesting, + skipInitializeChainGovernance + ); }); await program.parseAsync(process.argv); diff --git a/l2-contracts/src/upgrade-consistency-checker.ts b/l2-contracts/src/upgrade-consistency-checker.ts new file mode 100644 index 000000000..8bebe197d --- /dev/null +++ b/l2-contracts/src/upgrade-consistency-checker.ts @@ -0,0 +1,129 @@ +/// This is the script to double check the consistency of the upgrade +/// It is yet to be refactored. + +// hardhat import should be the first import in the file +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import * as hardhat from "hardhat"; +import { Command } from "commander"; +import { ethers } from "ethers"; +import { Provider } from "zksync-ethers"; + +// Things that still have to be manually double checked: +// 1. Contracts must be verified. +// 2. Getter methods in STM. + +// List the contracts that should become the upgrade targets +const l2BridgeImplAddr = "0x470afaacce2acdaefcc662419b74c79d76c914ae"; + +const eraChainId = 324; + +const l2Provider = new Provider(process.env.API_WEB3_JSON_RPC_HTTP_URL); + +async function checkIdenticalBytecode(addr: string, contract: string) { + const correctCode = (await hardhat.artifacts.readArtifact(contract)).deployedBytecode; + const currentCode = await l2Provider.getCode(addr); + + if (ethers.utils.keccak256(currentCode) == ethers.utils.keccak256(correctCode)) { + console.log(contract, "bytecode is correct"); + } else { + throw new Error(contract + " bytecode is not correct"); + } +} + +async function checkL2SharedBridgeImpl() { + await checkIdenticalBytecode(l2BridgeImplAddr, "L2SharedBridge"); + + // In Era we can retrieve the immutable from the simulator + const contract = new ethers.Contract( + "0x0000000000000000000000000000000000008005", + immutableSimulatorAbi(), + l2Provider + ); + + const usedEraChainId = ethers.BigNumber.from(await contract.getImmutable(l2BridgeImplAddr, 0)); + if (!usedEraChainId.eq(ethers.BigNumber.from(eraChainId))) { + throw new Error("Era chain id is not correct"); + } + console.log("L2SharedBridge is correct!"); +} + +async function main() { + const program = new Command(); + + program + .version("0.1.0") + .name("upgrade-consistency-checker") + .description("upgrade shared bridge for era diamond proxy"); + + program.action(async () => { + await checkL2SharedBridgeImpl(); + }); + + await program.parseAsync(process.argv); +} + +main() + .then(() => process.exit(0)) + .catch((err) => { + console.error("Error:", err); + process.exit(1); + }); + +function immutableSimulatorAbi() { + return [ + { + inputs: [ + { + internalType: "address", + name: "_dest", + type: "address", + }, + { + internalType: "uint256", + name: "_index", + type: "uint256", + }, + ], + name: "getImmutable", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_dest", + type: "address", + }, + { + components: [ + { + internalType: "uint256", + name: "index", + type: "uint256", + }, + { + internalType: "bytes32", + name: "value", + type: "bytes32", + }, + ], + internalType: "struct ImmutableData[]", + name: "_immutables", + type: "tuple[]", + }, + ], + name: "setImmutables", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + ]; +} diff --git a/l2-contracts/src/verify.ts b/l2-contracts/src/verify.ts index 53fc28f33..a45c83795 100644 --- a/l2-contracts/src/verify.ts +++ b/l2-contracts/src/verify.ts @@ -22,13 +22,14 @@ async function main() { // Contracts without constructor parameters for (const address of [ process.env.CONTRACTS_L2_WETH_TOKEN_IMPL_ADDR, - process.env.CONTRACTS_L2_ERC20_BRIDGE_IMPL_ADDR, process.env.CONTRACTS_L2_ERC20_BRIDGE_TOKEN_IMPL_ADDR, ]) { const promise = verifyPromise(address); promises.push(promise); } + promises.push(verifyPromise(process.env.CONTRACTS_L2_SHARED_BRIDGE_IMPL_ADDR, [process.env.CONTRACTS_ERA_CHAIN_ID])); + const messages = await Promise.allSettled(promises); for (const message of messages) { console.log(message.status == "fulfilled" ? message.value : message.reason); diff --git a/package.json b/package.json index edb9817b9..5aeef0ff2 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "packages": [ "l1-contracts", "l2-contracts", - "system-contracts" + "system-contracts", + "gas-bound-caller" ], "nohoist": [ "**/@openzeppelin/**" @@ -36,6 +37,7 @@ "prettier:fix": "prettier --write \"**/*.{js,json,md,sol,ts,yaml}\"", "l1": "yarn workspace l1-contracts", "l2": "yarn workspace l2-contracts", - "sc": "yarn workspace system-contracts" + "sc": "yarn workspace system-contracts", + "gas-bound-caller": "yarn workspace gas-bound-caller" } } diff --git a/system-contracts/SystemContractsHashes.json b/system-contracts/SystemContractsHashes.json index e56f2dcc0..5c927c10f 100644 --- a/system-contracts/SystemContractsHashes.json +++ b/system-contracts/SystemContractsHashes.json @@ -55,13 +55,6 @@ "bytecodeHash": "0x0100000781e55a60f3f14fd7dd67e3c8caab896b7b0fca4a662583959299eede", "sourceCodeHash": "0xc88a4210dda96bc21fc852860fb74a4efeb0cc4101ffe6d928551cab46d15263" }, - { - "contractName": "GasBoundCaller", - "bytecodePath": "artifacts-zk/contracts-preprocessed/GasBoundCaller.sol/GasBoundCaller.json", - "sourceCodePath": "contracts-preprocessed/GasBoundCaller.sol", - "bytecodeHash": "0x010000abbab74213f27d4d7f132761f7315ad84dd2239195f336aa910c9c92fc", - "sourceCodeHash": "0x7774719fe82d1664af19f3f30e9ca2223784374367d32399638671722e9da850" - }, { "contractName": "ImmutableSimulator", "bytecodePath": "artifacts-zk/contracts-preprocessed/ImmutableSimulator.sol/ImmutableSimulator.json", @@ -185,35 +178,35 @@ "contractName": "bootloader_test", "bytecodePath": "bootloader/build/artifacts/bootloader_test.yul.zbin", "sourceCodePath": "bootloader/build/bootloader_test.yul", - "bytecodeHash": "0x010003cbda052f30cd056d0c758c7cc5be2c31f77461591d4be73dc3291b6ca5", - "sourceCodeHash": "0xa13475403f910dff1ea0cf7ebf80633d9dcc37b4f8dd9f0c678a935793580932" + "bytecodeHash": "0x010003cb92f46b717a3351f085b9d0d7cf1b6c164f390487f29da5c3b1f272a1", + "sourceCodeHash": "0xbf798f55f3b5c3d4d29423278bd68c6bb6428f0290f6791b2e20963166b3b21a" }, { "contractName": "fee_estimate", "bytecodePath": "bootloader/build/artifacts/fee_estimate.yul.zbin", "sourceCodePath": "bootloader/build/fee_estimate.yul", - "bytecodeHash": "0x010009519c960b6baa12091cd1f8538d00fa0d7505c81943ba6e379a5f1c9c9e", - "sourceCodeHash": "0xbcdce81799656df718b5dfff4ba6ce8aac82087a78c0c33edb0675e55bb8364c" + "bytecodeHash": "0x01000951d5e14249434340fe5e6be707157f16d66ca10d0e5fcc110cc674def4", + "sourceCodeHash": "0xc7cd680273e89b1dfc3c6941f69611894c885e26656adfc4586c4eb149285d9d" }, { "contractName": "gas_test", "bytecodePath": "bootloader/build/artifacts/gas_test.yul.zbin", "sourceCodePath": "bootloader/build/gas_test.yul", - "bytecodeHash": "0x010008d7f0ddbde3a633aa89aff433666ba28dcf3adb35deb26a207ea1824454", - "sourceCodeHash": "0xe08f6eaca1995981baf257dd86e5dec8b62c1b5ac53a1173142cebca510b7859" + "bytecodeHash": "0x010008d7db21670fda613a703321bb109f192ef92a6cef40b07a35a661c4d563", + "sourceCodeHash": "0xaf85e223e2156440f007fed14c9fe8899d33ad2f0aeb7feab3f4fd81d759bfb6" }, { "contractName": "playground_batch", "bytecodePath": "bootloader/build/artifacts/playground_batch.yul.zbin", "sourceCodePath": "bootloader/build/playground_batch.yul", - "bytecodeHash": "0x01000957fe599ea1dfc3fc958c63b11f46a726d9462078b77c3f28c4db21446c", - "sourceCodeHash": "0x97e33d4d96f9ee25e171add92ec7d725de58904dfc68466e4aae1807e9997dc2" + "bytecodeHash": "0x0100095722e18e6831abbece78d3bbaac4fe187db091d6be75d0c6f59aff0f7f", + "sourceCodeHash": "0xac0a99374868ffafa2427f23a7fd1f5f8551402904efbdf84f38638cad00ec14" }, { "contractName": "proved_batch", "bytecodePath": "bootloader/build/artifacts/proved_batch.yul.zbin", "sourceCodePath": "bootloader/build/proved_batch.yul", - "bytecodeHash": "0x010008e756cd4ef7a22e5bf21b357fda5c90223b7947f38cd3749826a6faff68", - "sourceCodeHash": "0x62556a75277d8c72961a1fb9e1d0e1f1cef41c0e8f8d26a6e85cda336a734834" + "bytecodeHash": "0x010008e75c912d19d915dc90467be59c81c8004271b03639354806f669f14ad3", + "sourceCodeHash": "0x25e4cdb4020792f534611de304297e7e0230ed7ea0510bc049862c2540cc458e" } ] diff --git a/system-contracts/bootloader/bootloader.yul b/system-contracts/bootloader/bootloader.yul index 96b4e70e2..85cc33dda 100644 --- a/system-contracts/bootloader/bootloader.yul +++ b/system-contracts/bootloader/bootloader.yul @@ -405,7 +405,7 @@ object "Bootloader" { /// for the sake of simplicity we will spend 32 bytes on each /// of those for now. function MAX_MEM_SIZE() -> ret { - ret := 59000000 + ret := 63800000 } function L1_TX_INTRINSIC_L2_GAS() -> ret { diff --git a/system-contracts/bootloader/test_infra/Cargo.lock b/system-contracts/bootloader/test_infra/Cargo.lock index 3a764c769..6fa43fd45 100644 --- a/system-contracts/bootloader/test_infra/Cargo.lock +++ b/system-contracts/bootloader/test_infra/Cargo.lock @@ -131,6 +131,51 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.69" @@ -214,7 +259,6 @@ dependencies = [ "num-bigint", "num-integer", "num-traits", - "serde", ] [[package]] @@ -354,7 +398,7 @@ dependencies = [ [[package]] name = "boojum" version = "0.2.0" -source = "git+https://github.com/matter-labs/era-boojum.git?branch=main#30300f043c9afaeeb35d0f7bd3cc0acaf69ccde4" +source = "git+https://github.com/matter-labs/era-boojum.git?branch=main#19988079852ea22576da6b09e39365e6cdc1368f" dependencies = [ "arrayvec 0.7.4", "bincode", @@ -577,7 +621,7 @@ dependencies = [ [[package]] name = "circuit_encodings" version = "0.1.42" -source = "git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.4.2#012dcc678990c695f97e5dd1f136dfa8fe376c16" +source = "git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.4.2#3149a162a729581005fbad6dbcef027a3ee1b214" dependencies = [ "derivative", "serde", @@ -585,6 +629,17 @@ dependencies = [ "zkevm_circuits 1.4.1", ] +[[package]] +name = "circuit_encodings" +version = "0.1.50" +source = "git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.5.0#394e1c7d1aec06d2f3abd63bdc2ddf0efef5ac49" +dependencies = [ + "derivative", + "serde", + "zk_evm 1.5.0", + "zkevm_circuits 1.5.0", +] + [[package]] name = "circuit_sequencer_api" version = "0.1.0" @@ -626,7 +681,7 @@ dependencies = [ [[package]] name = "circuit_sequencer_api" version = "0.1.42" -source = "git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.4.2#012dcc678990c695f97e5dd1f136dfa8fe376c16" +source = "git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.4.2#3149a162a729581005fbad6dbcef027a3ee1b214" dependencies = [ "bellman_ce", "circuit_encodings 0.1.42", @@ -636,6 +691,18 @@ dependencies = [ "zk_evm 1.4.1", ] +[[package]] +name = "circuit_sequencer_api" +version = "0.1.50" +source = "git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.5.0#394e1c7d1aec06d2f3abd63bdc2ddf0efef5ac49" +dependencies = [ + "bellman_ce", + "circuit_encodings 0.1.50", + "derivative", + "rayon", + "serde", +] + [[package]] name = "clang-sys" version = "1.6.1" @@ -660,7 +727,8 @@ dependencies = [ [[package]] name = "compile-fmt" version = "0.1.0" -source = "git+https://github.com/slowli/compile-fmt.git?rev=c6a41c846c9a6f70cdba4b44c9f3922242ffcf12#c6a41c846c9a6f70cdba4b44c9f3922242ffcf12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bed69047ed42e52c7e38d6421eeb8ceefb4f2a2b52eed59137f7bad7908f6800" [[package]] name = "const-oid" @@ -922,7 +990,7 @@ dependencies = [ [[package]] name = "cs_derive" version = "0.1.0" -source = "git+https://github.com/matter-labs/era-boojum.git?branch=main#30300f043c9afaeeb35d0f7bd3cc0acaf69ccde4" +source = "git+https://github.com/matter-labs/era-boojum.git?branch=main#19988079852ea22576da6b09e39365e6cdc1368f" dependencies = [ "proc-macro-error", "proc-macro2", @@ -958,41 +1026,6 @@ dependencies = [ "syn 2.0.40", ] -[[package]] -name = "darling" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 1.0.109", -] - -[[package]] -name = "darling_macro" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" -dependencies = [ - "darling_core", - "quote", - "syn 1.0.109", -] - [[package]] name = "dashmap" version = "5.5.3" @@ -1070,6 +1103,27 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_more" +version = "1.0.0-beta.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7abbfc297053be59290e3152f8cbcd52c8642e0728b69ee187d991d4c1af08d" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0-beta.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bba3e9872d7c58ce7ef0fcf1844fcc3e23ef2a58377b50df35dd98e42a5726e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.40", + "unicode-xid", +] + [[package]] name = "digest" version = "0.9.0" @@ -1646,7 +1700,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 2.1.0", "slab", "tokio", "tokio-util", @@ -1714,6 +1768,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.3" @@ -1836,6 +1896,18 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -1872,12 +1944,6 @@ dependencies = [ "cc", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "0.4.0" @@ -1936,6 +2002,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.1.0" @@ -2111,6 +2187,8 @@ dependencies = [ "glob", "libc", "libz-sys", + "lz4-sys", + "zstd-sys", ] [[package]] @@ -2209,6 +2287,16 @@ dependencies = [ "logos-codegen", ] +[[package]] +name = "lz4-sys" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "match_cfg" version = "0.1.0" @@ -2224,6 +2312,12 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -2264,28 +2358,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "metrics" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fde3af1a009ed76a778cb84fdef9e7dbbdf5775ae3e4cc1f434a6a307f6f76c5" -dependencies = [ - "ahash 0.8.6", - "metrics-macros", - "portable-atomic", -] - -[[package]] -name = "metrics-macros" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddece26afd34c31585c74a4db0630c376df271c285d682d1e55012197830b6df" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.40", -] - [[package]] name = "miette" version = "5.10.0" @@ -2365,13 +2437,14 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "multivm" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?rev=ce5743679f18d737b459635c4d0d7b07dbf6ee2d#ce5743679f18d737b459635c4d0d7b07dbf6ee2d" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=c4d1c49282a3710be5d491636ba26207efb1f9ce#c4d1c49282a3710be5d491636ba26207efb1f9ce" dependencies = [ "anyhow", "circuit_sequencer_api 0.1.0", "circuit_sequencer_api 0.1.40", "circuit_sequencer_api 0.1.41", "circuit_sequencer_api 0.1.42", + "circuit_sequencer_api 0.1.50", "hex", "itertools 0.10.5", "once_cell", @@ -2383,10 +2456,6 @@ dependencies = [ "zk_evm 1.4.0", "zk_evm 1.4.1", "zk_evm 1.5.0", - "zkevm_test_harness 1.3.3", - "zkevm_test_harness 1.4.0", - "zkevm_test_harness 1.4.1", - "zkevm_test_harness 1.4.2", "zksync_contracts", "zksync_state", "zksync_system_constants", @@ -2566,7 +2635,16 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" dependencies = [ - "num_enum_derive", + "num_enum_derive 0.6.1", +] + +[[package]] +name = "num_enum" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +dependencies = [ + "num_enum_derive 0.7.2", ] [[package]] @@ -2581,6 +2659,18 @@ dependencies = [ "syn 2.0.40", ] +[[package]] +name = "num_enum_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +dependencies = [ + "proc-macro-crate 2.0.1", + "proc-macro2", + "quote", + "syn 2.0.40", +] + [[package]] name = "object" version = "0.32.1" @@ -2646,6 +2736,110 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "opentelemetry" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9591d937bc0e6d2feb6f71a559540ab300ea49955229c347a517a28d27784c54" +dependencies = [ + "opentelemetry_api", + "opentelemetry_sdk", +] + +[[package]] +name = "opentelemetry-http" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7594ec0e11d8e33faf03530a4c49af7064ebba81c1480e01be67d90b356508b" +dependencies = [ + "async-trait", + "bytes", + "http", + "opentelemetry_api", + "reqwest", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e5e5a5c4135864099f3faafbe939eb4d7f9b80ebf68a8448da961b32a7c1275" +dependencies = [ + "async-trait", + "futures-core", + "http", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry-semantic-conventions", + "opentelemetry_api", + "opentelemetry_sdk", + "prost 0.11.9", + "reqwest", + "thiserror", + "tokio", + "tonic", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e3f814aa9f8c905d0ee4bde026afd3b2577a97c10e1699912e3e44f0c4cbeb" +dependencies = [ + "opentelemetry_api", + "opentelemetry_sdk", + "prost 0.11.9", + "tonic", +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73c9f9340ad135068800e7f1b24e9e09ed9e7143f5bf8518ded3d3ec69789269" +dependencies = [ + "opentelemetry", +] + +[[package]] +name = "opentelemetry_api" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a81f725323db1b1206ca3da8bb19874bbd3f57c3bcd59471bfb04525b265b9b" +dependencies = [ + "futures-channel", + "futures-util", + "indexmap 1.9.3", + "js-sys", + "once_cell", + "pin-project-lite", + "thiserror", + "urlencoding", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa8e705a0612d48139799fcbaba0d4a90f06277153e43dd2bdc16c6f0edd8026" +dependencies = [ + "async-trait", + "crossbeam-channel 0.5.8", + "futures-channel", + "futures-executor", + "futures-util", + "once_cell", + "opentelemetry_api", + "ordered-float 3.9.2", + "percent-encoding", + "rand 0.8.5", + "regex", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", +] + [[package]] name = "ordered-float" version = "2.10.1" @@ -2655,6 +2849,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ordered-float" +version = "3.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" +dependencies = [ + "num-traits", +] + [[package]] name = "os_info" version = "3.7.0" @@ -2710,7 +2913,7 @@ dependencies = [ [[package]] name = "pairing_ce" version = "0.28.5" -source = "git+https://github.com/matter-labs/pairing.git?rev=f55393fd366596eac792d78525d26e9c4d6ed1ca#f55393fd366596eac792d78525d26e9c4d6ed1ca" +source = "git+https://github.com/matter-labs/pairing.git?rev=d24f2c5871089c4cd4f54c0ca266bb9fef6115eb#d24f2c5871089c4cd4f54c0ca266bb9fef6115eb" dependencies = [ "byteorder", "cfg-if 1.0.0", @@ -2722,7 +2925,7 @@ dependencies = [ [[package]] name = "pairing_ce" version = "0.28.5" -source = "git+https://github.com/matter-labs/pairing.git#f55393fd366596eac792d78525d26e9c4d6ed1ca" +source = "git+https://github.com/matter-labs/pairing.git#d24f2c5871089c4cd4f54c0ca266bb9fef6115eb" dependencies = [ "byteorder", "cfg-if 1.0.0", @@ -2814,7 +3017,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap", + "indexmap 2.1.0", ] [[package]] @@ -2892,12 +3095,6 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" -[[package]] -name = "portable-atomic" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" - [[package]] name = "powerfmt" version = "0.2.0" @@ -2997,9 +3194,9 @@ dependencies = [ [[package]] name = "prometheus-client" -version = "0.21.2" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c99afa9a01501019ac3a14d71d9f94050346f55ca471ce90c799a15c58f61e2" +checksum = "c1ca959da22a332509f2a73ae9e5f23f9dcfc31fd3a54d71f159495bd5909baa" dependencies = [ "dtoa", "itoa", @@ -3018,6 +3215,16 @@ dependencies = [ "syn 2.0.40", ] +[[package]] +name = "prost" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +dependencies = [ + "bytes", + "prost-derive 0.11.9", +] + [[package]] name = "prost" version = "0.12.3" @@ -3025,7 +3232,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.12.3", ] [[package]] @@ -3035,14 +3242,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" dependencies = [ "bytes", - "heck", + "heck 0.4.1", "itertools 0.11.0", "log", "multimap", "once_cell", "petgraph", "prettyplease", - "prost", + "prost 0.12.3", "prost-types", "regex", "syn 2.0.40", @@ -3050,6 +3257,19 @@ dependencies = [ "which", ] +[[package]] +name = "prost-derive" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "anyhow", + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "prost-derive" version = "0.12.3" @@ -3073,7 +3293,7 @@ dependencies = [ "logos", "miette", "once_cell", - "prost", + "prost 0.12.3", "prost-types", "serde", "serde-value", @@ -3085,7 +3305,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" dependencies = [ - "prost", + "prost 0.12.3", ] [[package]] @@ -3096,7 +3316,7 @@ checksum = "00bb76c5f6221de491fe2c8f39b106330bbd9762c6511119c07940e10eb9ff11" dependencies = [ "bytes", "miette", - "prost", + "prost 0.12.3", "prost-reflect", "prost-types", "protox-parse", @@ -3812,7 +4032,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" dependencies = [ - "ordered-float", + "ordered-float 2.10.1", "serde", ] @@ -3851,26 +4071,29 @@ dependencies = [ ] [[package]] -name = "serde_with" -version = "1.14.0" +name = "serde_yaml" +version = "0.9.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +checksum = "a15e0ef66bf939a7c890a0bf6d5a733c70202225f9888a89ed5c62298b019129" dependencies = [ - "base64 0.13.1", + "indexmap 2.1.0", + "itoa", + "ryu", "serde", - "serde_with_macros", + "unsafe-libyaml", ] [[package]] -name = "serde_with_macros" -version = "1.5.2" +name = "sha-1" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 1.0.109", + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", ] [[package]] @@ -3884,19 +4107,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "sha2" version = "0.10.6" @@ -4040,6 +4250,21 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "soketto" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" +dependencies = [ + "base64 0.13.1", + "bytes", + "futures", + "httparse", + "log", + "rand 0.8.5", + "sha-1", +] + [[package]] name = "spin" version = "0.5.2" @@ -4123,7 +4348,7 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap", + "indexmap 2.1.0", "ipnetwork", "log", "memchr", @@ -4166,7 +4391,7 @@ dependencies = [ "atomic-write-file", "dotenvy", "either", - "heck", + "heck 0.4.1", "hex", "once_cell", "proc-macro2", @@ -4320,12 +4545,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strum" version = "0.24.1" @@ -4341,7 +4560,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", @@ -4388,6 +4607,12 @@ dependencies = [ "syn 2.0.40", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "system-configuration" version = "0.5.1" @@ -4573,6 +4798,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-macros" version = "2.2.0" @@ -4623,6 +4858,7 @@ checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", "pin-project-lite", "tokio", @@ -4641,7 +4877,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap", + "indexmap 2.1.0", "toml_datetime", "winnow", ] @@ -4652,11 +4888,65 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap", + "indexmap 2.1.0", "toml_datetime", "winnow", ] +[[package]] +name = "tonic" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" +dependencies = [ + "async-trait", + "axum", + "base64 0.21.5", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost 0.11.9", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand 0.8.5", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.2" @@ -4696,6 +4986,17 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-log" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + [[package]] name = "tracing-log" version = "0.2.0" @@ -4707,6 +5008,22 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-opentelemetry" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75327c6b667828ddc28f5e3f169036cb793c3f588d83bf0f262a7f062ffed3c8" +dependencies = [ + "once_cell", + "opentelemetry", + "opentelemetry_sdk", + "smallvec", + "tracing", + "tracing-core", + "tracing-log 0.1.4", + "tracing-subscriber", +] + [[package]] name = "tracing-serde" version = "0.1.3" @@ -4735,7 +5052,7 @@ dependencies = [ "time", "tracing", "tracing-core", - "tracing-log", + "tracing-log 0.2.0", "tracing-serde", ] @@ -4842,6 +5159,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" version = "0.9.0" @@ -4909,7 +5232,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vise" version = "0.1.0" -source = "git+https://github.com/matter-labs/vise.git?rev=1c9cc500e92cf9ea052b230e114a6f9cce4fb2c1#1c9cc500e92cf9ea052b230e114a6f9cce4fb2c1" +source = "git+https://github.com/matter-labs/vise.git?rev=a5bb80c9ce7168663114ee30e794d6dc32159ee4#a5bb80c9ce7168663114ee30e794d6dc32159ee4" dependencies = [ "compile-fmt", "elsa", @@ -4922,7 +5245,7 @@ dependencies = [ [[package]] name = "vise-macros" version = "0.1.0" -source = "git+https://github.com/matter-labs/vise.git?rev=1c9cc500e92cf9ea052b230e114a6f9cce4fb2c1#1c9cc500e92cf9ea052b230e114a6f9cce4fb2c1" +source = "git+https://github.com/matter-labs/vise.git?rev=a5bb80c9ce7168663114ee30e794d6dc32159ee4#a5bb80c9ce7168663114ee30e794d6dc32159ee4" dependencies = [ "proc-macro2", "quote", @@ -4932,12 +5255,16 @@ dependencies = [ [[package]] name = "vlog" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?rev=ce5743679f18d737b459635c4d0d7b07dbf6ee2d#ce5743679f18d737b459635c4d0d7b07dbf6ee2d" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=c4d1c49282a3710be5d491636ba26207efb1f9ce#c4d1c49282a3710be5d491636ba26207efb1f9ce" dependencies = [ "chrono", + "opentelemetry", + "opentelemetry-otlp", + "opentelemetry-semantic-conventions", "sentry", "serde_json", "tracing", + "tracing-opentelemetry", "tracing-subscriber", ] @@ -5051,7 +5378,7 @@ dependencies = [ "arrayvec 0.7.4", "base64 0.21.5", "bytes", - "derive_more", + "derive_more 0.99.17", "ethabi", "ethereum-types", "futures", @@ -5069,7 +5396,24 @@ dependencies = [ "secp256k1", "serde", "serde_json", + "soketto", "tiny-keccak 2.0.2", + "tokio", + "tokio-stream", + "tokio-util", + "url", + "web3-async-native-tls", +] + +[[package]] +name = "web3-async-native-tls" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f6d8d1636b2627fe63518d5a9b38a569405d9c9bc665c43c9c341de57227ebb" +dependencies = [ + "native-tls", + "thiserror", + "tokio", "url", ] @@ -5417,7 +5761,7 @@ dependencies = [ [[package]] name = "zk_evm" version = "1.5.0" -source = "git+https://github.com/matter-labs/era-zk_evm.git?branch=v1.5.0#912fbab463f6f010f824eb66844fe8c8ff240765" +source = "git+https://github.com/matter-labs/era-zk_evm.git?branch=v1.5.0#0448e26ffd8e2f1935ff8cd3303fe5a504cd5d7b" dependencies = [ "anyhow", "lazy_static", @@ -5434,7 +5778,7 @@ version = "0.1.0" source = "git+https://github.com/matter-labs/era-zk_evm_abstractions.git#32dd320953841aa78579d9da08abbc70bcaed175" dependencies = [ "anyhow", - "num_enum", + "num_enum 0.6.1", "serde", "static_assertions", "zkevm_opcode_defs 1.3.2", @@ -5446,7 +5790,7 @@ version = "1.4.1" source = "git+https://github.com/matter-labs/era-zk_evm_abstractions.git?branch=v1.4.1#0aac08c3b097ee8147e748475117ac46bddcdcef" dependencies = [ "anyhow", - "num_enum", + "num_enum 0.6.1", "serde", "static_assertions", "zkevm_opcode_defs 1.4.1", @@ -5455,10 +5799,10 @@ dependencies = [ [[package]] name = "zk_evm_abstractions" version = "1.5.0" -source = "git+https://github.com/matter-labs/era-zk_evm_abstractions.git?branch=v1.5.0#3e4253e2cb86cc0b51f230643571d9b387bca0c6" +source = "git+https://github.com/matter-labs/era-zk_evm_abstractions.git?branch=v1.5.0#e464b2cf2b146d883be80e7d690c752bf670ff05" dependencies = [ "anyhow", - "num_enum", + "num_enum 0.6.1", "serde", "static_assertions", "zkevm_opcode_defs 1.5.0", @@ -5488,7 +5832,7 @@ dependencies = [ [[package]] name = "zkevm_circuits" version = "1.4.1" -source = "git+https://github.com/matter-labs/era-zkevm_circuits.git?branch=v1.4.1#3a973afb3cf2b50b7138c1af61cc6ac3d7d0189f" +source = "git+https://github.com/matter-labs/era-zkevm_circuits.git?branch=v1.4.1#8bf24543ffc5bafab34182388394e887ecb37d17" dependencies = [ "arrayvec 0.7.4", "bincode", @@ -5506,6 +5850,25 @@ dependencies = [ "zkevm_opcode_defs 1.4.1", ] +[[package]] +name = "zkevm_circuits" +version = "1.5.0" +source = "git+https://github.com/matter-labs/era-zkevm_circuits.git?branch=v1.5.0#861f81029bf3a916dae55afa5bd7f82b2eaca98b" +dependencies = [ + "arrayvec 0.7.4", + "boojum", + "cs_derive", + "derivative", + "hex", + "itertools 0.10.5", + "rand 0.4.6", + "rand 0.8.5", + "seq-macro", + "serde", + "smallvec", + "zkevm_opcode_defs 1.5.0", +] + [[package]] name = "zkevm_opcode_defs" version = "1.3.1" @@ -5548,7 +5911,7 @@ dependencies = [ [[package]] name = "zkevm_opcode_defs" version = "1.5.0" -source = "git+https://github.com/matter-labs/era-zkevm_opcode_defs.git?branch=v1.5.0#7bf8016f5bb13a73289f321ad6ea8f614540ece9" +source = "git+https://github.com/matter-labs/era-zkevm_opcode_defs.git?branch=v1.5.0#109d9f734804a8b9dc0531c0b576e2a0f55a40de" dependencies = [ "bitflags 2.4.1", "blake2 0.10.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -5561,49 +5924,23 @@ dependencies = [ "sha3 0.10.8", ] -[[package]] -name = "zkevm_test_harness" -version = "1.4.2" -source = "git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.4.2#d9da285c2e8a5fedddd2e9c2a893769ebd12a6ed" -dependencies = [ - "bincode", - "circuit_definitions 0.1.0 (git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.4.2)", - "codegen", - "crossbeam 0.8.2", - "curl", - "derivative", - "env_logger 0.9.3", - "hex", - "lazy_static", - "rand 0.4.6", - "rayon", - "reqwest", - "rescue_poseidon 0.4.1 (git+https://github.com/matter-labs/rescue-poseidon.git?branch=poseidon2)", - "serde", - "serde_json", - "smallvec", - "snark_wrapper", - "structopt", - "test-log", - "tracing", - "walkdir", - "zkevm-assembly 1.4.1", -] - [[package]] name = "zksync_basic_types" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?rev=ce5743679f18d737b459635c4d0d7b07dbf6ee2d#ce5743679f18d737b459635c4d0d7b07dbf6ee2d" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=c4d1c49282a3710be5d491636ba26207efb1f9ce#c4d1c49282a3710be5d491636ba26207efb1f9ce" dependencies = [ + "chrono", + "num_enum 0.7.2", "serde", "serde_json", + "strum", "web3", ] [[package]] name = "zksync_concurrency" version = "0.1.0" -source = "git+https://github.com/matter-labs/era-consensus.git?rev=5329a809cfc06d4939fb5ece26c9ad1e1741c50a#5329a809cfc06d4939fb5ece26c9ad1e1741c50a" +source = "git+https://github.com/matter-labs/era-consensus.git?rev=1dcda70c1c25d0e4db6781ba8d2645d7e8966d49#1dcda70c1c25d0e4db6781ba8d2645d7e8966d49" dependencies = [ "anyhow", "once_cell", @@ -5621,25 +5958,27 @@ dependencies = [ [[package]] name = "zksync_config" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?rev=ce5743679f18d737b459635c4d0d7b07dbf6ee2d#ce5743679f18d737b459635c4d0d7b07dbf6ee2d" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=c4d1c49282a3710be5d491636ba26207efb1f9ce#c4d1c49282a3710be5d491636ba26207efb1f9ce" dependencies = [ "anyhow", "rand 0.8.5", "serde", "zksync_basic_types", + "zksync_consensus_utils", + "zksync_crypto_primitives", ] [[package]] name = "zksync_consensus_crypto" version = "0.1.0" -source = "git+https://github.com/matter-labs/era-consensus.git?rev=5329a809cfc06d4939fb5ece26c9ad1e1741c50a#5329a809cfc06d4939fb5ece26c9ad1e1741c50a" +source = "git+https://github.com/matter-labs/era-consensus.git?rev=1dcda70c1c25d0e4db6781ba8d2645d7e8966d49#1dcda70c1c25d0e4db6781ba8d2645d7e8966d49" dependencies = [ "anyhow", "blst", "ed25519-dalek", "ff_ce", "hex", - "pairing_ce 0.28.5 (git+https://github.com/matter-labs/pairing.git?rev=f55393fd366596eac792d78525d26e9c4d6ed1ca)", + "pairing_ce 0.28.5 (git+https://github.com/matter-labs/pairing.git?rev=d24f2c5871089c4cd4f54c0ca266bb9fef6115eb)", "rand 0.4.6", "rand 0.8.5", "sha3 0.10.8", @@ -5650,12 +5989,12 @@ dependencies = [ [[package]] name = "zksync_consensus_roles" version = "0.1.0" -source = "git+https://github.com/matter-labs/era-consensus.git?rev=5329a809cfc06d4939fb5ece26c9ad1e1741c50a#5329a809cfc06d4939fb5ece26c9ad1e1741c50a" +source = "git+https://github.com/matter-labs/era-consensus.git?rev=1dcda70c1c25d0e4db6781ba8d2645d7e8966d49#1dcda70c1c25d0e4db6781ba8d2645d7e8966d49" dependencies = [ "anyhow", "bit-vec", "hex", - "prost", + "prost 0.12.3", "rand 0.8.5", "serde", "thiserror", @@ -5670,11 +6009,11 @@ dependencies = [ [[package]] name = "zksync_consensus_storage" version = "0.1.0" -source = "git+https://github.com/matter-labs/era-consensus.git?rev=5329a809cfc06d4939fb5ece26c9ad1e1741c50a#5329a809cfc06d4939fb5ece26c9ad1e1741c50a" +source = "git+https://github.com/matter-labs/era-consensus.git?rev=1dcda70c1c25d0e4db6781ba8d2645d7e8966d49#1dcda70c1c25d0e4db6781ba8d2645d7e8966d49" dependencies = [ "anyhow", "async-trait", - "prost", + "prost 0.12.3", "rand 0.8.5", "thiserror", "tracing", @@ -5688,8 +6027,9 @@ dependencies = [ [[package]] name = "zksync_consensus_utils" version = "0.1.0" -source = "git+https://github.com/matter-labs/era-consensus.git?rev=5329a809cfc06d4939fb5ece26c9ad1e1741c50a#5329a809cfc06d4939fb5ece26c9ad1e1741c50a" +source = "git+https://github.com/matter-labs/era-consensus.git?rev=1dcda70c1c25d0e4db6781ba8d2645d7e8966d49#1dcda70c1c25d0e4db6781ba8d2645d7e8966d49" dependencies = [ + "rand 0.8.5", "thiserror", "zksync_concurrency", ] @@ -5697,7 +6037,7 @@ dependencies = [ [[package]] name = "zksync_contracts" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?rev=ce5743679f18d737b459635c4d0d7b07dbf6ee2d#ce5743679f18d737b459635c4d0d7b07dbf6ee2d" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=c4d1c49282a3710be5d491636ba26207efb1f9ce#c4d1c49282a3710be5d491636ba26207efb1f9ce" dependencies = [ "envy", "ethabi", @@ -5711,21 +6051,37 @@ dependencies = [ [[package]] name = "zksync_crypto" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?rev=ce5743679f18d737b459635c4d0d7b07dbf6ee2d#ce5743679f18d737b459635c4d0d7b07dbf6ee2d" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=c4d1c49282a3710be5d491636ba26207efb1f9ce#c4d1c49282a3710be5d491636ba26207efb1f9ce" dependencies = [ "blake2 0.10.6 (registry+https://github.com/rust-lang/crates.io-index)", "hex", "once_cell", "serde", - "sha2 0.9.9", + "sha2 0.10.8", + "thiserror", + "zksync_basic_types", +] + +[[package]] +name = "zksync_crypto_primitives" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=c4d1c49282a3710be5d491636ba26207efb1f9ce#c4d1c49282a3710be5d491636ba26207efb1f9ce" +dependencies = [ + "anyhow", + "hex", + "secp256k1", + "serde", + "serde_json", "thiserror", + "web3", "zksync_basic_types", + "zksync_utils", ] [[package]] name = "zksync_dal" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?rev=ce5743679f18d737b459635c4d0d7b07dbf6ee2d#ce5743679f18d737b459635c4d0d7b07dbf6ee2d" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=c4d1c49282a3710be5d491636ba26207efb1f9ce#c4d1c49282a3710be5d491636ba26207efb1f9ce" dependencies = [ "anyhow", "bigdecimal", @@ -5733,8 +6089,7 @@ dependencies = [ "chrono", "hex", "itertools 0.10.5", - "once_cell", - "prost", + "prost 0.12.3", "rand 0.8.5", "serde", "serde_json", @@ -5743,12 +6098,11 @@ dependencies = [ "thiserror", "tokio", "tracing", - "url", "vise", "zksync_consensus_roles", "zksync_consensus_storage", "zksync_contracts", - "zksync_health_check", + "zksync_db_connection", "zksync_protobuf", "zksync_protobuf_build", "zksync_system_constants", @@ -5756,15 +6110,34 @@ dependencies = [ "zksync_utils", ] +[[package]] +name = "zksync_db_connection" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=c4d1c49282a3710be5d491636ba26207efb1f9ce#c4d1c49282a3710be5d491636ba26207efb1f9ce" +dependencies = [ + "anyhow", + "rand 0.8.5", + "serde", + "serde_json", + "sqlx", + "thiserror", + "tokio", + "tracing", + "url", + "vise", + "zksync_health_check", +] + [[package]] name = "zksync_health_check" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?rev=ce5743679f18d737b459635c4d0d7b07dbf6ee2d#ce5743679f18d737b459635c4d0d7b07dbf6ee2d" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=c4d1c49282a3710be5d491636ba26207efb1f9ce#c4d1c49282a3710be5d491636ba26207efb1f9ce" dependencies = [ "async-trait", "futures", "serde", "serde_json", + "thiserror", "tokio", "tracing", "vise", @@ -5773,7 +6146,7 @@ dependencies = [ [[package]] name = "zksync_mini_merkle_tree" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?rev=ce5743679f18d737b459635c4d0d7b07dbf6ee2d#ce5743679f18d737b459635c4d0d7b07dbf6ee2d" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=c4d1c49282a3710be5d491636ba26207efb1f9ce#c4d1c49282a3710be5d491636ba26207efb1f9ce" dependencies = [ "once_cell", "zksync_basic_types", @@ -5783,28 +6156,30 @@ dependencies = [ [[package]] name = "zksync_protobuf" version = "0.1.0" -source = "git+https://github.com/matter-labs/era-consensus.git?rev=5329a809cfc06d4939fb5ece26c9ad1e1741c50a#5329a809cfc06d4939fb5ece26c9ad1e1741c50a" +source = "git+https://github.com/matter-labs/era-consensus.git?rev=1dcda70c1c25d0e4db6781ba8d2645d7e8966d49#1dcda70c1c25d0e4db6781ba8d2645d7e8966d49" dependencies = [ "anyhow", "bit-vec", "once_cell", - "prost", + "prost 0.12.3", "prost-reflect", "quick-protobuf", "rand 0.8.5", "serde", "serde_json", + "serde_yaml", "zksync_concurrency", + "zksync_consensus_utils", "zksync_protobuf_build", ] [[package]] name = "zksync_protobuf_build" version = "0.1.0" -source = "git+https://github.com/matter-labs/era-consensus.git?rev=5329a809cfc06d4939fb5ece26c9ad1e1741c50a#5329a809cfc06d4939fb5ece26c9ad1e1741c50a" +source = "git+https://github.com/matter-labs/era-consensus.git?rev=1dcda70c1c25d0e4db6781ba8d2645d7e8966d49#1dcda70c1c25d0e4db6781ba8d2645d7e8966d49" dependencies = [ "anyhow", - "heck", + "heck 0.5.0", "prettyplease", "proc-macro2", "prost-build", @@ -5814,18 +6189,32 @@ dependencies = [ "syn 2.0.40", ] +[[package]] +name = "zksync_shared_metrics" +version = "0.1.0" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=c4d1c49282a3710be5d491636ba26207efb1f9ce#c4d1c49282a3710be5d491636ba26207efb1f9ce" +dependencies = [ + "vise", + "zksync_dal", + "zksync_types", +] + [[package]] name = "zksync_state" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?rev=ce5743679f18d737b459635c4d0d7b07dbf6ee2d#ce5743679f18d737b459635c4d0d7b07dbf6ee2d" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=c4d1c49282a3710be5d491636ba26207efb1f9ce#c4d1c49282a3710be5d491636ba26207efb1f9ce" dependencies = [ "anyhow", + "async-trait", + "chrono", "itertools 0.10.5", "mini-moka", + "once_cell", "tokio", "tracing", "vise", "zksync_dal", + "zksync_shared_metrics", "zksync_storage", "zksync_types", "zksync_utils", @@ -5834,11 +6223,12 @@ dependencies = [ [[package]] name = "zksync_storage" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?rev=ce5743679f18d737b459635c4d0d7b07dbf6ee2d#ce5743679f18d737b459635c4d0d7b07dbf6ee2d" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=c4d1c49282a3710be5d491636ba26207efb1f9ce#c4d1c49282a3710be5d491636ba26207efb1f9ce" dependencies = [ "num_cpus", "once_cell", "rocksdb", + "thread_local", "tracing", "vise", ] @@ -5846,7 +6236,7 @@ dependencies = [ [[package]] name = "zksync_system_constants" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?rev=ce5743679f18d737b459635c4d0d7b07dbf6ee2d#ce5743679f18d737b459635c4d0d7b07dbf6ee2d" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=c4d1c49282a3710be5d491636ba26207efb1f9ce#c4d1c49282a3710be5d491636ba26207efb1f9ce" dependencies = [ "once_cell", "zksync_basic_types", @@ -5856,27 +6246,28 @@ dependencies = [ [[package]] name = "zksync_types" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?rev=ce5743679f18d737b459635c4d0d7b07dbf6ee2d#ce5743679f18d737b459635c4d0d7b07dbf6ee2d" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=c4d1c49282a3710be5d491636ba26207efb1f9ce#c4d1c49282a3710be5d491636ba26207efb1f9ce" dependencies = [ "anyhow", "blake2 0.10.6 (registry+https://github.com/rust-lang/crates.io-index)", "chrono", + "derive_more 1.0.0-beta.6", "hex", "itertools 0.10.5", "num", - "num_enum", + "num_enum 0.7.2", "once_cell", - "prost", + "prost 0.12.3", "rlp", "secp256k1", "serde", "serde_json", - "serde_with", "strum", "thiserror", "zksync_basic_types", "zksync_config", "zksync_contracts", + "zksync_crypto_primitives", "zksync_mini_merkle_tree", "zksync_protobuf", "zksync_protobuf_build", @@ -5887,14 +6278,13 @@ dependencies = [ [[package]] name = "zksync_utils" version = "0.1.0" -source = "git+https://github.com/matter-labs/zksync-era.git?rev=ce5743679f18d737b459635c4d0d7b07dbf6ee2d#ce5743679f18d737b459635c4d0d7b07dbf6ee2d" +source = "git+https://github.com/matter-labs/zksync-era.git?rev=c4d1c49282a3710be5d491636ba26207efb1f9ce#c4d1c49282a3710be5d491636ba26207efb1f9ce" dependencies = [ "anyhow", "bigdecimal", "futures", "hex", "itertools 0.10.5", - "metrics", "num", "reqwest", "serde", @@ -5905,3 +6295,13 @@ dependencies = [ "zk_evm 1.3.3 (git+https://github.com/matter-labs/era-zk_evm.git?tag=v1.3.3-rc2)", "zksync_basic_types", ] + +[[package]] +name = "zstd-sys" +version = "2.0.10+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/system-contracts/bootloader/test_infra/Cargo.toml b/system-contracts/bootloader/test_infra/Cargo.toml index df3756e5d..cfcd2c9a3 100644 --- a/system-contracts/bootloader/test_infra/Cargo.toml +++ b/system-contracts/bootloader/test_infra/Cargo.toml @@ -7,12 +7,12 @@ edition = "2021" [dependencies] -multivm = { git = "https://github.com/matter-labs/zksync-era.git", rev = "ce5743679f18d737b459635c4d0d7b07dbf6ee2d" } -zksync_types = { git = "https://github.com/matter-labs/zksync-era.git", rev = "ce5743679f18d737b459635c4d0d7b07dbf6ee2d" } -zksync_contracts = { git = "https://github.com/matter-labs/zksync-era.git", rev = "ce5743679f18d737b459635c4d0d7b07dbf6ee2d" } -zksync_utils = { git = "https://github.com/matter-labs/zksync-era.git", rev = "ce5743679f18d737b459635c4d0d7b07dbf6ee2d" } -zksync_state = { git = "https://github.com/matter-labs/zksync-era.git", rev = "ce5743679f18d737b459635c4d0d7b07dbf6ee2d" } -vlog = { git = "https://github.com/matter-labs/zksync-era.git", rev = "ce5743679f18d737b459635c4d0d7b07dbf6ee2d" } +multivm = { git = "https://github.com/matter-labs/zksync-era.git", rev = "c4d1c49282a3710be5d491636ba26207efb1f9ce" } +zksync_types = { git = "https://github.com/matter-labs/zksync-era.git", rev = "c4d1c49282a3710be5d491636ba26207efb1f9ce" } +zksync_contracts = { git = "https://github.com/matter-labs/zksync-era.git", rev = "c4d1c49282a3710be5d491636ba26207efb1f9ce" } +zksync_utils = { git = "https://github.com/matter-labs/zksync-era.git", rev = "c4d1c49282a3710be5d491636ba26207efb1f9ce" } +zksync_state = { git = "https://github.com/matter-labs/zksync-era.git", rev = "c4d1c49282a3710be5d491636ba26207efb1f9ce" } +vlog = { git = "https://github.com/matter-labs/zksync-era.git", rev = "c4d1c49282a3710be5d491636ba26207efb1f9ce" } colored = "2.0" hex = "0.4" diff --git a/system-contracts/bootloader/test_infra/src/hook.rs b/system-contracts/bootloader/test_infra/src/hook.rs index ca30ae03b..04bacc90c 100644 --- a/system-contracts/bootloader/test_infra/src/hook.rs +++ b/system-contracts/bootloader/test_infra/src/hook.rs @@ -1,5 +1,5 @@ use multivm::vm_latest::{ - constants::{BOOTLOADER_HEAP_PAGE, VM_HOOK_PARAMS_START_POSITION}, + constants::{BOOTLOADER_HEAP_PAGE, get_vm_hook_start_position_latest}, HistoryMode, SimpleMemory, }; @@ -26,7 +26,7 @@ pub(crate) enum TestVmHook { // Number of 32-bytes slots that are reserved for test hooks (passing information between bootloader test code and the VM). const TEST_HOOKS: u32 = 5; -const TEST_HOOK_ENUM_POSITION: u32 = VM_HOOK_PARAMS_START_POSITION - 1; +const TEST_HOOK_ENUM_POSITION: u32 = get_vm_hook_start_position_latest() - 1; const TEST_HOOK_START: u32 = TEST_HOOK_ENUM_POSITION - TEST_HOOKS; pub fn get_vm_hook_params(memory: &SimpleMemory) -> Vec { diff --git a/system-contracts/bootloader/test_infra/src/main.rs b/system-contracts/bootloader/test_infra/src/main.rs index 33dae7ddd..3928200e4 100644 --- a/system-contracts/bootloader/test_infra/src/main.rs +++ b/system-contracts/bootloader/test_infra/src/main.rs @@ -21,7 +21,7 @@ use zksync_state::{ InMemoryStorage, StoragePtr, StorageView, IN_MEMORY_STORAGE_DEFAULT_NETWORK_ID, }; use zksync_types::system_contracts::get_system_smart_contracts_from_dir; -use zksync_types::{block::MiniblockHasher, Address, L1BatchNumber, MiniblockNumber, U256}; +use zksync_types::{block::L2BlockHasher, Address, L1BatchNumber, L2BlockNumber, U256}; use zksync_types::{L2ChainId, Transaction}; use zksync_utils::bytecode::hash_bytecode; use zksync_utils::{bytes_to_be_words, u256_to_h256}; @@ -63,7 +63,7 @@ fn execute_internal_bootloader_test() { zk_porter_available: false, version: zksync_types::ProtocolVersionId::latest(), base_system_smart_contracts: base_system_contract, - gas_limit: u32::MAX, + bootloader_gas_limit: u32::MAX, execution_mode: TxExecutionMode::VerifyExecute, default_validation_computational_gas_limit: u32::MAX, chain_id: zksync_types::L2ChainId::from(299), @@ -80,7 +80,7 @@ fn execute_internal_bootloader_test() { first_l2_block: L2BlockEnv { number: 1, timestamp: 15, - prev_block_hash: MiniblockHasher::legacy_hash(MiniblockNumber(0)), + prev_block_hash: L2BlockHasher::legacy_hash(L2BlockNumber(0)), max_virtual_blocks_to_create: 1, }, }; diff --git a/system-contracts/contracts/test-contracts/GasBoundCallerTester.sol b/system-contracts/contracts/test-contracts/GasBoundCallerTester.sol deleted file mode 100644 index 8a86c2e39..000000000 --- a/system-contracts/contracts/test-contracts/GasBoundCallerTester.sol +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -import {GasBoundCaller} from "../GasBoundCaller.sol"; -import {SystemContractHelper} from "../libraries/SystemContractHelper.sol"; - -/** - * @author Matter Labs - * @custom:security-contact security@matterlabs.dev - * @notice The contract that allows to limit the final gas expenditure of the call. - */ -contract GasBoundCallerTester is GasBoundCaller { - uint256 public lastRecordedGasLeft; - - function testEntryOverheadInner(uint256 _expectedGas) external payable { - // `2/3` to ensure that the constant is good with sufficient overhead - require(gasleft() + (2 * CALL_ENTRY_OVERHEAD) / 3 >= _expectedGas, "Entry overhead is incorrect"); - - lastRecordedGasLeft = gasleft(); - } - - function testEntryOverhead(uint256 _expectedGas) external payable { - this.testEntryOverheadInner{gas: _expectedGas}(_expectedGas); - } - - function testReturndataOverheadInner(bool _shouldReturn, uint256 _len) external { - if (_shouldReturn) { - assembly { - return(0, _len) - } - } else { - (bool success, bytes memory returnData) = address(this).call( - abi.encodeWithSignature("testReturndataOverheadInner(bool,uint256)", true, _len) - ); - require(success, "Call failed"); - - // `2/3` to ensure that the constant is good with sufficient overhead - SystemContractHelper.burnGas(uint32(gasleft() - (2 * CALL_RETURN_OVERHEAD) / 3), 0); - assembly { - // We just relay the return data from the call. - return(add(returnData, 0x20), mload(returnData)) - } - } - } - - function testReturndataOverhead(uint256 len) external { - uint256 gasbefore = gasleft(); - this.testReturndataOverheadInner(false, len); - lastRecordedGasLeft = gasbefore - gasleft(); - } - - function spender(uint32 _gasToBurn, uint32 _pubdataToUse) external { - SystemContractHelper.burnGas(_gasToBurn, _pubdataToUse); - } - - function gasBoundCallRelayer( - uint256 _gasToPass, - address _to, - uint256 _maxTotalGas, - bytes calldata _data - ) external payable { - this.gasBoundCall{gas: _gasToPass}(_to, _maxTotalGas, _data); - } -} diff --git a/system-contracts/hardhat.config.ts b/system-contracts/hardhat.config.ts index cc0a5e0b4..2adf0c440 100644 --- a/system-contracts/hardhat.config.ts +++ b/system-contracts/hardhat.config.ts @@ -55,6 +55,14 @@ export default { ethNetwork: "localhost", zksync: true, }, + stage: { + url: "https://z2-dev-api.zksync.dev/", + ethNetwork: "sepolia", + zksync: true, + }, + sepolia: { + url: "", // add your sepolia node url + }, }, paths: { sources: "./contracts-preprocessed", diff --git a/system-contracts/scripts/compile-yul.ts b/system-contracts/scripts/compile-yul.ts index 6c75cf3ef..67b468987 100644 --- a/system-contracts/scripts/compile-yul.ts +++ b/system-contracts/scripts/compile-yul.ts @@ -31,9 +31,11 @@ export async function compileYul(paths: CompilerPaths, file: string) { export async function compileYulFolder(path: string) { const paths = prepareCompilerPaths(path); const files: string[] = (await fs.promises.readdir(path)).filter((fn) => fn.endsWith(".yul")); + const promises: Promise[] = []; for (const file of files) { - await compileYul(paths, `${file}`); + promises.push(compileYul(paths, `${file}`)); } + await Promise.all(promises); } async function main() { diff --git a/system-contracts/scripts/deploy-preimages.ts b/system-contracts/scripts/deploy-preimages.ts index 035b98e88..6803f9a53 100644 --- a/system-contracts/scripts/deploy-preimages.ts +++ b/system-contracts/scripts/deploy-preimages.ts @@ -19,7 +19,7 @@ const testConfigPath = path.join(process.env.ZKSYNC_HOME as string, "etc/test_co const ethTestConfig = JSON.parse(fs.readFileSync(`${testConfigPath}/eth.json`, { encoding: "utf-8" })); // Maximum length of the combined length of dependencies -const MAX_COMBINED_LENGTH = 90000; +const MAX_COMBINED_LENGTH = 125000; const DEFAULT_ACCOUNT_CONTRACT_NAME = "DefaultAccount"; const BOOTLOADER_CONTRACT_NAME = "Bootloader"; diff --git a/tools/data/scheduler_key.json b/tools/data/scheduler_key.json index ab950921b..acb7e3fe8 100644 --- a/tools/data/scheduler_key.json +++ b/tools/data/scheduler_key.json @@ -6,16 +6,16 @@ "gate_setup_commitments": [ { "x": [ - 13368816467732756588, - 12631704582998645112, - 2875950984924293274, - 3113194142004855172 + 14543631136906534221, + 11532161447842416044, + 11114175029926010938, + 1228896787564295039 ], "y": [ - 3570060617207608740, - 2032397225743366002, - 5866786775367305329, - 1313634009443853971 + 13293602262342424489, + 8897930584356943159, + 13256028170406220369, + 3214939367598363288 ], "infinity": false }, @@ -96,16 +96,16 @@ }, { "x": [ - 13727181310656943815, - 296737807969282193, - 6059560013411781327, - 1793671435315065066 + 9586697317366528906, + 2325800863365957883, + 1243781259615311278, + 3048012003267036960 ], "y": [ - 1522594187298952706, - 6626811309287846888, - 8870954808386738181, - 171006237191164060 + 612821620743617231, + 1510385666449513894, + 9368337288452385056, + 2949736812933507034 ], "infinity": false }, diff --git a/yarn.lock b/yarn.lock index 1b462d2be..0e9ffb705 100644 --- a/yarn.lock +++ b/yarn.lock @@ -773,6 +773,11 @@ resolved "https://registry.yarnpkg.com/@matterlabs/prettier-config/-/prettier-config-1.0.3.tgz#3e2eb559c0112bbe9671895f935700dad2a15d38" integrity sha512-JW7nHREPqEtjBWz3EfxLarkmJBD8vi7Kx/1AQ6eBZnz12eHc1VkOyrc6mpR5ogTf0dOUNXFAfZut+cDe2dn4kQ== +"@matterlabs/zksync-contracts@^0.6.1": + version "0.6.1" + resolved "https://registry.yarnpkg.com/@matterlabs/zksync-contracts/-/zksync-contracts-0.6.1.tgz#39f061959d5890fd0043a2f1ae710f764b172230" + integrity sha512-+hucLw4DhGmTmQlXOTEtpboYCaOm/X2VJcWmnW4abNcOgQXEHX+mTxQrxEfPjIZT0ZE6z5FTUrOK9+RgUZwBMQ== + "@metamask/eth-sig-util@^4.0.0": version "4.0.1" resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz#3ad61f6ea9ad73ba5b19db780d40d9aae5157088"