From 22ecf905e66ffc3bb6b7a0114c7394453469fac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Pavl=C3=ADn?= Date: Tue, 16 Sep 2025 15:34:08 +0200 Subject: [PATCH] add minimal implementation of freeRegistrationAddress --- package.json | 8 +++++--- script/Deploy.s.sol | 17 ++++++++++++++--- src/Membership.sol | 23 ++++++++++++++++++----- src/WakuRlnV2.sol | 12 ++++++++++-- test/WakuRlnV2.t.sol | 4 ++-- 5 files changed, 49 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 96b2dd7..182d578 100644 --- a/package.json +++ b/package.json @@ -34,10 +34,12 @@ "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 && 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": "export RPC_URL=https://rpc.cardona.zkevm-rpc.com && ./envCheck.sh && FOUNDRY_PROFILE=cardona forge script --chain 2442 script/Deploy.s.sol:Deploy --rpc-url $RPC_URL --broadcast --verify -vv --account $ACCOUNT --legacy --sender $ETH_FROM", + "deploy:sepolia:price_calculator": "export RPC_URL=https://ethereum-sepolia-public.nodies.app && ./envCheck.sh && FOUNDRY_PROFILE=sepolia forge script --chain 11155111 script/Deploy.s.sol:DeployPriceCalculator --rpc-url $RPC_URL --broadcast --verify -vv --account $ACCOUNT --legacy --sender $ETH_FROM", + "deploy:sepolia:wakurln_impl_v2": "export RPC_URL=https://ethereum-sepolia-public.nodies.app && ./envCheck.sh && FOUNDRY_PROFILE=sepolia forge script --chain 11155111 script/Deploy.s.sol:DeployWakuRlnV2 --rpc-url $RPC_URL --broadcast --verify -vv --account $ACCOUNT --legacy --sender $ETH_FROM", + "deploy:sepolia:proxy": "export RPC_URL=https://ethereum-sepolia-public.nodies.app && ./envCheck.sh && FOUNDRY_PROFILE=linea_sepolia forge script --chain 11155111 script/Deploy.s.sol:DeployProxy --rpc-url $RPC_URL --broadcast --verify -vv --account $ACCOUNT --legacy --sender $ETH_FROM", + "deploy:sepolia": "npm run deploy:sepolia:price_calculator && npm run deploy:sepolia:wakurln_impl_v2 && npm run deploy:sepolia:proxy", "deploy:localhost:price_calculator": "./envCheck.sh && forge script script/Deploy.s.sol:DeployPriceCalculator --rpc-url $RPC_URL --broadcast -vv --sender $ETH_FROM --account $ACCOUNT", - "deploy:localhost:wakurln_impl_v2": "./envCheck.sh && forge script script/Deploy.s.sol:DeployWakuRlnV2 --rpc-url $RPC_URL --broadcast -vv --sender $ETH_FROM --account $ACCOUNT", + "deploy:localhost:wakurln_impl_v2": "./envCheck.sh && forge script script/Deploy.s.sol:DeployWakuRlnV2 --rpc-url $RPC_URL --broadcast -vv --sender $ETH_FROM --account $ACCOUNT", "deploy:localhost:proxy": "./envCheck.sh && forge script script/Deploy.s.sol:DeployProxy --rpc-url $RPC_URL --broadcast -vv --sender $ETH_FROM --account $ACCOUNT", "deploy:localhost": "npm run deploy:localhost:price_calculator && npm run deploy:localhost:wakurln_impl_v2 && npm run deploy:localhost:proxy", "deploy:linea_sepolia:price_calculator": "export RPC_URL=https://rpc.sepolia.linea.build && ./envCheck.sh && FOUNDRY_PROFILE=linea_sepolia forge script --chain 59141 script/Deploy.s.sol:DeployPriceCalculator --rpc-url $RPC_URL --broadcast --verify -vv --account $ACCOUNT --legacy --sender $ETH_FROM", diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 1da8735..d8a5ebe 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -55,6 +55,7 @@ contract DeployProxy is BaseScript { function run() public broadcast returns (address) { address priceCalcAddr; address wakuRlnV2ImplAddr; + address freeRegistrationAddress; try vm.envAddress("PRICE_CALCULATOR_ADDRESS") returns (address envPriceCalcAddress) { console.log("Loading price calculator address from environment variable"); @@ -72,13 +73,22 @@ contract DeployProxy is BaseScript { wakuRlnV2ImplAddr = DevOpsTools.get_most_recent_deployment("WakuRlnV2", block.chainid); } + try vm.envAddress("FREE_REGISTRATION_ADDRESS") returns (address envFreeRegistrationAddress) { + console.log("Loading free registration address from environment variable"); + freeRegistrationAddress = envFreeRegistrationAddress; + } catch { + console.log("Loading free registration address from broadcast directory"); + freeRegistrationAddress = DevOpsTools.get_most_recent_deployment("FreeRegistration", block.chainid); + } + + console.log("Using free registration address: %s", freeRegistrationAddress); console.log("Using price calculator address: %s", priceCalcAddr); console.log("Using WakuRLNV2 address: %s", wakuRlnV2ImplAddr); - return address(deploy(priceCalcAddr, wakuRlnV2ImplAddr)); + return address(deploy(priceCalcAddr, wakuRlnV2ImplAddr, freeRegistrationAddress)); } - function deploy(address _priceCalcAddr, address _wakuRlnV2ImplAddr) public returns (ERC1967Proxy) { + function deploy(address _priceCalcAddr, address _wakuRlnV2ImplAddr, address _freeRegistrationAddress) public returns (ERC1967Proxy) { bytes memory data = abi.encodeCall( WakuRlnV2.initialize, ( @@ -87,7 +97,8 @@ contract DeployProxy is BaseScript { MIN_RATELIMIT_PER_MEMBERSHIP, MAX_RATELIMIT_PER_MEMBERSHIP, ACTIVE_DURATION, - GRACE_PERIOD_DURATION + GRACE_PERIOD_DURATION, + _freeRegistrationAddress ) ); return new ERC1967Proxy(_wakuRlnV2ImplAddr, data); diff --git a/src/Membership.sol b/src/Membership.sol index 7470f93..5b008d3 100644 --- a/src/Membership.sol +++ b/src/Membership.sol @@ -65,6 +65,9 @@ abstract contract MembershipUpgradeable is Initializable { /// @notice Indices of erased memberships that can be reused for new registrations uint32[] public indicesOfLazilyErasedMemberships; + /// @notice Address which is allowed to register for free + address public freeRegistrationAddress; + struct MembershipInfo { /// @notice the deposit amount (in tokens) to register this membership uint256 depositAmount; @@ -124,7 +127,8 @@ abstract contract MembershipUpgradeable is Initializable { uint32 _minMembershipRateLimit, uint32 _maxMembershipRateLimit, uint32 _activeDurationForNewMemberships, - uint32 _gracePeriodDurationForNewMemberships + uint32 _gracePeriodDurationForNewMemberships, + address _freeRegistrationAddress ) internal onlyInitializing @@ -135,7 +139,8 @@ abstract contract MembershipUpgradeable is Initializable { _minMembershipRateLimit, _maxMembershipRateLimit, _activeDurationForNewMemberships, - _gracePeriodDurationForNewMemberships + _gracePeriodDurationForNewMemberships, + _freeRegistrationAddress ); } @@ -145,7 +150,8 @@ abstract contract MembershipUpgradeable is Initializable { uint32 _minMembershipRateLimit, uint32 _maxMembershipRateLimit, uint32 _activeDurationForNewMemberships, - uint32 _gracePeriodDurationForNewMemberships + uint32 _gracePeriodDurationForNewMemberships, + address _freeRegistrationAddress ) internal onlyInitializing @@ -161,6 +167,7 @@ abstract contract MembershipUpgradeable is Initializable { maxMembershipRateLimit = _maxMembershipRateLimit; activeDurationForNewMemberships = _activeDurationForNewMemberships; gracePeriodDurationForNewMemberships = _gracePeriodDurationForNewMemberships; + freeRegistrationAddress = _freeRegistrationAddress; } /// @dev acquire a membership and transfer the deposit to the contract @@ -189,7 +196,11 @@ abstract contract MembershipUpgradeable is Initializable { revert CannotExceedMaxTotalRateLimit(); } - (address token, uint256 depositAmount) = priceCalculator.calculate(_rateLimit); + (address token, uint256 depositAmount) = (address(0), 0); + + if (priceCalculator != IPriceCalculator(address(0)) && _sender != address(freeRegistrationAddress)) { + (token, depositAmount) = priceCalculator.calculate(_rateLimit); + } // Possibly reuse an index of an erased membership (index, indexReused) = _getFreeIndex(); @@ -205,7 +216,9 @@ abstract contract MembershipUpgradeable is Initializable { index: index }); - IERC20(token).safeTransferFrom(_sender, address(this), depositAmount); + if (priceCalculator != IPriceCalculator(address(0)) && _sender != address(freeRegistrationAddress)) { + IERC20(token).safeTransferFrom(_sender, address(this), depositAmount); + } } /// @notice Checks if a rate limit is within the allowed bounds diff --git a/src/WakuRlnV2.sol b/src/WakuRlnV2.sol index 802bd67..a4271a4 100644 --- a/src/WakuRlnV2.sol +++ b/src/WakuRlnV2.sol @@ -74,7 +74,8 @@ contract WakuRlnV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable, Member uint32 _minMembershipRateLimit, uint32 _maxMembershipRateLimit, uint32 _activeDuration, - uint32 _gracePeriod + uint32 _gracePeriod, + address _freeRegistrationAddress ) public initializer @@ -87,7 +88,8 @@ contract WakuRlnV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable, Member _minMembershipRateLimit, _maxMembershipRateLimit, _activeDuration, - _gracePeriod + _gracePeriod, + _freeRegistrationAddress ); MAX_MEMBERSHIP_SET_SIZE = uint32(1 << MERKLE_TREE_DEPTH); @@ -305,4 +307,10 @@ contract WakuRlnV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable, Member // Note: grace period duration may be equal to zero gracePeriodDurationForNewMemberships = _gracePeriodDurationForNewMembership; } + + /// @notice Set the address that can register for free (e.g. ZKPassport Verifier wrapper address) + /// @param _freeRegistrationAddress new free registration address + function setFreeRegistrationAddress(address _freeRegistrationAddress) external onlyOwner { + freeRegistrationAddress = _freeRegistrationAddress; + } } diff --git a/test/WakuRlnV2.t.sol b/test/WakuRlnV2.t.sol index b9f9d25..744d0f6 100644 --- a/test/WakuRlnV2.t.sol +++ b/test/WakuRlnV2.t.sol @@ -32,7 +32,7 @@ contract WakuRlnV2Test is Test { IPriceCalculator priceCalculator = (new DeployPriceCalculator()).deploy(address(token)); WakuRlnV2 wakuRlnV2 = (new DeployWakuRlnV2()).deploy(); - ERC1967Proxy proxy = (new DeployProxy()).deploy(address(priceCalculator), address(wakuRlnV2)); + ERC1967Proxy proxy = (new DeployProxy()).deploy(address(priceCalculator), address(wakuRlnV2), address(0)); w = WakuRlnV2(address(proxy)); @@ -773,7 +773,7 @@ contract WakuRlnV2Test is Test { function test__Upgrade() external { address testImpl = address(new WakuRlnV2()); - bytes memory data = abi.encodeCall(WakuRlnV2.initialize, (address(0), 100, 1, 10, 10 minutes, 4 minutes)); + bytes memory data = abi.encodeCall(WakuRlnV2.initialize, (address(0), 100, 1, 10, 10 minutes, 4 minutes, address(0))); address proxy = address(new ERC1967Proxy(testImpl, data)); address newImpl = address(new WakuRlnV2());