Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: tests #5

Merged
merged 8 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
WakuRlnV2Test:test__ValidRegistration() (gas: 108661)
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, μ: 403497, ~: 136269)
WakuRlnV2Test:test__ValidPaginationQuery__OneElement() (gas: 128461)
WakuRlnV2Test:test__ValidRegistration(uint256,uint32) (runs: 1001, μ: 133518, ~: 133518)
WakuRlnV2Test:test__ValidRegistration__kats() (gas: 108902)
3 changes: 3 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
src = "src"
test = "test"

[fuzz]
max_test_rejects = 128_000

[profile.ci]
fuzz = { runs = 10_000 }
verbosity = 4
Expand Down
74 changes: 58 additions & 16 deletions src/WakuRlnV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,18 @@
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;

Check warning on line 29 in src/WakuRlnV2.sol

View workflow job for this annotation

GitHub Actions / lint

Variable name must be in mixedCase

/// @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;

Check warning on line 35 in src/WakuRlnV2.sol

View workflow job for this annotation

GitHub Actions / lint

Variable name must be in mixedCase

/// @notice The index of the next member to be registered
uint32 public idCommitmentIndex = 0;

/// @notice the membership metadata of the member
struct MembershipInfo {
/// @notice the user message limit of each member
uint32 userMessageLimit;
Expand All @@ -45,7 +46,7 @@
}

/// @notice the member metadata
mapping(uint256 => MembershipInfo) public memberInfo;

Check warning on line 49 in src/WakuRlnV2.sol

View workflow job for this annotation

GitHub Actions / lint

Main key parameter in mapping memberInfo is not named

Check warning on line 49 in src/WakuRlnV2.sol

View workflow job for this annotation

GitHub Actions / lint

Value parameter in mapping memberInfo is not named

/// @notice the deployed block number
uint32 public immutable deployedBlockNumber;
Expand All @@ -59,27 +60,67 @@
/// @param index The index of the member in the set
event MemberRegistered(uint256 idCommitment, uint32 userMessageLimit, uint32 index);

/// @notice the modifier to check if the idCommitment is valid
/// @param idCommitment The idCommitment of the member
modifier onlyValidIdCommitment(uint256 idCommitment) {
if (!isValidCommitment(idCommitment)) revert InvalidIdCommitment(idCommitment);
_;
}

