diff --git a/src/Rln.sol b/src/Rln.sol index 5510453..31d7978 100644 --- a/src/Rln.sol +++ b/src/Rln.sol @@ -7,9 +7,10 @@ contract Rln is RlnBase { constructor( uint256 membershipDeposit, uint256 depth, + uint256 maxMessageLimit, address _verifier ) - RlnBase(membershipDeposit, depth, _verifier) + RlnBase(membershipDeposit, depth, maxMessageLimit, _verifier) { } function _validateRegistration(uint256 idCommitment) internal pure override { } diff --git a/src/RlnBase.sol b/src/RlnBase.sol index a1b8bb7..d546e33 100644 --- a/src/RlnBase.sol +++ b/src/RlnBase.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.19; import { IVerifier } from "./IVerifier.sol"; -import { PoseidonT3 } from "poseidon-solidity/PoseidonT3.sol"; /// The tree is full error FullTree(); @@ -14,7 +13,7 @@ error FullTree(); error InsufficientDeposit(uint256 required, uint256 provided); /// Member is already registered -error DuplicateRateCommitment(); +error DuplicateIdCommitment(); /// Failed validation on registration/slashing error FailedValidation(); @@ -52,7 +51,7 @@ abstract contract RlnBase { 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 - uint256 public constant MAX_MESSAGE_LIMIT = 20; + uint256 public immutable MAX_MESSAGE_LIMIT; /// @notice The deposit amount required to register as a member uint256 public immutable MEMBERSHIP_DEPOSIT; @@ -64,7 +63,7 @@ abstract contract RlnBase { uint256 public immutable SET_SIZE; /// @notice The index of the next member to be registered - uint256 public rateCommitmentIndex = 0; + uint256 public idCommitmentIndex = 0; /// @notice The amount of eth staked by each member /// maps from idCommitment to the amount staked @@ -95,8 +94,9 @@ abstract contract RlnBase { /// Emitted when a new member is added to the set /// @param idCommitment The idCommitment of the member + /// @param userMessageLimit the user message limit of the member /// @param index The index of the member in the set - event MemberRegistered(uint256 idCommitment, uint256 index); + event MemberRegistered(uint256 idCommitment, uint256 userMessageLimit, uint256 index); /// Emitted when a member is removed from the set /// @param idCommitment The idCommitment of the member @@ -114,8 +114,9 @@ abstract contract RlnBase { _; } - constructor(uint256 membershipDeposit, uint256 depth, address _verifier) { + constructor(uint256 membershipDeposit, uint256 depth, uint256 maxMessageLimit, address _verifier) { MEMBERSHIP_DEPOSIT = membershipDeposit; + MAX_MESSAGE_LIMIT = maxMessageLimit; DEPTH = depth; SET_SIZE = 1 << depth; verifier = IVerifier(_verifier); @@ -147,7 +148,7 @@ abstract contract RlnBase { revert InsufficientDeposit(MEMBERSHIP_DEPOSIT, msg.value); } _validateRegistration(idCommitment); - _register(idCommitment, msg.value); + _register(idCommitment, userMessageLimit, msg.value); } /// Registers a member @@ -155,18 +156,17 @@ abstract contract RlnBase { /// @param userMessageLimit The message limit of the member /// @param stake The amount of eth staked by the member function _register(uint256 idCommitment, uint256 userMessageLimit, uint256 stake) internal virtual { - uint256 rateCommitment = PoseidonT3.hash([idCommitment, userMessageLimit]); - if (memberExists[rateCommitment]) revert DuplicateRateCommitment(); - if (rateCommitmentIndex >= SET_SIZE) revert FullTree(); - - members[rateCommitment] = rateCommitmentIndex; - indexToCommitment[rateCommitmentIndex] = rateCommitment; - memberExists[rateCommitment] = true; - stakedAmounts[rateCommitment] = stake; + if (memberExists[idCommitment]) revert DuplicateIdCommitment(); + if (idCommitmentIndex >= SET_SIZE) revert FullTree(); + + members[idCommitment] = idCommitmentIndex; + indexToCommitment[idCommitmentIndex] = idCommitment; + memberExists[idCommitment] = true; + stakedAmounts[idCommitment] = stake; userMessageLimits[idCommitment] = userMessageLimit; - emit MemberRegistered(rateCommitment, rateCommitmentIndex); - rateCommitmentIndex += 1; + emit MemberRegistered(idCommitment, userMessageLimit, idCommitmentIndex); + idCommitmentIndex += 1; } /// @dev Inheriting contracts MUST override this function @@ -197,25 +197,24 @@ abstract contract RlnBase { } uint256 userMessageLimit = userMessageLimits[idCommitment]; - uint256 rateCommitment = PoseidonT3.hash([idCommitment, userMessageLimit]); - if (memberExists[rateCommitment] == false) revert MemberNotRegistered(rateCommitment); + if (memberExists[idCommitment] == false) revert MemberNotRegistered(idCommitment); // check if member is registered - if (stakedAmounts[rateCommitment] == 0) { - revert MemberHasNoStake(rateCommitment); + if (stakedAmounts[idCommitment] == 0) { + revert MemberHasNoStake(idCommitment); } - if (!_verifyProof(rateCommitment, receiver, proof)) { + if (!_verifyProof(idCommitment, receiver, proof)) { revert InvalidProof(); } - uint256 amountToTransfer = stakedAmounts[rateCommitment]; + uint256 amountToTransfer = stakedAmounts[idCommitment]; // delete member - uint256 index = members[rateCommitment]; - members[rateCommitment] = 0; + uint256 index = members[idCommitment]; + members[idCommitment] = 0; indexToCommitment[index] = 0; - memberExists[rateCommitment] = false; - stakedAmounts[rateCommitment] = 0; + memberExists[idCommitment] = false; + stakedAmounts[idCommitment] = 0; userMessageLimits[idCommitment] = 0; // refund deposit @@ -272,7 +271,7 @@ abstract contract RlnBase { function getCommitments(uint256 startIndex, uint256 endIndex) public view returns (uint256[] memory) { if (startIndex >= endIndex) revert InvalidPaginationQuery(startIndex, endIndex); - if (endIndex > rateCommitmentIndex) revert InvalidPaginationQuery(startIndex, endIndex); + if (endIndex > idCommitmentIndex) revert InvalidPaginationQuery(startIndex, endIndex); uint256[] memory commitments = new uint256[](endIndex - startIndex); for (uint256 i = startIndex; i < endIndex; i++) { diff --git a/test/Rln.t.sol b/test/Rln.t.sol index bf41e98..40204ca 100644 --- a/test/Rln.t.sol +++ b/test/Rln.t.sol @@ -21,10 +21,11 @@ contract RlnTest is Test { trueVerifier = new TrueVerifier(); falseVerifier = new FalseVerifier(); - rln = new Rln(MEMBERSHIP_DEPOSIT, DEPTH, address(trueVerifier)); + rln = new Rln(MEMBERSHIP_DEPOSIT, DEPTH, MAX_MESSAGE_LIMIT, address(trueVerifier)); } uint256 public constant MEMBERSHIP_DEPOSIT = 1_000_000_000_000_000; + uint256 public constant MAX_MESSAGE_LIMIT = 20; uint256 public constant DEPTH = 20; uint256 public constant SET_SIZE = 1_048_576; uint256[8] public zeroedProof = [0, 0, 0, 0, 0, 0, 0, 0]; @@ -34,49 +35,68 @@ contract RlnTest is Test { assertEq(rln.MEMBERSHIP_DEPOSIT(), MEMBERSHIP_DEPOSIT); assertEq(rln.DEPTH(), DEPTH); assertEq(rln.SET_SIZE(), SET_SIZE); + assertEq(rln.MAX_MESSAGE_LIMIT(), MAX_MESSAGE_LIMIT); assertEq(rln.deployedBlockNumber(), 1); } function test__ValidRegistration(uint256 idCommitment) public { vm.assume(rln.isValidCommitment(idCommitment)); - rln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment); + rln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment, 1); assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT); assertEq(rln.memberExists(idCommitment), true); assertEq(rln.members(idCommitment), 0); + assertEq(rln.userMessageLimits(idCommitment), 1); } function test__InvalidRegistration__DuplicateCommitment(uint256 idCommitment) public { vm.assume(rln.isValidCommitment(idCommitment)); - rln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment); + rln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment, 1); assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT); assertEq(rln.memberExists(idCommitment), true); assertEq(rln.members(idCommitment), 0); + assertEq(rln.userMessageLimits(idCommitment), 1); vm.expectRevert(DuplicateIdCommitment.selector); - rln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment); + rln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment, 1); } function test__InvalidRegistration__InvalidIdCommitment(uint256 idCommitment) public { vm.assume(!rln.isValidCommitment(idCommitment)); vm.expectRevert(abi.encodeWithSelector(InvalidIdCommitment.selector, idCommitment)); - rln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment); + rln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment, 1); + } + + function test__InvalidRegistration__InvalidUserMessageLimit() public { + uint256 idCommitment = + 9_014_214_495_641_488_759_237_505_126_948_346_942_972_912_379_615_652_741_039_992_445_865_937_985_820; + vm.assume(rln.isValidCommitment(idCommitment)); + vm.expectRevert(abi.encodeWithSelector(InvalidUserMessageLimit.selector, 0)); + rln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment, 0); + } + + function test__InvalidRegistration__MaxUserMessageLimit() public { + uint256 idCommitment = + 9_014_214_495_641_488_759_237_505_126_948_346_942_972_912_379_615_652_741_039_992_445_865_937_985_820; + vm.assume(rln.isValidCommitment(idCommitment)); + vm.expectRevert(abi.encodeWithSelector(InvalidUserMessageLimit.selector, MAX_MESSAGE_LIMIT + 1)); + rln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment, MAX_MESSAGE_LIMIT + 1); } function test__InvalidRegistration__InsufficientDeposit(uint256 idCommitment) public { vm.assume(rln.isValidCommitment(idCommitment)); uint256 badDepositAmount = MEMBERSHIP_DEPOSIT - 1; vm.expectRevert(abi.encodeWithSelector(InsufficientDeposit.selector, MEMBERSHIP_DEPOSIT, badDepositAmount)); - rln.register{ value: badDepositAmount }(idCommitment); + rln.register{ value: badDepositAmount }(idCommitment, 1); } function test__InvalidRegistration__FullSet() public { - Rln tempRln = new Rln(MEMBERSHIP_DEPOSIT, 2, address(rln.verifier())); + Rln tempRln = new Rln(MEMBERSHIP_DEPOSIT, 2, MAX_MESSAGE_LIMIT, address(rln.verifier())); uint256 setSize = tempRln.SET_SIZE(); for (uint256 i = 1; i <= setSize; i++) { - tempRln.register{ value: MEMBERSHIP_DEPOSIT }(i); + tempRln.register{ value: MEMBERSHIP_DEPOSIT }(i, 1); } assertEq(tempRln.idCommitmentIndex(), 4); vm.expectRevert(FullTree.selector); - tempRln.register{ value: MEMBERSHIP_DEPOSIT }(setSize + 1); + tempRln.register{ value: MEMBERSHIP_DEPOSIT }(setSize + 1, 1); } function test__ValidSlash(uint256 idCommitment, address payable to) public { @@ -87,7 +107,7 @@ contract RlnTest is Test { vm.assume(to != address(0)); vm.assume(rln.isValidCommitment(idCommitment)); - rln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment); + rln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment, 1); assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT); uint256 balanceBefore = to.balance; @@ -105,7 +125,7 @@ contract RlnTest is Test { uint256 idCommitment = 9_014_214_495_641_488_759_237_505_126_948_346_942_972_912_379_615_652_741_039_992_445_865_937_985_820; - rln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment); + rln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment, 1); assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT); vm.expectRevert(abi.encodeWithSelector(InvalidReceiverAddress.selector, address(0))); rln.slash(idCommitment, payable(address(0)), zeroedProof); @@ -114,7 +134,7 @@ contract RlnTest is Test { function test__InvalidSlash__ToRlnAddress() public { uint256 idCommitment = 19_014_214_495_641_488_759_237_505_126_948_346_942_972_912_379_615_652_741_039_992_445_865_937_985_820; - rln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment); + rln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment, 1); assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT); vm.expectRevert(abi.encodeWithSelector(InvalidReceiverAddress.selector, address(rln))); rln.slash(idCommitment, payable(address(rln)), zeroedProof); @@ -134,7 +154,7 @@ contract RlnTest is Test { vm.assume(to != address(0)); vm.assume(rln.isValidCommitment(idCommitment)); - rln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment); + rln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment, 1); assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT); rln.slash(idCommitment, to, zeroedProof); @@ -152,9 +172,9 @@ contract RlnTest is Test { uint256 idCommitment = 19_014_214_495_641_488_759_237_505_126_948_346_942_972_912_379_615_652_741_039_992_445_865_937_985_820; - Rln tempRln = new Rln(MEMBERSHIP_DEPOSIT, 2, address(falseVerifier)); + Rln tempRln = new Rln(MEMBERSHIP_DEPOSIT, 2, MAX_MESSAGE_LIMIT, address(falseVerifier)); - tempRln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment); + tempRln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment, 1); vm.expectRevert(InvalidProof.selector); tempRln.slash(idCommitment, payable(address(this)), zeroedProof); @@ -168,7 +188,7 @@ contract RlnTest is Test { function test__InvalidWithdraw__InsufficientContractBalance() public { uint256 idCommitment = 19_014_214_495_641_488_759_237_505_126_948_346_942_972_912_379_615_652_741_039_992_445_865_937_985_820; - rln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment); + rln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment, 1); assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT); rln.slash(idCommitment, payable(address(this)), zeroedProof); assertEq(rln.stakedAmounts(idCommitment), 0); @@ -187,7 +207,7 @@ contract RlnTest is Test { uint256 idCommitment = 19_014_214_495_641_488_759_237_505_126_948_346_942_972_912_379_615_652_741_039_992_445_865_937_985_820; - rln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment); + rln.register{ value: MEMBERSHIP_DEPOSIT }(idCommitment, 1); assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT); rln.slash(idCommitment, to, zeroedProof); assertEq(rln.stakedAmounts(idCommitment), 0);