diff --git a/.env.example b/.env.example index 98c1028..0beb78e 100644 --- a/.env.example +++ b/.env.example @@ -8,4 +8,5 @@ export API_KEY_OPTIMISTIC_ETHERSCAN="YOUR_API_KEY_OPTIMISTIC_ETHERSCAN" export API_KEY_POLYGONSCAN="YOUR_API_KEY_POLYGONSCAN" export API_KEY_SNOWTRACE="YOUR_API_KEY_SNOWTRACE" export MNEMONIC="YOUR_MNEMONIC" +export ETH_FROM="YOUR_ETH_ADDRESS" export FOUNDRY_PROFILE="default" diff --git a/.gas-snapshot b/.gas-snapshot index 0793653..c39b737 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,13 +1,14 @@ -WakuRlnV2Test:test__IdCommitmentToMetadata__DoesntExist() (gas: 8299) -WakuRlnV2Test:test__InvalidPaginationQuery__EndIndexGTIdCommitmentIndex() (gas: 13351) -WakuRlnV2Test:test__InvalidPaginationQuery__StartIndexGTEndIndex() (gas: 11184) -WakuRlnV2Test:test__InvalidRegistration__DuplicateIdCommitment() (gas: 111313) -WakuRlnV2Test:test__InvalidRegistration__FullTree() (gas: 97043) -WakuRlnV2Test:test__InvalidRegistration__InvalidIdCommitment__LargerThanField() (gas: 9926) -WakuRlnV2Test:test__InvalidRegistration__InvalidIdCommitment__Zero() (gas: 9139) -WakuRlnV2Test:test__InvalidRegistration__InvalidUserMessageLimit__LargerThanMax() (gas: 10147) -WakuRlnV2Test:test__InvalidRegistration__InvalidUserMessageLimit__Zero() (gas: 9242) -WakuRlnV2Test:test__ValidPaginationQuery(uint32) (runs: 1002, μ: 402088, ~: 136269) -WakuRlnV2Test:test__ValidPaginationQuery__OneElement() (gas: 128461) -WakuRlnV2Test:test__ValidRegistration(uint256,uint32) (runs: 1001, μ: 133518, ~: 133518) -WakuRlnV2Test:test__ValidRegistration__kats() (gas: 108906) \ No newline at end of file +WakuRlnV2Test:test__IdCommitmentToMetadata__DoesntExist() (gas: 16723) +WakuRlnV2Test:test__InvalidPaginationQuery__EndIndexGTIdCommitmentIndex() (gas: 18260) +WakuRlnV2Test:test__InvalidPaginationQuery__StartIndexGTEndIndex() (gas: 16083) +WakuRlnV2Test:test__InvalidRegistration__DuplicateIdCommitment() (gas: 99824) +WakuRlnV2Test:test__InvalidRegistration__FullTree() (gas: 14343) +WakuRlnV2Test:test__InvalidRegistration__InvalidIdCommitment__LargerThanField() (gas: 15258) +WakuRlnV2Test:test__InvalidRegistration__InvalidIdCommitment__Zero() (gas: 14046) +WakuRlnV2Test:test__InvalidRegistration__InvalidUserMessageLimit__LargerThanMax() (gas: 17639) +WakuRlnV2Test:test__InvalidRegistration__InvalidUserMessageLimit__Zero() (gas: 14126) +WakuRlnV2Test:test__Upgrade() (gas: 3668443) +WakuRlnV2Test:test__ValidPaginationQuery(uint32) (runs: 1000, μ: 446587, ~: 159861) +WakuRlnV2Test:test__ValidPaginationQuery__OneElement() (gas: 120064) +WakuRlnV2Test:test__ValidRegistration(uint256,uint32) (runs: 1000, μ: 124674, ~: 124674) +WakuRlnV2Test:test__ValidRegistration__kats() (gas: 96917) \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 53e2060..485d439 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,9 @@ branch = "v1" path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/openzeppelin-foundry-upgrades"] + path = lib/openzeppelin-foundry-upgrades + url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades +[submodule "lib/openzeppelin-contracts-upgradeable"] + path = lib/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable diff --git a/envCheck.sh b/envCheck.sh index eee36dc..f71e493 100755 --- a/envCheck.sh +++ b/envCheck.sh @@ -1,6 +1,10 @@ # This script is used to assert if require env vars are present for deployment # RPC_URL: RPC URL for the network # ACCOUNT: Accessed with `cast wallet` command +# ETH_FROM: Address to send transactions from +# we need ETH_FROM because of the following bug: +# https://github.com/foundry-rs/foundry/issues/7255 + if [ -z "$RPC_URL" ]; then echo "RPC_URL is required" @@ -11,3 +15,8 @@ if [ -z "$ACCOUNT" ]; then echo "ACCOUNT is required" exit 1 fi + +if [ -z "$ETH_FROM" ]; then + echo "ETH_FROM is required" + exit 1 +fi diff --git a/foundry.toml b/foundry.toml index fd3568d..3e2d7fc 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,7 +5,7 @@ block_timestamp = 1_680_220_800 # March 31, 2023 at 00:00 GMT bytecode_hash = "none" cbor_metadata = false - evm_version = "paris" + evm_version = "cancun" fuzz = { runs = 1_000 } gas_reports = ["*"] libs = ["lib"] @@ -13,7 +13,7 @@ optimizer_runs = 10_000 out = "out" script = "script" - solc = "0.8.19" + solc = "0.8.24" src = "src" test = "test" @@ -27,6 +27,7 @@ max_test_rejects = 128_000 [etherscan] mainnet = { key = "${API_KEY_ETHERSCAN}" } sepolia = { key = "${API_KEY_ETHERSCAN}" } + 2442 = { key = "${API_KEY_CARDONA}", url = "https://api-cardona-zkevm.polygonscan.com/api" } [fmt] bracket_spacing = true @@ -41,3 +42,10 @@ max_test_rejects = 128_000 [rpc_endpoints] localhost = "http://localhost:8545" sepolia = "https://eth-sepolia.g.alchemy.com/v2/${API_KEY_ALCHEMY}" + 2442 = "https://rpc.cardona.zkevm-rpc.com" + +[profile.sepolia] +libraries = ["node_modules/@zk-kit/imt.sol/contracts/LazyIMT.sol:LazyIMT:0x22317F732AE9f9015b0866d03319a441FB42cd7f", "node_modules/poseidon-solidity/PoseidonT3.sol:PoseidonT3:0x4CF6285AC1E3ddAD6E1E378146CbCd3A6CA3Ed60"] + +[profile.cardona] +libraries = ["node_modules/@zk-kit/imt.sol/contracts/LazyIMT.sol:LazyIMT:0x8176F5f2A49cDBcCB46487D9C839c45D0200A270", "node_modules/poseidon-solidity/PoseidonT3.sol:PoseidonT3:0x99419DF6428Bad6Fe117513129FACaD4864afdcF"] diff --git a/lib/forge-std b/lib/forge-std index 74cfb77..978ac6f 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 74cfb77e308dd188d2f58864aaf44963ae6b88b1 +Subproject commit 978ac6fadb62f5f0b723c996f64be52eddba6801 diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 0000000..0a71a5e --- /dev/null +++ b/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit 0a71a5ebfbf4136cae3176e5cc9bcc5efc23f76b diff --git a/lib/openzeppelin-foundry-upgrades b/lib/openzeppelin-foundry-upgrades new file mode 160000 index 0000000..4cd15fc --- /dev/null +++ b/lib/openzeppelin-foundry-upgrades @@ -0,0 +1 @@ +Subproject commit 4cd15fc50b141c77d8cc9ff8efb44d00e841a299 diff --git a/package.json b/package.json index 05942b1..f5a6415 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "gas-report": "forge test --gas-report 2>&1 | (tee /dev/tty | awk '/Test result:/ {found=1; buffer=\"\"; next} found && !/Ran/ {buffer=buffer $0 ORS} /Ran/ {found=0} END {printf \"%s\", buffer}' > .gas-report)", "release": "commit-and-tag-version", "adorno": "pnpm prettier:write && forge fmt && forge snapshot && pnpm gas-report", - "deploy:sepolia": "./envCheck.sh && forge script --chain sepolia script/Deploy.s.sol:Deploy --rpc-url $RPC_URL --broadcast --verify -vv --account $ACCOUNT --legacy", - "deploy:localhost": "./envCheck.sh && forge script script/Deploy.s.sol:Deploy --rpc-url $RPC_URL --broadcast -vv --account $ACCOUNT" + "deploy:sepolia": "./envCheck.sh && FOUNDRY_PROFILE=sepolia forge script --chain sepolia script/Deploy.s.sol:Deploy --rpc-url $RPC_URL --broadcast --verify -vv --account $ACCOUNT --legacy --sender $ETH_FROM", + "deploy:cardona": "./envCheck.sh && FOUNDRY_PROFILE=cardona forge script --chain 2442 script/Deploy.s.sol:Deploy --rpc-url https://rpc.cardona.zkevm-rpc.com --broadcast --verify -vv --account $ACCOUNT --legacy --sender $ETH_FROM", + "deploy:localhost": "./envCheck.sh && forge script script/Deploy.s.sol:Deploy --rpc-url $RPC_URL --broadcast -vv --account $ACCOUNT --sender $ETH_FROM" } } diff --git a/remappings.txt b/remappings.txt index e210eef..28510ac 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,3 +1,5 @@ forge-std/=lib/forge-std/src/ @zk-kit/imt.sol/=node_modules/@zk-kit/imt.sol/contracts poseidon-solidity/=node_modules/poseidon-solidity/ +@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/ +@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 0409996..3c1f37f 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -2,11 +2,33 @@ pragma solidity >=0.8.19 <=0.9.0; import { WakuRlnV2 } from "../src/WakuRlnV2.sol"; +import { PoseidonT3 } from "poseidon-solidity/PoseidonT3.sol"; +import { LazyIMT } from "@zk-kit/imt.sol/LazyIMT.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + import { BaseScript } from "./Base.s.sol"; import { DeploymentConfig } from "./DeploymentConfig.s.sol"; contract Deploy is BaseScript { - function run() public broadcast returns (WakuRlnV2 w) { - w = new WakuRlnV2(20); + function run() public broadcast returns (WakuRlnV2 w, address impl) { + impl = address(new WakuRlnV2()); + bytes memory data = abi.encodeCall(WakuRlnV2.initialize, (msg.sender, 20)); + address proxy = address(new ERC1967Proxy(impl, data)); + w = WakuRlnV2(proxy); + } +} + +contract DeployLibs is BaseScript { + function run() public broadcast returns (address poseidonT3, address lazyImt) { + bytes memory poseidonT3Bytecode = type(PoseidonT3).creationCode; + assembly { + poseidonT3 := create(0, add(poseidonT3Bytecode, 0x20), mload(poseidonT3Bytecode)) + } + + bytes memory lazyImtBytecode = type(LazyIMT).creationCode; + assembly { + lazyImt := create(0, add(lazyImtBytecode, 0x20), mload(lazyImtBytecode)) + } } } diff --git a/src/WakuRlnV2.sol b/src/WakuRlnV2.sol index 0389853..640e193 100644 --- a/src/WakuRlnV2.sol +++ b/src/WakuRlnV2.sol @@ -1,10 +1,13 @@ // SPDX-License-Identifier: MIT - -pragma solidity 0.8.19; +pragma solidity 0.8.24; import { LazyIMT, LazyIMTData } from "@zk-kit/imt.sol/LazyIMT.sol"; import { PoseidonT3 } from "poseidon-solidity/PoseidonT3.sol"; +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + /// The tree is full error FullTree(); @@ -20,22 +23,22 @@ error InvalidUserMessageLimit(uint32 messageLimit); /// Invalid pagination query error InvalidPaginationQuery(uint256 startIndex, uint256 endIndex); -contract WakuRlnV2 { +contract WakuRlnV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable { /// @notice The Field uint256 public constant Q = 21_888_242_871_839_275_222_246_405_745_257_275_088_548_364_400_416_034_343_698_204_186_575_808_495_617; /// @notice The max message limit per epoch - uint32 public immutable MAX_MESSAGE_LIMIT; + uint32 public MAX_MESSAGE_LIMIT; /// @notice The depth of the merkle tree uint8 public constant DEPTH = 20; /// @notice The size of the merkle tree, i.e 2^depth - uint32 public immutable SET_SIZE; + uint32 public SET_SIZE; /// @notice The index of the next member to be registered - uint32 public idCommitmentIndex = 0; + uint32 public idCommitmentIndex; /// @notice the membership metadata of the member struct MembershipInfo { @@ -49,7 +52,7 @@ contract WakuRlnV2 { mapping(uint256 => MembershipInfo) public memberInfo; /// @notice the deployed block number - uint32 public immutable deployedBlockNumber; + uint32 public deployedBlockNumber; /// @notice the stored imt data LazyIMTData public imtData; @@ -74,14 +77,22 @@ contract WakuRlnV2 { _; } - /// @notice the constructor of the contract - constructor(uint32 maxMessageLimit) { + constructor() { + _disableInitializers(); + } + + function initialize(address initialOwner, uint32 maxMessageLimit) public initializer { + __Ownable_init(initialOwner); + __UUPSUpgradeable_init(); MAX_MESSAGE_LIMIT = maxMessageLimit; SET_SIZE = uint32(1 << DEPTH); deployedBlockNumber = uint32(block.number); LazyIMT.init(imtData, DEPTH); + idCommitmentIndex = 0; } + function _authorizeUpgrade(address newImplementation) internal override onlyOwner { } // solhint-disable-line + /// @notice Checks if a commitment is valid /// @param idCommitment The idCommitment of the member /// @return true if the commitment is valid, false otherwise diff --git a/test/WakuRlnV2.t.sol b/test/WakuRlnV2.t.sol index 2022546..bb67431 100644 --- a/test/WakuRlnV2.t.sol +++ b/test/WakuRlnV2.t.sol @@ -2,25 +2,22 @@ pragma solidity >=0.8.19 <0.9.0; import { Test } from "forge-std/Test.sol"; -import { stdStorage, StdStorage } from "forge-std/Test.sol"; - import { Deploy } from "../script/Deploy.s.sol"; import { DeploymentConfig } from "../script/DeploymentConfig.s.sol"; -import "../src/WakuRlnV2.sol"; +import "../src/WakuRlnV2.sol"; // solhint-disable-line import { PoseidonT3 } from "poseidon-solidity/PoseidonT3.sol"; -import { LazyIMT } from "@zk-kit/imt.sol/LazyIMT.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; contract WakuRlnV2Test is Test { - using stdStorage for StdStorage; - WakuRlnV2 internal w; + address internal impl; DeploymentConfig internal deploymentConfig; address internal deployer; function setUp() public virtual { Deploy deployment = new Deploy(); - w = deployment.run(); + (w, impl) = deployment.run(); } function test__ValidRegistration__kats() external { @@ -90,7 +87,7 @@ contract WakuRlnV2Test is Test { assertEq(fetchedRateCommitment, rateCommitment); } - function test__IdCommitmentToMetadata__DoesntExist() external { + function test__IdCommitmentToMetadata__DoesntExist() external view { uint256 idCommitment = 2; (uint32 userMessageLimit, uint32 index, uint256 rateCommitment) = w.idCommitmentToMetadata(idCommitment); assertEq(userMessageLimit, 0); @@ -137,7 +134,18 @@ contract WakuRlnV2Test is Test { function test__InvalidRegistration__FullTree() external { uint32 userMessageLimit = 2; // we progress the tree to the last leaf - stdstore.target(address(w)).sig("idCommitmentIndex()").checked_write(1 << w.DEPTH()); + /*| Name | Type | Slot | Offset | Bytes | + |---------------------|-----------------------------------------------------|------|--------|-------| + | MAX_MESSAGE_LIMIT | uint32 | 0 | 0 | 4 | + | SET_SIZE | uint32 | 0 | 4 | 4 | + | idCommitmentIndex | uint32 | 0 | 8 | 4 | + | memberInfo | mapping(uint256 => struct WakuRlnV2.MembershipInfo) | 1 | 0 | 32 | + | deployedBlockNumber | uint32 | 2 | 0 | 4 | + | imtData | struct LazyIMTData | 3 | 0 | 64 |*/ + // we set MAX_MESSAGE_LIMIT to 20 (unaltered) + // we set SET_SIZE to 4294967295 (1 << 20) (unaltered) + // we set idCommitmentIndex to 4294967295 (1 << 20) (altered) + vm.store(address(w), bytes32(0), 0x0000000000000000000000000000000000000000ffffffffffffffff00000014); vm.expectRevert(FullTree.selector); w.register(1, userMessageLimit); } @@ -179,4 +187,18 @@ contract WakuRlnV2Test is Test { assertEq(commitments[i], rateCommitment); } } + + function test__Upgrade() external { + address newImplementation = address(new WakuRlnV2()); + bytes memory data; + UUPSUpgradeable(address(w)).upgradeToAndCall(newImplementation, data); + // ensure that the implementation is set correctly + // ref: + // solhint-disable-next-line + // https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades/blob/4cd15fc50b141c77d8cc9ff8efb44d00e841a299/src/internal/Core.sol#L289 + address fetchedImpl = address( + uint160(uint256(vm.load(address(w), 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc))) + ); + assertEq(fetchedImpl, newImplementation); + } }