modifier onlyValidUserMessageLimit(uint32 messageLimit) {
if (messageLimit > MAX_MESSAGE_LIMIT) revert InvalidUserMessageLimit(messageLimit);
if (messageLimit == 0) revert InvalidUserMessageLimit(messageLimit);
/// @notice the modifier to check if the userMessageLimit is valid
/// @param userMessageLimit The user message limit
modifier onlyValidUserMessageLimit(uint32 userMessageLimit) {
alrevuelta marked this conversation as resolved.
Show resolved Hide resolved
if (!isValidUserMessageLimit(userMessageLimit)) revert InvalidUserMessageLimit(userMessageLimit);
_;
}

/// @notice the constructor of the contract
constructor(uint32 maxMessageLimit) {
MAX_MESSAGE_LIMIT = maxMessageLimit;
SET_SIZE = uint32(1 << DEPTH);
deployedBlockNumber = uint32(block.number);
LazyIMT.init(imtData, DEPTH);
}

function memberExists(uint256 idCommitment) public view returns (bool) {
/// @notice Checks if a commitment is valid
/// @param idCommitment The idCommitment of the member
/// @return true if the commitment is valid, false otherwise
function isValidCommitment(uint256 idCommitment) public pure returns (bool) {
return idCommitment != 0 && idCommitment < Q;
}

/// @notice Checks if a user message limit is valid
/// @param userMessageLimit The user message limit
/// @return true if the user message limit is valid, false otherwise
function isValidUserMessageLimit(uint32 userMessageLimit) public view returns (bool) {
return userMessageLimit > 0 && userMessageLimit <= MAX_MESSAGE_LIMIT;
}

/// @notice Returns the rateCommitment of a member
/// @param index The index of the member
/// @return The rateCommitment of the member
function indexToCommitment(uint32 index) internal view returns (uint256) {
return imtData.elements[LazyIMT.indexForElement(0, index)];
}

/// @notice Returns the metadata of a member
/// @param idCommitment The idCommitment of the member
/// @return The metadata of the member (userMessageLimit, index, rateCommitment)
function idCommitmentToMetadata(uint256 idCommitment) public view returns (uint32, uint32, uint256) {
MembershipInfo memory member = memberInfo[idCommitment];
return member.userMessageLimit > 0 && member.index >= 0;
// we cannot call indexToCommitment for 0 index if the member doesn't exist
if (member.userMessageLimit == 0) {
return (0, 0, 0);
}
return (member.userMessageLimit, member.index, indexToCommitment(member.index));
}

/// @notice Checks if a member exists
/// @param idCommitment The idCommitment of the member
/// @return true if the member exists, false otherwise
function memberExists(uint256 idCommitment) public view returns (bool) {
(,, uint256 rateCommitment) = idCommitmentToMetadata(idCommitment);
return rateCommitment != 0;
}

/// Allows a user to register as a member
Expand Down Expand Up @@ -112,29 +153,30 @@
idCommitmentIndex += 1;
}

function isValidCommitment(uint256 idCommitment) public pure returns (bool) {
return idCommitment != 0 && idCommitment < Q;
}

function indexToCommitment(uint32 index) public view returns (uint256) {
return imtData.elements[LazyIMT.indexForElement(0, index)];
}

/// @notice Returns the commitments of a range of members
alrevuelta marked this conversation as resolved.
Show resolved Hide resolved
/// @param startIndex The start index of the range
/// @param endIndex The end index of the range
/// @return The commitments of the members
alrevuelta marked this conversation as resolved.
Show resolved Hide resolved
function getCommitments(uint32 startIndex, uint32 endIndex) public view returns (uint256[] memory) {
if (startIndex >= endIndex) revert InvalidPaginationQuery(startIndex, endIndex);
if (startIndex > endIndex) revert InvalidPaginationQuery(startIndex, endIndex);
if (endIndex > idCommitmentIndex) revert InvalidPaginationQuery(startIndex, endIndex);

uint256[] memory commitments = new uint256[](endIndex - startIndex);
for (uint32 i = startIndex; i < endIndex; i++) {
uint256[] memory commitments = new uint256[](endIndex - startIndex + 1);
for (uint32 i = startIndex; i <= endIndex; i++) {
commitments[i - startIndex] = indexToCommitment(i);
}
return commitments;
}

/// @notice Returns the root of the IMT
/// @return The root of the IMT
function root() external view returns (uint256) {
return LazyIMT.root(imtData, DEPTH);
}

/// @notice Returns the merkle proof elements of a given membership
/// @param index The index of the member
/// @return The merkle proof elements of the member
function merkleProofElements(uint40 index) public view returns (uint256[] memory) {
return LazyIMT.merkleProofElements(imtData, index, DEPTH);
}
Expand Down
122 changes: 115 additions & 7 deletions test/WakuRlnV2.t.sol
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.19 <0.9.0;

import { Test, console } from "forge-std/Test.sol";
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 { WakuRlnV2 } from "../src/WakuRlnV2.sol";
import "../src/WakuRlnV2.sol";

Check warning on line 9 in test/WakuRlnV2.t.sol

View workflow job for this annotation

GitHub Actions / lint

global import of path ../src/WakuRlnV2.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just mentioning this: Typically it's easier to follow where stuff comes from with named imports. No biggie at all here, but if there isn't a good reason to make this change I'd rather keep it as it was.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah there's a bunch of errors that we assert for, thats why grouped all the imports together :)

import { PoseidonT3 } from "poseidon-solidity/PoseidonT3.sol";
import { LazyIMT } from "@zk-kit/imt.sol/LazyIMT.sol";

Check warning on line 11 in test/WakuRlnV2.t.sol

View workflow job for this annotation

GitHub Actions / lint

imported name LazyIMT is not used

contract WakuRlnV2Test is Test {
using stdStorage for StdStorage;

WakuRlnV2 internal w;
DeploymentConfig internal deploymentConfig;

Expand All @@ -20,7 +23,7 @@
(w, deploymentConfig) = deployment.run();
}

function test__ValidRegistration() external {
function test__ValidRegistration__kats() external {
vm.pauseGasMetering();
uint256 idCommitment = 2;
uint32 userMessageLimit = 2;
Expand All @@ -35,14 +38,119 @@
// kats from zerokit
uint256 rateCommitment =
4_699_387_056_273_519_054_140_667_386_511_343_037_709_699_938_246_587_880_795_929_666_834_307_503_001;
assertEq(w.indexToCommitment(0), rateCommitment);
uint256[] memory commitments = w.getCommitments(0, 1);
assertEq(commitments.length, 1);
assertEq(commitments[index], rateCommitment);
assertEq(
w.root(),
13_801_897_483_540_040_307_162_267_952_866_411_686_127_372_014_953_358_983_481_592_640_000_001_877_295
);
(uint32 fetchedUserMessageLimit2, uint32 index2, uint256 rateCommitment2) =
w.idCommitmentToMetadata(idCommitment);
assertEq(fetchedUserMessageLimit2, userMessageLimit);
assertEq(index2, 0);
assertEq(rateCommitment2, rateCommitment);
vm.resumeGasMetering();
}
Comment on lines +48 to +51
Copy link
Contributor Author

@rymnc rymnc May 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably add a kat here for the merkleProof elements. left it out since we'd be basically testing the upstream library, but I can add it in a follow up PR


function test__ValidRegistration(uint256 idCommitment, uint32 userMessageLimit) external {
vm.assume(w.isValidCommitment(idCommitment) && w.isValidUserMessageLimit(userMessageLimit));

assertEq(w.memberExists(idCommitment), false);
w.register(idCommitment, userMessageLimit);
uint256 rateCommitment = PoseidonT3.hash([idCommitment, userMessageLimit]);

(uint32 fetchedUserMessageLimit, uint32 index, uint256 fetchedRateCommitment) =
w.idCommitmentToMetadata(idCommitment);
assertEq(fetchedUserMessageLimit, userMessageLimit);
assertEq(index, 0);
assertEq(fetchedRateCommitment, rateCommitment);
}

function test__IdCommitmentToMetadata__DoesntExist() external {
uint256 idCommitment = 2;
(uint32 userMessageLimit, uint32 index, uint256 rateCommitment) = w.idCommitmentToMetadata(idCommitment);
assertEq(userMessageLimit, 0);
assertEq(index, 0);
assertEq(rateCommitment, 0);
}

function test__InvalidRegistration__InvalidIdCommitment__Zero() external {
uint256 idCommitment = 0;
uint32 userMessageLimit = 2;
vm.expectRevert(abi.encodeWithSelector(InvalidIdCommitment.selector, 0));
w.register(idCommitment, userMessageLimit);
}

function test__InvalidRegistration__InvalidIdCommitment__LargerThanField() external {
uint256 idCommitment = w.Q() + 1;
uint32 userMessageLimit = 2;
vm.expectRevert(abi.encodeWithSelector(InvalidIdCommitment.selector, idCommitment));
w.register(idCommitment, userMessageLimit);
}

function test__InvalidRegistration__InvalidUserMessageLimit__Zero() external {
uint256 idCommitment = 2;
uint32 userMessageLimit = 0;
vm.expectRevert(abi.encodeWithSelector(InvalidUserMessageLimit.selector, 0));
w.register(idCommitment, userMessageLimit);
}

function test__InvalidRegistration__InvalidUserMessageLimit__LargerThanMax() external {
uint256 idCommitment = 2;
uint32 userMessageLimit = w.MAX_MESSAGE_LIMIT() + 1;
vm.expectRevert(abi.encodeWithSelector(InvalidUserMessageLimit.selector, userMessageLimit));
w.register(idCommitment, userMessageLimit);
}

function test__InvalidRegistration__DuplicateIdCommitment() external {
uint256 idCommitment = 2;
uint32 userMessageLimit = 2;
w.register(idCommitment, userMessageLimit);
vm.expectRevert(DuplicateIdCommitment.selector);
w.register(idCommitment, userMessageLimit);
}

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());
vm.expectRevert(FullTree.selector);
w.register(1, userMessageLimit);
}

function test__InvalidPaginationQuery__StartIndexGTEndIndex() external {
vm.expectRevert(abi.encodeWithSelector(InvalidPaginationQuery.selector, 1, 0));
w.getCommitments(1, 0);
}

function test__InvalidPaginationQuery__EndIndexGTIdCommitmentIndex() external {
vm.expectRevert(abi.encodeWithSelector(InvalidPaginationQuery.selector, 0, 2));
w.getCommitments(0, 2);
}

function test__ValidPaginationQuery__OneElement() external {
uint32 userMessageLimit = 2;
uint256 idCommitment = 1;
w.register(idCommitment, userMessageLimit);
uint256[] memory commitments = w.getCommitments(0, 0);
assertEq(commitments.length, 1);
uint256 rateCommitment = PoseidonT3.hash([idCommitment, userMessageLimit]);
assertEq(commitments[0], rateCommitment);
}

function test__ValidPaginationQuery(uint32 idCommitmentsLength) external {
vm.assume(idCommitmentsLength > 0 && idCommitmentsLength <= 100);
uint32 userMessageLimit = 2;

vm.pauseGasMetering();
for (uint256 i = 0; i < idCommitmentsLength; i++) {
w.register(i + 1, userMessageLimit);
}
vm.resumeGasMetering();

uint256[] memory commitments = w.getCommitments(0, idCommitmentsLength);
assertEq(commitments.length, idCommitmentsLength + 1);
for (uint256 i = 0; i < idCommitmentsLength; i++) {
uint256 rateCommitment = PoseidonT3.hash([i + 1, userMessageLimit]);
assertEq(commitments[i], rateCommitment);
}
}
}
Loading