mirror of
https://github.com/logos-messaging/logos-messaging-rlnv2-contract.git
synced 2026-01-07 08:23:09 +00:00
feat: membership
This commit is contained in:
parent
64df4593c6
commit
f3d085df8d
@ -1,14 +1,11 @@
|
||||
WakuRlnV2Test:test__IdCommitmentToMetadata__DoesntExist() (gas: 16726)
|
||||
WakuRlnV2Test:test__InvalidPaginationQuery__EndIndexGTcommitmentIndex() (gas: 18249)
|
||||
WakuRlnV2Test:test__InvalidPaginationQuery__StartIndexGTEndIndex() (gas: 16130)
|
||||
WakuRlnV2Test:test__InvalidRegistration__DuplicateIdCommitment() (gas: 99502)
|
||||
WakuRlnV2Test:test__InvalidRegistration__FullTree() (gas: 14328)
|
||||
WakuRlnV2Test:test__InvalidRegistration__InvalidIdCommitment__LargerThanField() (gas: 15229)
|
||||
WakuRlnV2Test:test__InvalidRegistration__InvalidIdCommitment__Zero() (gas: 14004)
|
||||
WakuRlnV2Test:test__InvalidRegistration__InvalidUserMessageLimit__LargerThanMax() (gas: 17703)
|
||||
WakuRlnV2Test:test__InvalidRegistration__InvalidUserMessageLimit__Zero() (gas: 14085)
|
||||
WakuRlnV2Test:test__Upgrade() (gas: 3791109)
|
||||
WakuRlnV2Test:test__ValidPaginationQuery(uint32) (runs: 1000, μ: 445155, ~: 159972)
|
||||
WakuRlnV2Test:test__ValidPaginationQuery__OneElement() (gas: 119773)
|
||||
WakuRlnV2Test:test__ValidRegistration(uint256,uint32) (runs: 1000, μ: 124408, ~: 124408)
|
||||
WakuRlnV2Test:test__ValidRegistration__kats() (gas: 96616)
|
||||
WakuRlnV2Test:test__IdCommitmentToMetadata__DoesntExist() (gas: 27478)
|
||||
WakuRlnV2Test:test__InsertionNormalOrder(uint32) (runs: 1001, μ: 1123469, ~: 494837)
|
||||
WakuRlnV2Test:test__InvalidPaginationQuery__EndIndexGTcommitmentIndex() (gas: 18261)
|
||||
WakuRlnV2Test:test__InvalidPaginationQuery__StartIndexGTEndIndex() (gas: 16108)
|
||||
WakuRlnV2Test:test__InvalidRegistration__DuplicateIdCommitment() (gas: 249370)
|
||||
WakuRlnV2Test:test__InvalidRegistration__InvalidIdCommitment__Zero() (gas: 12135)
|
||||
WakuRlnV2Test:test__Upgrade() (gas: 6728616)
|
||||
WakuRlnV2Test:test__ValidPaginationQuery(uint32) (runs: 1002, μ: 226428, ~: 52927)
|
||||
WakuRlnV2Test:test__ValidPaginationQuery__OneElement() (gas: 262351)
|
||||
WakuRlnV2Test:test__ValidRegistration(uint256,uint32) (runs: 1001, μ: 268741, ~: 268741)
|
||||
WakuRlnV2Test:test__ValidRegistration__kats() (gas: 238744)
|
||||
2453
pnpm-lock.yaml
generated
2453
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -2,18 +2,22 @@
|
||||
pragma solidity >=0.8.19 <=0.9.0;
|
||||
|
||||
import { WakuRlnV2 } from "../src/WakuRlnV2.sol";
|
||||
import { LinearPriceCalculator } from "../src/LinearPriceCalculator.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, address impl) {
|
||||
// TODO: Use the correct values when deploying to mainnet
|
||||
address priceCalcAddr = address(new LinearPriceCalculator(address(0), 0.05 ether));
|
||||
// TODO: set DAI address 0x6B175474E89094C44Da98b954EedeAC495271d0F
|
||||
impl = address(new WakuRlnV2());
|
||||
bytes memory data = abi.encodeCall(WakuRlnV2.initialize, 100);
|
||||
bytes memory data = abi.encodeCall(WakuRlnV2.initialize, (priceCalcAddr, 160_000, 20, 600, 30 days, 5 days));
|
||||
// (priceCalcAddr, 160000, 20, 600, 30 days, 5 days)
|
||||
address proxy = address(new ERC1967Proxy(impl, data));
|
||||
w = WakuRlnV2(proxy);
|
||||
}
|
||||
|
||||
11
src/IPriceCalculator.sol
Normal file
11
src/IPriceCalculator.sol
Normal file
@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.24;
|
||||
|
||||
interface IPriceCalculator {
|
||||
/// Returns the token and price to pay in `token` for some `_rateLimit`
|
||||
/// @param _rateLimit the rate limit the user wants to acquire
|
||||
/// @param _numberOfPeriods the number of periods the user wants to acquire
|
||||
/// @return address of the erc20 token
|
||||
/// @return uint price to pay for acquiring the specified `_rateLimit`
|
||||
function calculate(uint256 _rateLimit, uint32 _numberOfPeriods) external view returns (address, uint256);
|
||||
}
|
||||
36
src/LinearPriceCalculator.sol
Normal file
36
src/LinearPriceCalculator.sol
Normal file
@ -0,0 +1,36 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.24;
|
||||
|
||||
import { Ownable } from "openzeppelin-contracts/contracts/access/Ownable.sol";
|
||||
import { IPriceCalculator } from "./IPriceCalculator.sol";
|
||||
|
||||
/// @title Linear Price Calculator to determine the price to acquire a membership
|
||||
contract LinearPriceCalculator is IPriceCalculator, Ownable {
|
||||
/// @notice Address of the ERC20 token accepted by this contract. Address(0) represents ETH
|
||||
address public token;
|
||||
|
||||
/// @notice The price per message per epoch per period
|
||||
uint256 public pricePerMessagePerPeriod;
|
||||
|
||||
constructor(address _token, uint256 _pricePerPeriod) Ownable() {
|
||||
token = _token;
|
||||
pricePerMessagePerPeriod = _pricePerPeriod;
|
||||
}
|
||||
|
||||
/// Set accepted token and price per message per epoch per period
|
||||
/// @param _token The token accepted by the membership management for RLN
|
||||
/// @param _pricePerPeriod Price per message per epoch
|
||||
function setTokenAndPrice(address _token, uint256 _pricePerPeriod) external onlyOwner {
|
||||
token = _token;
|
||||
pricePerMessagePerPeriod = _pricePerPeriod;
|
||||
}
|
||||
|
||||
/// Returns the token and price to pay in `token` for some `_rateLimit`
|
||||
/// @param _rateLimit the rate limit the user wants to acquire
|
||||
/// @param _numberOfPeriods the number of periods the user wants to acquire
|
||||
/// @return address of the erc20 token
|
||||
/// @return uint price to pay for acquiring the specified `_rateLimit`
|
||||
function calculate(uint256 _rateLimit, uint32 _numberOfPeriods) external view returns (address, uint256) {
|
||||
return (token, uint256(_numberOfPeriods) * uint256(_rateLimit) * pricePerMessagePerPeriod);
|
||||
}
|
||||
}
|
||||
427
src/Membership.sol
Normal file
427
src/Membership.sol
Normal file
@ -0,0 +1,427 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.24;
|
||||
|
||||
import { IPriceCalculator } from "./IPriceCalculator.sol";
|
||||
import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import "openzeppelin-contracts/contracts/utils/Context.sol";
|
||||
|
||||
// The number of periods should be greater than zero
|
||||
error NumberOfPeriodsCantBeZero();
|
||||
|
||||
// ETH Amount passed as value on the transaction is not correct
|
||||
error IncorrectAmount();
|
||||
|
||||
// An eth value was assigned in the transaction and only tokens were expected
|
||||
error OnlyTokensAccepted();
|
||||
|
||||
// The specified rate limit was not correct or within the expected limits
|
||||
error InvalidRateLimit();
|
||||
|
||||
// It's not possible to acquire the rate limit due to exceeding the expected limits
|
||||
// even after attempting to erase expired memberships
|
||||
error ExceedMaxRateLimitPerEpoch();
|
||||
|
||||
// This membership is not in grace period yet
|
||||
error NotInGracePeriod(uint256 idCommitment);
|
||||
|
||||
// The sender is not the holder of the membership
|
||||
error NotHolder(uint256 idCommitment);
|
||||
|
||||
// This membership cannot be erased (either it is not expired or not in grace period and/or not the owner)
|
||||
error CantEraseMembership(uint256 idCommitment);
|
||||
|
||||
contract Membership {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
/// @notice Address of the Price Calculator used to calculate the price of a new membership
|
||||
IPriceCalculator public priceCalculator;
|
||||
|
||||
/// @notice Maximum total rate limit of all memberships in the tree
|
||||
uint32 public maxTotalRateLimitPerEpoch;
|
||||
|
||||
/// @notice Maximum rate limit of one membership
|
||||
uint32 public maxRateLimitPerMembership;
|
||||
|
||||
/// @notice Minimum rate limit of one membership
|
||||
uint32 public minRateLimitPerMembership;
|
||||
|
||||
/// @notice Membership billing period
|
||||
uint32 public billingPeriod;
|
||||
|
||||
/// @notice Membership grace period
|
||||
uint32 public gracePeriod;
|
||||
|
||||
/// @notice balances available to withdraw
|
||||
mapping(address => mapping(address => uint256)) public balancesToWithdraw; // holder -> token -> balance
|
||||
|
||||
/// @notice Total rate limit of all memberships in the tree
|
||||
uint256 public totalRateLimitPerEpoch;
|
||||
|
||||
/// @notice List of registered memberships (IDCommitment to membership metadata)
|
||||
mapping(uint256 => MembershipInfo) public members;
|
||||
|
||||
/// @notice The index of the next member to be registered
|
||||
uint32 public commitmentIndex;
|
||||
|
||||
/// @notice track available indices that are available due to expired memberships being removed
|
||||
uint32[] public availableExpiredIndices;
|
||||
|
||||
/// @dev Oldest membership
|
||||
uint256 public head = 0;
|
||||
|
||||
/// @dev Newest membership
|
||||
uint256 public tail = 0;
|
||||
|
||||
struct MembershipInfo {
|
||||
/// @notice idCommitment of the previous membership
|
||||
uint256 prev;
|
||||
/// @notice idCommitment of the next membership
|
||||
uint256 next;
|
||||
/// @notice amount of the token used to acquire this membership
|
||||
uint256 amount;
|
||||
/// @notice numPeriods
|
||||
uint32 numberOfPeriods;
|
||||
/// @notice timestamp of when the grace period starts for this membership
|
||||
uint256 gracePeriodStartDate;
|
||||
/// @notice duration of the grace period
|
||||
uint32 gracePeriod;
|
||||
/// @notice the user message limit of each member
|
||||
uint32 userMessageLimit;
|
||||
/// @notice the index of the member in the set
|
||||
uint32 index;
|
||||
/// @notice address of the owner of this membership
|
||||
address holder;
|
||||
/// @notice token used to acquire this membership
|
||||
address token;
|
||||
}
|
||||
|
||||
/// @notice Emitted when a membership is erased due to having exceeded the grace period or the owner having chosen
|
||||
/// to not extend it
|
||||
/// @param idCommitment the idCommitment of the member
|
||||
/// @param userMessageLimit the rate limit of this membership
|
||||
/// @param index the index of the membership in the merkle tree
|
||||
event MemberExpired(uint256 idCommitment, uint32 userMessageLimit, uint32 index);
|
||||
|
||||
/// @notice Emitted when a membership in grace period is extended
|
||||
/// @param idCommitment the idCommitment of the member
|
||||
/// @param userMessageLimit the rate limit of this membership
|
||||
/// @param index the index of the membership in the merkle tree
|
||||
/// @param newExpirationDate the new expiration date of this membership
|
||||
event MemberExtended(uint256 idCommitment, uint32 userMessageLimit, uint32 index, uint256 newExpirationDate);
|
||||
|
||||
/// @dev contract initializer
|
||||
/// @param _priceCalculator Address of an instance of IPriceCalculator
|
||||
/// @param _maxTotalRateLimitPerEpoch Maximum total rate limit of all memberships in the tree
|
||||
/// @param _minRateLimitPerMembership Minimum rate limit of one membership
|
||||
/// @param _maxRateLimitPerMembership Maximum rate limit of one membership
|
||||
/// @param _expirationTerm Membership expiration term
|
||||
/// @param _gracePeriod Membership grace period
|
||||
function __Membership_init(
|
||||
address _priceCalculator,
|
||||
uint32 _maxTotalRateLimitPerEpoch,
|
||||
uint32 _minRateLimitPerMembership,
|
||||
uint32 _maxRateLimitPerMembership,
|
||||
uint32 _expirationTerm,
|
||||
uint32 _gracePeriod
|
||||
)
|
||||
internal
|
||||
{
|
||||
priceCalculator = IPriceCalculator(_priceCalculator);
|
||||
maxTotalRateLimitPerEpoch = _maxTotalRateLimitPerEpoch;
|
||||
maxRateLimitPerMembership = _maxRateLimitPerMembership;
|
||||
minRateLimitPerMembership = _minRateLimitPerMembership;
|
||||
billingPeriod = _expirationTerm;
|
||||
gracePeriod = _gracePeriod;
|
||||
}
|
||||
|
||||
/// @notice Checks if a user message limit is valid. This does not take into account whether we the total
|
||||
/// memberships have reached already the `maxTotalRateLimitPerEpoch`
|
||||
/// @param userMessageLimit The user message limit
|
||||
/// @return true if the user message limit is valid, false otherwise
|
||||
function isValidUserMessageLimit(uint32 userMessageLimit) external view returns (bool) {
|
||||
return userMessageLimit >= minRateLimitPerMembership && userMessageLimit <= maxRateLimitPerMembership;
|
||||
}
|
||||
|
||||
/// @dev acquire a membership and trasnfer the fees to the contract
|
||||
/// @param _sender address of the owner of the new membership
|
||||
/// @param _idCommitment the idcommitment of the new membership
|
||||
/// @param _rateLimit the user message limit
|
||||
/// @param _numberOfPeriods the number of periods the user wants to acquire
|
||||
/// @return index the index in the merkle tree
|
||||
/// @return reusedIndex indicates whether a new leaf is being used or if using an existing leaf in the merkle tree
|
||||
function _acquireMembership(
|
||||
address _sender,
|
||||
uint256 _idCommitment,
|
||||
uint32 _rateLimit,
|
||||
uint32 _numberOfPeriods
|
||||
)
|
||||
internal
|
||||
returns (uint32 index, bool reusedIndex)
|
||||
{
|
||||
if (_numberOfPeriods == 0) revert NumberOfPeriodsCantBeZero();
|
||||
|
||||
(address token, uint256 amount) = priceCalculator.calculate(_rateLimit, _numberOfPeriods);
|
||||
(index, reusedIndex) =
|
||||
_setupMembershipDetails(_sender, _idCommitment, _rateLimit, _numberOfPeriods, token, amount);
|
||||
_transferFees(_sender, token, amount);
|
||||
}
|
||||
|
||||
function _transferFees(address _from, address _token, uint256 _amount) internal {
|
||||
if (_token == address(0)) {
|
||||
if (msg.value != _amount) revert IncorrectAmount();
|
||||
} else {
|
||||
if (msg.value != 0) revert OnlyTokensAccepted();
|
||||
IERC20(_token).safeTransferFrom(_from, address(this), _amount);
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Setup a new membership. If there are not enough remaining rate limit to acquire
|
||||
/// a new membership, it will attempt to erase existing memberships and reuse one of the
|
||||
/// slots helds by the membership
|
||||
/// @param _sender holder of the membership. Generally `msg.sender`
|
||||
/// @param _idCommitment IDCommitment
|
||||
/// @param _rateLimit User message limit
|
||||
/// @param _numberOfPeriods the number of periods the user wants to acquire
|
||||
/// @param _token Address of the token used to acquire the membership
|
||||
/// @param _amount Amount of the token used to acquire the membership
|
||||
/// @return index membership index on the merkle tree
|
||||
/// @return reusedIndex indicates whether the index returned was a reused slot on the tree or not
|
||||
function _setupMembershipDetails(
|
||||
address _sender,
|
||||
uint256 _idCommitment,
|
||||
uint32 _rateLimit,
|
||||
uint32 _numberOfPeriods,
|
||||
address _token,
|
||||
uint256 _amount
|
||||
)
|
||||
internal
|
||||
returns (uint32 index, bool reusedIndex)
|
||||
{
|
||||
if (_rateLimit < minRateLimitPerMembership || _rateLimit > maxRateLimitPerMembership) {
|
||||
revert InvalidRateLimit();
|
||||
}
|
||||
|
||||
// Attempt to free expired membership slots
|
||||
while (totalRateLimitPerEpoch + _rateLimit > maxTotalRateLimitPerEpoch) {
|
||||
// Determine if there are any available spot in the membership map
|
||||
// by looking at the oldest membership. If it's expired, we can free it
|
||||
MembershipInfo memory oldestMembership = members[head];
|
||||
|
||||
if (
|
||||
oldestMembership.holder != address(0) // membership has a holder
|
||||
&& isExpired(oldestMembership.gracePeriodStartDate)
|
||||
) {
|
||||
emit MemberExpired(head, oldestMembership.userMessageLimit, oldestMembership.index);
|
||||
|
||||
// Deduct the expired membership rate limit
|
||||
totalRateLimitPerEpoch -= oldestMembership.userMessageLimit;
|
||||
|
||||
// Promote the next oldest membership to oldest
|
||||
uint256 nextOldest = oldestMembership.next;
|
||||
head = nextOldest;
|
||||
if (nextOldest != 0) {
|
||||
members[nextOldest].prev = 0;
|
||||
}
|
||||
|
||||
// Move balance from expired membership to holder balance
|
||||
balancesToWithdraw[oldestMembership.holder][oldestMembership.token] += oldestMembership.amount;
|
||||
|
||||
availableExpiredIndices.push(oldestMembership.index);
|
||||
|
||||
delete members[head];
|
||||
} else {
|
||||
revert ExceedMaxRateLimitPerEpoch();
|
||||
}
|
||||
}
|
||||
|
||||
uint256 prev = 0;
|
||||
if (tail != 0) {
|
||||
MembershipInfo storage latestMembership = members[tail];
|
||||
latestMembership.next = _idCommitment;
|
||||
prev = tail;
|
||||
} else {
|
||||
// First item
|
||||
// TODO: test adding memberships after the list has been emptied
|
||||
head = _idCommitment;
|
||||
}
|
||||
|
||||
totalRateLimitPerEpoch += _rateLimit;
|
||||
|
||||
// Reuse available slots from previously removed expired memberships
|
||||
uint256 arrLen = availableExpiredIndices.length;
|
||||
if (arrLen != 0) {
|
||||
index = availableExpiredIndices[arrLen - 1];
|
||||
availableExpiredIndices.pop();
|
||||
reusedIndex = true;
|
||||
} else {
|
||||
index = commitmentIndex;
|
||||
}
|
||||
|
||||
members[_idCommitment] = MembershipInfo({
|
||||
holder: _sender,
|
||||
gracePeriodStartDate: block.timestamp + (uint256(billingPeriod) * uint256(_numberOfPeriods)),
|
||||
gracePeriod: gracePeriod,
|
||||
numberOfPeriods: _numberOfPeriods,
|
||||
token: _token,
|
||||
amount: _amount,
|
||||
userMessageLimit: _rateLimit,
|
||||
next: 0, // It's the last value, so point to nowhere
|
||||
prev: prev,
|
||||
index: index
|
||||
});
|
||||
|
||||
tail = _idCommitment;
|
||||
}
|
||||
|
||||
/// @dev Extend a membership expiration date. Membership must be on grace period
|
||||
/// @param _sender the address of the holder of the membership
|
||||
/// @param _idCommitment the idCommitment of the membership
|
||||
function _extendMembership(address _sender, uint256 _idCommitment) public {
|
||||
MembershipInfo storage mdetails = members[_idCommitment];
|
||||
|
||||
if (!_isGracePeriod(mdetails.gracePeriodStartDate, mdetails.gracePeriod, mdetails.numberOfPeriods)) {
|
||||
// TODO: can a membership that has exceeded the expired period be extended?
|
||||
revert NotInGracePeriod(_idCommitment);
|
||||
}
|
||||
|
||||
if (_sender != mdetails.holder) revert NotHolder(_idCommitment);
|
||||
|
||||
uint256 newExpirationDate = block.timestamp + (uint256(billingPeriod) * uint256(mdetails.numberOfPeriods));
|
||||
|
||||
uint256 mdetailsNext = mdetails.next;
|
||||
uint256 mdetailsPrev = mdetails.prev;
|
||||
|
||||
// Remove current membership references
|
||||
if (mdetailsPrev != 0) {
|
||||
members[mdetailsPrev].next = mdetailsNext;
|
||||
} else {
|
||||
head = mdetailsNext;
|
||||
}
|
||||
|
||||
if (mdetailsNext != 0) {
|
||||
members[mdetailsNext].prev = mdetailsPrev;
|
||||
} else {
|
||||
tail = mdetailsPrev;
|
||||
}
|
||||
|
||||
// Move membership to the end (since it will be the newest)
|
||||
mdetails.next = 0;
|
||||
mdetails.prev = tail;
|
||||
mdetails.gracePeriodStartDate = newExpirationDate;
|
||||
mdetails.gracePeriod = gracePeriod;
|
||||
|
||||
members[tail].next = _idCommitment;
|
||||
tail = _idCommitment;
|
||||
|
||||
emit MemberExtended(_idCommitment, mdetails.userMessageLimit, mdetails.index, newExpirationDate);
|
||||
}
|
||||
|
||||
/// @dev Determine whether a timestamp is considered to be expired or not after exceeding the grace period
|
||||
/// @param _gracePeriodStartDate timestamp in which the grace period starts
|
||||
/// @param _gracePeriod duration of the grace period
|
||||
/// @param _numberOfPeriods the number of periods the user wants to acquire
|
||||
function _isExpired(
|
||||
uint256 _gracePeriodStartDate,
|
||||
uint32 _gracePeriod,
|
||||
uint32 _numberOfPeriods
|
||||
)
|
||||
internal
|
||||
view
|
||||
returns (bool)
|
||||
{
|
||||
return block.timestamp > _gracePeriodStartDate + (uint256(_gracePeriod) * uint256(_numberOfPeriods));
|
||||
}
|
||||
|
||||
/// @notice Determine if a membership is expired (has exceeded the grace period)
|
||||
/// @param _idCommitment the idCommitment of the membership
|
||||
function isExpired(uint256 _idCommitment) public view returns (bool) {
|
||||
MembershipInfo memory m = members[_idCommitment];
|
||||
return _isExpired(m.gracePeriodStartDate, m.gracePeriod, m.numberOfPeriods);
|
||||
}
|
||||
|
||||
function expirationDate(uint256 _idCommitment) public view returns (uint256) {
|
||||
MembershipInfo memory m = members[_idCommitment];
|
||||
return m.gracePeriodStartDate + (uint256(m.gracePeriod) * uint256(m.numberOfPeriods));
|
||||
}
|
||||
|
||||
/// @dev Determine whether a timestamp is considered to be in grace period or not
|
||||
/// @param _gracePeriodStartDate timestamp in which the grace period starts
|
||||
/// @param _gracePeriod duration of the grace period
|
||||
/// @param _numberOfPeriods the number of periods the user wants to acquire
|
||||
function _isGracePeriod(
|
||||
uint256 _gracePeriodStartDate,
|
||||
uint32 _gracePeriod,
|
||||
uint32 _numberOfPeriods
|
||||
)
|
||||
internal
|
||||
view
|
||||
returns (bool)
|
||||
{
|
||||
uint256 blockTimestamp = block.timestamp;
|
||||
return blockTimestamp >= _gracePeriodStartDate
|
||||
&& blockTimestamp <= _gracePeriodStartDate + (uint256(_gracePeriod) * uint256(_numberOfPeriods));
|
||||
}
|
||||
|
||||
/// @notice Determine if a membership is in grace period
|
||||
/// @param _idCommitment the idCommitment of the membership
|
||||
function isGracePeriod(uint256 _idCommitment) public view returns (bool) {
|
||||
MembershipInfo memory m = members[_idCommitment];
|
||||
return _isGracePeriod(m.gracePeriodStartDate, m.gracePeriod, m.numberOfPeriods);
|
||||
}
|
||||
|
||||
/// @dev Remove expired memberships or owned memberships in grace period.
|
||||
/// @param _sender address of the sender of transaction (will be used to check memberships in grace period)
|
||||
/// @param _idCommitment IDCommitment of the membership to erase
|
||||
function _eraseMembership(address _sender, uint256 _idCommitment, MembershipInfo memory _mdetails) internal {
|
||||
bool membershipExpired =
|
||||
_isExpired(_mdetails.gracePeriodStartDate, _mdetails.gracePeriod, _mdetails.numberOfPeriods);
|
||||
bool isGracePeriodAndOwned = _isGracePeriod(
|
||||
_mdetails.gracePeriodStartDate, _mdetails.gracePeriod, _mdetails.numberOfPeriods
|
||||
) && _mdetails.holder == _sender;
|
||||
|
||||
if (!membershipExpired && !isGracePeriodAndOwned) revert CantEraseMembership(_idCommitment);
|
||||
|
||||
emit MemberExpired(head, _mdetails.userMessageLimit, _mdetails.index);
|
||||
|
||||
// Move balance from expired membership to holder balance
|
||||
balancesToWithdraw[_mdetails.holder][_mdetails.token] += _mdetails.amount;
|
||||
|
||||
// Deduct the expired membership rate limit
|
||||
totalRateLimitPerEpoch -= _mdetails.userMessageLimit;
|
||||
|
||||
// Remove current membership references
|
||||
if (_mdetails.prev != 0) {
|
||||
members[_mdetails.prev].next = _mdetails.next;
|
||||
} else {
|
||||
head = _mdetails.next;
|
||||
}
|
||||
|
||||
if (_mdetails.next != 0) {
|
||||
members[_mdetails.next].prev = _mdetails.prev;
|
||||
} else {
|
||||
tail = _mdetails.prev;
|
||||
}
|
||||
|
||||
availableExpiredIndices.push(_mdetails.index);
|
||||
|
||||
delete members[_idCommitment];
|
||||
}
|
||||
|
||||
/// @dev Withdraw any available balance in tokens after a membership is erased.
|
||||
/// @param _sender the address of the owner of the tokens
|
||||
/// @param _token the address of the token to withdraw. Use 0x000...000 to withdraw ETH
|
||||
function _withdraw(address _sender, address _token) internal {
|
||||
uint256 amount = balancesToWithdraw[_sender][_token];
|
||||
require(amount > 0, "Insufficient balance");
|
||||
|
||||
balancesToWithdraw[_sender][_token] = 0;
|
||||
if (_token == address(0)) {
|
||||
// ETH
|
||||
(bool success,) = _sender.call{ value: amount }("");
|
||||
require(success, "eth transfer failed");
|
||||
} else {
|
||||
IERC20(_token).safeTransfer(_sender, amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,9 @@ import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/O
|
||||
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
||||
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
||||
|
||||
import { Membership } from "./Membership.sol";
|
||||
import { IPriceCalculator } from "./IPriceCalculator.sol";
|
||||
|
||||
/// The tree is full
|
||||
error FullTree();
|
||||
|
||||
@ -23,34 +26,17 @@ error InvalidUserMessageLimit(uint32 messageLimit);
|
||||
/// Invalid pagination query
|
||||
error InvalidPaginationQuery(uint256 startIndex, uint256 endIndex);
|
||||
|
||||
contract WakuRlnV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable {
|
||||
contract WakuRlnV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable, Membership {
|
||||
/// @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 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 SET_SIZE;
|
||||
|
||||
/// @notice The index of the next member to be registered
|
||||
uint32 public commitmentIndex;
|
||||
|
||||
/// @notice the membership metadata of the member
|
||||
struct MembershipInfo {
|
||||
/// @notice the user message limit of each member
|
||||
uint32 userMessageLimit;
|
||||
/// @notice the index of the member in the set
|
||||
uint32 index;
|
||||
}
|
||||
|
||||
/// @notice the member metadata
|
||||
mapping(uint256 => MembershipInfo) public memberInfo;
|
||||
|
||||
/// @notice the deployed block number
|
||||
uint32 public deployedBlockNumber;
|
||||
|
||||
@ -69,21 +55,39 @@ contract WakuRlnV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable {
|
||||
_;
|
||||
}
|
||||
|
||||
/// @notice the modifier to check if the userMessageLimit is valid
|
||||
/// @param userMessageLimit The user message limit
|
||||
modifier onlyValidUserMessageLimit(uint32 userMessageLimit) {
|
||||
if (!isValidUserMessageLimit(userMessageLimit)) revert InvalidUserMessageLimit(userMessageLimit);
|
||||
_;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
_disableInitializers();
|
||||
}
|
||||
|
||||
function initialize(uint32 maxMessageLimit) public initializer {
|
||||
/// @dev contract initializer
|
||||
/// @param _priceCalculator Address of an instance of IPriceCalculator
|
||||
/// @param _maxTotalRateLimitPerEpoch Maximum total rate limit of all memberships in the tree
|
||||
/// @param _minRateLimitPerMembership Minimum rate limit of one membership
|
||||
/// @param _maxRateLimitPerMembership Maximum rate limit of one membership
|
||||
/// @param _billingPeriod Membership billing period
|
||||
/// @param _gracePeriod Membership grace period
|
||||
function initialize(
|
||||
address _priceCalculator,
|
||||
uint32 _maxTotalRateLimitPerEpoch,
|
||||
uint16 _minRateLimitPerMembership,
|
||||
uint16 _maxRateLimitPerMembership,
|
||||
uint32 _billingPeriod,
|
||||
uint32 _gracePeriod
|
||||
)
|
||||
public
|
||||
initializer
|
||||
{
|
||||
__Ownable_init();
|
||||
__UUPSUpgradeable_init();
|
||||
MAX_MESSAGE_LIMIT = maxMessageLimit;
|
||||
__Membership_init(
|
||||
_priceCalculator,
|
||||
_maxTotalRateLimitPerEpoch,
|
||||
_minRateLimitPerMembership,
|
||||
_maxRateLimitPerMembership,
|
||||
_billingPeriod,
|
||||
_gracePeriod
|
||||
);
|
||||
|
||||
SET_SIZE = uint32(1 << DEPTH);
|
||||
deployedBlockNumber = uint32(block.number);
|
||||
LazyIMT.init(imtData, DEPTH);
|
||||
@ -99,13 +103,6 @@ contract WakuRlnV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable {
|
||||
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
|
||||
@ -117,9 +114,9 @@ contract WakuRlnV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable {
|
||||
/// @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];
|
||||
// we cannot call indexToCommitment for 0 index if the member doesn't exist
|
||||
if (member.userMessageLimit == 0) {
|
||||
MembershipInfo memory member = members[idCommitment];
|
||||
// we cannot call indexToCommitment if the member doesn't exist
|
||||
if (member.holder == address(0)) {
|
||||
return (0, 0, 0);
|
||||
}
|
||||
return (member.userMessageLimit, member.index, indexToCommitment(member.index));
|
||||
@ -133,34 +130,46 @@ contract WakuRlnV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable {
|
||||
return rateCommitment != 0;
|
||||
}
|
||||
|
||||
/// Allows a user to register as a member
|
||||
/// @notice Allows a user to register as a member
|
||||
/// @param idCommitment The idCommitment of the member
|
||||
/// @param userMessageLimit The message limit of the member
|
||||
/// @param numberOfPeriods The number of periods to acquire
|
||||
function register(
|
||||
uint256 idCommitment,
|
||||
uint32 userMessageLimit
|
||||
uint32 userMessageLimit,
|
||||
uint32 numberOfPeriods // TODO: is there a maximum number of periods allowed?
|
||||
)
|
||||
external
|
||||
payable
|
||||
onlyValidIdCommitment(idCommitment)
|
||||
onlyValidUserMessageLimit(userMessageLimit)
|
||||
{
|
||||
_register(idCommitment, userMessageLimit);
|
||||
if (memberExists(idCommitment)) revert DuplicateIdCommitment();
|
||||
|
||||
uint32 index;
|
||||
bool reusedIndex;
|
||||
(index, reusedIndex) = _acquireMembership(_msgSender(), idCommitment, userMessageLimit, numberOfPeriods);
|
||||
|
||||
_register(idCommitment, userMessageLimit, index, reusedIndex);
|
||||
}
|
||||
|
||||
/// Registers a member
|
||||
/// @dev Registers a member
|
||||
/// @param idCommitment The idCommitment of the member
|
||||
/// @param userMessageLimit The message limit of the member
|
||||
function _register(uint256 idCommitment, uint32 userMessageLimit) internal {
|
||||
if (memberExists(idCommitment)) revert DuplicateIdCommitment();
|
||||
/// @param index Indicates the index in the merkle tree
|
||||
/// @param reusedIndex indicates whether we're inserting a new element in the merkle tree or updating a existing
|
||||
/// leaf
|
||||
function _register(uint256 idCommitment, uint32 userMessageLimit, uint32 index, bool reusedIndex) internal {
|
||||
if (commitmentIndex >= SET_SIZE) revert FullTree();
|
||||
|
||||
uint256 rateCommitment = PoseidonT3.hash([idCommitment, userMessageLimit]);
|
||||
MembershipInfo memory member = MembershipInfo({ userMessageLimit: userMessageLimit, index: commitmentIndex });
|
||||
LazyIMT.insert(imtData, rateCommitment);
|
||||
memberInfo[idCommitment] = member;
|
||||
if (reusedIndex) {
|
||||
LazyIMT.update(imtData, rateCommitment, index);
|
||||
} else {
|
||||
LazyIMT.insert(imtData, rateCommitment);
|
||||
commitmentIndex += 1;
|
||||
}
|
||||
|
||||
emit MemberRegistered(rateCommitment, commitmentIndex);
|
||||
commitmentIndex += 1;
|
||||
emit MemberRegistered(rateCommitment, index);
|
||||
}
|
||||
|
||||
/// @notice Returns the commitments of a range of members
|
||||
@ -195,4 +204,70 @@ contract WakuRlnV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable {
|
||||
}
|
||||
return castedProof;
|
||||
}
|
||||
|
||||
/// @notice Extend a membership expiration date. Memberships must be on grace period
|
||||
/// @param idCommitments list of idcommitments
|
||||
function extend(uint256[] calldata idCommitments) external {
|
||||
for (uint256 i = 0; i < idCommitments.length; i++) {
|
||||
uint256 idCommitment = idCommitments[i];
|
||||
_extendMembership(_msgSender(), idCommitment);
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Remove expired memberships or owned memberships in grace period.
|
||||
/// The user can determine offchain which expired memberships slots
|
||||
/// are available, and proceed to free them.
|
||||
/// This is also used to erase memberships in grace period if they're
|
||||
/// held by the sender. The sender can then withdraw the tokens.
|
||||
/// @param idCommitments list of idcommitments of the memberships
|
||||
function eraseMemberships(uint256[] calldata idCommitments) external {
|
||||
for (uint256 i = 0; i < idCommitments.length; i++) {
|
||||
uint256 idCommitment = idCommitments[i];
|
||||
MembershipInfo memory mdetails = members[idCommitment];
|
||||
_eraseMembership(_msgSender(), idCommitment, mdetails);
|
||||
LazyIMT.update(imtData, 0, mdetails.index);
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Withdraw any available balance in tokens after a membership is erased.
|
||||
/// @param token The address of the token to withdraw. Use 0x000...000 to withdraw ETH
|
||||
function withdraw(address token) external {
|
||||
_withdraw(_msgSender(), token);
|
||||
}
|
||||
|
||||
/// @notice Set the address of the price calculator
|
||||
/// @param _priceCalculator new price calculator address
|
||||
function setPriceCalculator(address _priceCalculator) external onlyOwner {
|
||||
priceCalculator = IPriceCalculator(_priceCalculator);
|
||||
}
|
||||
|
||||
/// @notice Set the maximum total rate limit of all memberships in the tree
|
||||
/// @param _maxTotalRateLimitPerEpoch new value
|
||||
function setMaxTotalRateLimitPerEpoch(uint32 _maxTotalRateLimitPerEpoch) external onlyOwner {
|
||||
maxTotalRateLimitPerEpoch = _maxTotalRateLimitPerEpoch;
|
||||
}
|
||||
|
||||
/// @notice Set the maximum rate limit of one membership
|
||||
/// @param _maxRateLimitPerMembership new value
|
||||
function setMaxRateLimitPerMembership(uint32 _maxRateLimitPerMembership) external onlyOwner {
|
||||
maxRateLimitPerMembership = _maxRateLimitPerMembership;
|
||||
}
|
||||
|
||||
/// @notice Set the minimum rate limit of one membership
|
||||
/// @param _minRateLimitPerMembership new value
|
||||
function setMinRateLimitPerMembership(uint32 _minRateLimitPerMembership) external onlyOwner {
|
||||
minRateLimitPerMembership = _minRateLimitPerMembership;
|
||||
}
|
||||
|
||||
/// @notice Set the membership billing period
|
||||
/// @param _billingPeriod new value
|
||||
function setBillingPeriod(uint32 _billingPeriod) external onlyOwner {
|
||||
billingPeriod = _billingPeriod;
|
||||
}
|
||||
|
||||
/// @notice Set the membership grace period
|
||||
/// @param _gracePeriod new value
|
||||
function setGracePeriod(uint32 _gracePeriod) external onlyOwner {
|
||||
gracePeriod = _gracePeriod;
|
||||
}
|
||||
}
|
||||
|
||||
12
test/TestToken.sol
Normal file
12
test/TestToken.sol
Normal file
@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity >=0.8.19 <0.9.0;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
|
||||
contract TestToken is ERC20 {
|
||||
constructor() ERC20("TestToken", "TTT") { }
|
||||
|
||||
function mint(address to, uint256 amount) public {
|
||||
_mint(to, amount);
|
||||
}
|
||||
}
|
||||
@ -5,33 +5,47 @@ import { Test } from "forge-std/Test.sol";
|
||||
import { Deploy } from "../script/Deploy.s.sol";
|
||||
import { DeploymentConfig } from "../script/DeploymentConfig.s.sol";
|
||||
import "../src/WakuRlnV2.sol"; // solhint-disable-line
|
||||
import "../src/Membership.sol"; // solhint-disable-line
|
||||
import "../src/LinearPriceCalculator.sol"; // solhint-disable-line
|
||||
import "./TestToken.sol";
|
||||
import { PoseidonT3 } from "poseidon-solidity/PoseidonT3.sol";
|
||||
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
||||
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
||||
import "forge-std/console.sol";
|
||||
|
||||
contract WakuRlnV2Test is Test {
|
||||
WakuRlnV2 internal w;
|
||||
address internal impl;
|
||||
DeploymentConfig internal deploymentConfig;
|
||||
TestToken internal token;
|
||||
|
||||
address internal deployer;
|
||||
|
||||
function setUp() public virtual {
|
||||
Deploy deployment = new Deploy();
|
||||
(w, impl) = deployment.run();
|
||||
|
||||
token = new TestToken();
|
||||
}
|
||||
|
||||
function test__ValidRegistration__kats() external {
|
||||
vm.pauseGasMetering();
|
||||
// Merkle tree leaves are calculated using 2 as rateLimit
|
||||
vm.prank(w.owner());
|
||||
w.setMinRateLimitPerMembership(2);
|
||||
|
||||
uint256 idCommitment = 2;
|
||||
uint32 userMessageLimit = 2;
|
||||
uint32 numberOfPeriods = 1;
|
||||
(, uint256 price) = w.priceCalculator().calculate(userMessageLimit, numberOfPeriods);
|
||||
vm.resumeGasMetering();
|
||||
w.register(idCommitment, userMessageLimit);
|
||||
w.register{ value: price }(idCommitment, userMessageLimit, numberOfPeriods);
|
||||
vm.pauseGasMetering();
|
||||
assertEq(w.commitmentIndex(), 1);
|
||||
assertEq(w.memberExists(idCommitment), true);
|
||||
(uint32 fetchedUserMessageLimit, uint32 index) = w.memberInfo(idCommitment);
|
||||
(,,,,,, uint32 fetchedUserMessageLimit, uint32 index, address holder,) = w.members(idCommitment);
|
||||
assertEq(fetchedUserMessageLimit, userMessageLimit);
|
||||
assertEq(holder, address(this));
|
||||
assertEq(index, 0);
|
||||
// kats from zerokit
|
||||
uint256 rateCommitment =
|
||||
@ -74,11 +88,19 @@ contract WakuRlnV2Test is Test {
|
||||
vm.resumeGasMetering();
|
||||
}
|
||||
|
||||
function test__ValidRegistration(uint256 idCommitment, uint32 userMessageLimit) external {
|
||||
vm.assume(w.isValidCommitment(idCommitment) && w.isValidUserMessageLimit(userMessageLimit));
|
||||
function test__ValidRegistration(uint32 userMessageLimit, uint32 numberOfPeriods) external {
|
||||
vm.pauseGasMetering();
|
||||
uint256 idCommitment = 2;
|
||||
vm.assume(numberOfPeriods > 0 && numberOfPeriods < 100);
|
||||
(, uint256 price) = w.priceCalculator().calculate(userMessageLimit, numberOfPeriods);
|
||||
uint256 minUserMessageLimit = w.minRateLimitPerMembership();
|
||||
uint256 maxUserMessageLimit = w.maxRateLimitPerMembership();
|
||||
vm.assume(userMessageLimit >= minUserMessageLimit && userMessageLimit <= maxUserMessageLimit);
|
||||
vm.assume(w.isValidUserMessageLimit(userMessageLimit));
|
||||
vm.resumeGasMetering();
|
||||
|
||||
assertEq(w.memberExists(idCommitment), false);
|
||||
w.register(idCommitment, userMessageLimit);
|
||||
w.register{ value: price }(idCommitment, userMessageLimit, numberOfPeriods);
|
||||
uint256 rateCommitment = PoseidonT3.hash([idCommitment, userMessageLimit]);
|
||||
|
||||
(uint32 fetchedUserMessageLimit, uint32 index, uint256 fetchedRateCommitment) =
|
||||
@ -86,6 +108,95 @@ contract WakuRlnV2Test is Test {
|
||||
assertEq(fetchedUserMessageLimit, userMessageLimit);
|
||||
assertEq(index, 0);
|
||||
assertEq(fetchedRateCommitment, rateCommitment);
|
||||
|
||||
assertEq(address(w).balance, price);
|
||||
assertEq(w.totalRateLimitPerEpoch(), userMessageLimit);
|
||||
}
|
||||
|
||||
function test__InsertionNormalOrder(uint32 idCommitmentsLength) external {
|
||||
vm.assume(idCommitmentsLength > 0 && idCommitmentsLength <= 50);
|
||||
|
||||
uint32 userMessageLimit = w.minRateLimitPerMembership();
|
||||
uint32 numberOfPeriods = 1;
|
||||
(, uint256 price) = w.priceCalculator().calculate(userMessageLimit, 1);
|
||||
|
||||
// Register some commitments
|
||||
for (uint256 i = 0; i < idCommitmentsLength; i++) {
|
||||
uint256 idCommitment = i + 1;
|
||||
w.register{ value: price }(idCommitment, userMessageLimit, numberOfPeriods);
|
||||
(uint256 prev, uint256 next,,,,,,,,) = w.members(idCommitment);
|
||||
// new membership will always be the tail
|
||||
assertEq(next, 0);
|
||||
assertEq(w.tail(), idCommitment);
|
||||
// current membership prevLink will always point to previous membership
|
||||
assertEq(prev, idCommitment - 1);
|
||||
}
|
||||
assertEq(w.head(), 1);
|
||||
assertEq(w.tail(), idCommitmentsLength);
|
||||
|
||||
// Ensure that prev and next are chained correctly
|
||||
for (uint256 i = 0; i < idCommitmentsLength; i++) {
|
||||
uint256 idCommitment = i + 1;
|
||||
(uint256 prev, uint256 next,,,,,,,,) = w.members(idCommitment);
|
||||
|
||||
assertEq(prev, idCommitment - 1);
|
||||
if (i == idCommitmentsLength - 1) {
|
||||
assertEq(next, 0);
|
||||
} else {
|
||||
assertEq(next, idCommitment + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function test__LinearPriceCalculation(uint32 userMessageLimit, uint32 numberOfPeriods) external view {
|
||||
IPriceCalculator priceCalculator = w.priceCalculator();
|
||||
uint256 pricePerMessagePerPeriod = LinearPriceCalculator(address(priceCalculator)).pricePerMessagePerPeriod();
|
||||
assertNotEq(pricePerMessagePerPeriod, 0);
|
||||
uint256 expectedPrice = uint256(userMessageLimit) * uint256(numberOfPeriods) * pricePerMessagePerPeriod;
|
||||
(, uint256 price) = w.priceCalculator().calculate(userMessageLimit, numberOfPeriods);
|
||||
assertEq(price, expectedPrice);
|
||||
}
|
||||
|
||||
function test__RegistrationWithTokens(
|
||||
uint256 idCommitment,
|
||||
uint32 userMessageLimit,
|
||||
uint32 numberOfPeriods
|
||||
)
|
||||
external
|
||||
{
|
||||
vm.pauseGasMetering();
|
||||
vm.assume(numberOfPeriods > 0);
|
||||
LinearPriceCalculator priceCalculator = LinearPriceCalculator(address(w.priceCalculator()));
|
||||
vm.prank(priceCalculator.owner());
|
||||
priceCalculator.setTokenAndPrice(address(token), 5 wei);
|
||||
(, uint256 price) = w.priceCalculator().calculate(userMessageLimit, numberOfPeriods);
|
||||
uint256 minUserMessageLimit = w.minRateLimitPerMembership();
|
||||
uint256 maxUserMessageLimit = w.maxRateLimitPerMembership();
|
||||
vm.assume(userMessageLimit >= minUserMessageLimit && userMessageLimit <= maxUserMessageLimit);
|
||||
vm.assume(w.isValidCommitment(idCommitment) && w.isValidUserMessageLimit(userMessageLimit));
|
||||
vm.resumeGasMetering();
|
||||
|
||||
token.mint(address(this), price);
|
||||
token.approve(address(w), price);
|
||||
w.register(idCommitment, userMessageLimit, numberOfPeriods);
|
||||
assertEq(token.balanceOf(address(w)), price);
|
||||
assertEq(token.balanceOf(address(this)), 0);
|
||||
}
|
||||
|
||||
function test__InvalidETHAmount(uint256 idCommitment, uint32 userMessageLimit, uint32 numberOfPeriods) external {
|
||||
vm.pauseGasMetering();
|
||||
vm.assume(numberOfPeriods > 0 && numberOfPeriods < 100);
|
||||
uint256 minUserMessageLimit = w.minRateLimitPerMembership();
|
||||
uint256 maxUserMessageLimit = w.maxRateLimitPerMembership();
|
||||
vm.assume(userMessageLimit >= minUserMessageLimit && userMessageLimit <= maxUserMessageLimit);
|
||||
vm.assume(w.isValidCommitment(idCommitment) && w.isValidUserMessageLimit(userMessageLimit));
|
||||
(, uint256 price) = w.priceCalculator().calculate(userMessageLimit, numberOfPeriods);
|
||||
vm.resumeGasMetering();
|
||||
|
||||
vm.expectRevert(abi.encodeWithSelector(IncorrectAmount.selector));
|
||||
w.register{ value: price - 1 }(idCommitment, userMessageLimit, numberOfPeriods);
|
||||
vm.expectRevert(abi.encodeWithSelector(IncorrectAmount.selector));
|
||||
w.register{ value: price + 1 }(idCommitment, userMessageLimit, numberOfPeriods);
|
||||
}
|
||||
|
||||
function test__IdCommitmentToMetadata__DoesntExist() external view {
|
||||
@ -97,43 +208,398 @@ contract WakuRlnV2Test is Test {
|
||||
}
|
||||
|
||||
function test__InvalidRegistration__InvalidIdCommitment__Zero() external {
|
||||
vm.pauseGasMetering();
|
||||
uint256 idCommitment = 0;
|
||||
uint32 userMessageLimit = 2;
|
||||
uint32 numberOfPeriods = 2;
|
||||
(, uint256 price) = w.priceCalculator().calculate(userMessageLimit, numberOfPeriods);
|
||||
vm.resumeGasMetering();
|
||||
|
||||
vm.expectRevert(abi.encodeWithSelector(InvalidIdCommitment.selector, 0));
|
||||
w.register(idCommitment, userMessageLimit);
|
||||
w.register{ value: price }(idCommitment, userMessageLimit, numberOfPeriods);
|
||||
}
|
||||
|
||||
function test__InvalidRegistration__InvalidIdCommitment__LargerThanField() external {
|
||||
vm.pauseGasMetering();
|
||||
uint32 userMessageLimit = 20;
|
||||
uint32 numberOfPeriods = 3;
|
||||
(, uint256 price) = w.priceCalculator().calculate(userMessageLimit, numberOfPeriods);
|
||||
vm.resumeGasMetering();
|
||||
|
||||
uint256 idCommitment = w.Q() + 1;
|
||||
uint32 userMessageLimit = 2;
|
||||
vm.expectRevert(abi.encodeWithSelector(InvalidIdCommitment.selector, idCommitment));
|
||||
w.register(idCommitment, userMessageLimit);
|
||||
w.register{ value: price }(idCommitment, userMessageLimit, numberOfPeriods);
|
||||
}
|
||||
|
||||
function test__InvalidRegistration__InvalidUserMessageLimit__Zero() external {
|
||||
function test__InvalidRegistration__InvalidUserMessageLimit__MinMax() external {
|
||||
uint256 idCommitment = 2;
|
||||
uint32 userMessageLimit = 0;
|
||||
vm.expectRevert(abi.encodeWithSelector(InvalidUserMessageLimit.selector, 0));
|
||||
w.register(idCommitment, userMessageLimit);
|
||||
|
||||
uint32 invalidMin = w.minRateLimitPerMembership() - 1;
|
||||
uint32 invalidMax = w.maxRateLimitPerMembership() + 1;
|
||||
|
||||
vm.expectRevert(abi.encodeWithSelector(InvalidRateLimit.selector));
|
||||
w.register(idCommitment, invalidMin, 1);
|
||||
|
||||
vm.expectRevert(abi.encodeWithSelector(InvalidRateLimit.selector));
|
||||
w.register(idCommitment, invalidMax, 1);
|
||||
}
|
||||
|
||||
function test__InvalidRegistration__InvalidUserMessageLimit__LargerThanMax() external {
|
||||
function test__ValidRegistrationExtend(uint32 userMessageLimit, uint32 numberOfPeriods) external {
|
||||
vm.pauseGasMetering();
|
||||
uint256 idCommitment = 2;
|
||||
uint32 userMessageLimit = w.MAX_MESSAGE_LIMIT() + 1;
|
||||
vm.expectRevert(abi.encodeWithSelector(InvalidUserMessageLimit.selector, userMessageLimit));
|
||||
w.register(idCommitment, userMessageLimit);
|
||||
vm.assume(numberOfPeriods > 0 && numberOfPeriods < 100);
|
||||
(, uint256 price) = w.priceCalculator().calculate(userMessageLimit, numberOfPeriods);
|
||||
vm.assume(
|
||||
userMessageLimit >= w.minRateLimitPerMembership() && userMessageLimit <= w.maxRateLimitPerMembership()
|
||||
);
|
||||
vm.assume(w.isValidUserMessageLimit(userMessageLimit));
|
||||
vm.resumeGasMetering();
|
||||
|
||||
w.register{ value: price }(idCommitment, userMessageLimit, numberOfPeriods);
|
||||
(,,,, uint256 gracePeriodStartDate,,,,,) = w.members(idCommitment);
|
||||
|
||||
assertFalse(w.isGracePeriod(idCommitment));
|
||||
assertFalse(w.isExpired(idCommitment));
|
||||
|
||||
vm.warp(gracePeriodStartDate);
|
||||
|
||||
assertTrue(w.isGracePeriod(idCommitment));
|
||||
assertFalse(w.isExpired(idCommitment));
|
||||
|
||||
// Registering other memberships just to check linkage is correct
|
||||
for (uint256 i = 1; i < 5; i++) {
|
||||
w.register{ value: price }(idCommitment + i, userMessageLimit, numberOfPeriods);
|
||||
}
|
||||
|
||||
assertEq(w.head(), idCommitment);
|
||||
|
||||
uint256[] memory commitmentsToExtend = new uint256[](1);
|
||||
commitmentsToExtend[0] = idCommitment;
|
||||
|
||||
// Attempt to extend the membership (but it is not owned by us)
|
||||
address randomAddress = vm.addr(block.timestamp);
|
||||
vm.prank(randomAddress);
|
||||
vm.expectRevert(abi.encodeWithSelector(NotHolder.selector, commitmentsToExtend[0]));
|
||||
w.extend(commitmentsToExtend);
|
||||
|
||||
// Attempt to extend the membership (but now we are the owner)
|
||||
w.extend(commitmentsToExtend);
|
||||
|
||||
(,,,, uint256 newGracePeriodStartDate,,,,,) = w.members(idCommitment);
|
||||
|
||||
assertEq(block.timestamp + (uint256(w.billingPeriod()) * uint256(numberOfPeriods)), newGracePeriodStartDate);
|
||||
assertFalse(w.isGracePeriod(idCommitment));
|
||||
assertFalse(w.isExpired(idCommitment));
|
||||
|
||||
// Verify list order is correct
|
||||
assertEq(w.tail(), idCommitment);
|
||||
assertEq(w.head(), idCommitment + 1);
|
||||
|
||||
// Ensure that prev and next are chained correctly
|
||||
for (uint256 i = 0; i < 5; i++) {
|
||||
uint256 currIdCommitment = idCommitment + i;
|
||||
(uint256 prev, uint256 next,,,,,,,,) = w.members(currIdCommitment);
|
||||
console.log("idCommitment: %s - prev: %s - next: %s", currIdCommitment, prev, next);
|
||||
if (i == 0) {
|
||||
// Verifying links of extended idCommitment
|
||||
assertEq(next, 0);
|
||||
assertEq(prev, idCommitment + 4);
|
||||
} else if (i == 1) {
|
||||
// The second element in the chain became the oldest
|
||||
assertEq(next, currIdCommitment + 1);
|
||||
assertEq(prev, 0);
|
||||
} else if (i == 4) {
|
||||
assertEq(prev, currIdCommitment - 1);
|
||||
assertEq(next, idCommitment);
|
||||
} else {
|
||||
// The rest of the elements maintain their order
|
||||
assertEq(prev, currIdCommitment - 1);
|
||||
assertEq(next, currIdCommitment + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: should it be possible to extend expired memberships?
|
||||
|
||||
// Attempt to extend a non grace period membership
|
||||
commitmentsToExtend[0] = idCommitment + 1;
|
||||
vm.expectRevert(abi.encodeWithSelector(NotInGracePeriod.selector, commitmentsToExtend[0]));
|
||||
w.extend(commitmentsToExtend);
|
||||
}
|
||||
|
||||
function test__ValidRegistrationExpiry(uint32 userMessageLimit, uint32 numberOfPeriods) external {
|
||||
vm.pauseGasMetering();
|
||||
uint256 idCommitment = 2;
|
||||
vm.assume(numberOfPeriods > 0 && numberOfPeriods < 100);
|
||||
(, uint256 price) = w.priceCalculator().calculate(userMessageLimit, numberOfPeriods);
|
||||
vm.assume(
|
||||
userMessageLimit >= w.minRateLimitPerMembership() && userMessageLimit <= w.maxRateLimitPerMembership()
|
||||
);
|
||||
vm.assume(w.isValidUserMessageLimit(userMessageLimit));
|
||||
vm.resumeGasMetering();
|
||||
|
||||
w.register{ value: price }(idCommitment, userMessageLimit, numberOfPeriods);
|
||||
|
||||
(,,, uint32 fetchedNumberOfPeriods, uint256 fetchedGracePeriodStartDate, uint32 fetchedGracePeriod,,,,) =
|
||||
w.members(idCommitment);
|
||||
|
||||
uint256 expectedExpirationDate =
|
||||
fetchedGracePeriodStartDate + (uint256(fetchedGracePeriod) * uint256(fetchedNumberOfPeriods));
|
||||
uint256 expirationDate = w.expirationDate(idCommitment);
|
||||
|
||||
assertEq(expectedExpirationDate, expirationDate);
|
||||
|
||||
vm.warp(expirationDate + 1);
|
||||
|
||||
assertFalse(w.isGracePeriod(idCommitment));
|
||||
assertTrue(w.isExpired(idCommitment));
|
||||
|
||||
// Registering other memberships just to check linkage is correct
|
||||
for (uint256 i = 1; i <= 5; i++) {
|
||||
w.register{ value: price }(idCommitment + i, userMessageLimit, numberOfPeriods);
|
||||
}
|
||||
|
||||
assertEq(w.head(), idCommitment);
|
||||
assertEq(w.tail(), idCommitment + 5);
|
||||
}
|
||||
|
||||
function test__RegistrationWhenMaxRateLimitIsReached() external {
|
||||
// TODO: implement
|
||||
// TODO: validate elements are chained correctly
|
||||
// TODO: validate reuse of index
|
||||
}
|
||||
|
||||
function test__RegistrationWhenMaxRateLimitIsReachedAndSingleExpiredMemberAvailable() external {
|
||||
// TODO: implement
|
||||
// TODO: validate elements are chained correctly
|
||||
// TODO: validate reuse of index
|
||||
// TODO: validate balance
|
||||
}
|
||||
|
||||
function test__RegistrationWhenMaxRateLimitIsReachedAndMultipleExpiredMembersAvailable() external {
|
||||
// TODO: implement
|
||||
// TODO: validate elements are chained correctly
|
||||
// TODO: validate reuse of index
|
||||
// TODO: validate balance
|
||||
}
|
||||
|
||||
function test__RemoveExpiredMemberships(uint32 userMessageLimit, uint32 numberOfPeriods) external {
|
||||
vm.pauseGasMetering();
|
||||
uint256 idCommitment = 2;
|
||||
vm.assume(numberOfPeriods > 0 && numberOfPeriods < 100);
|
||||
(, uint256 price) = w.priceCalculator().calculate(userMessageLimit, numberOfPeriods);
|
||||
vm.assume(
|
||||
userMessageLimit >= w.minRateLimitPerMembership() && userMessageLimit <= w.maxRateLimitPerMembership()
|
||||
);
|
||||
vm.assume(w.isValidUserMessageLimit(userMessageLimit));
|
||||
vm.resumeGasMetering();
|
||||
|
||||
uint256 time = block.timestamp;
|
||||
for (uint256 i = 0; i < 5; i++) {
|
||||
w.register{ value: price }(idCommitment + i, userMessageLimit, numberOfPeriods);
|
||||
time += 100;
|
||||
vm.warp(time);
|
||||
}
|
||||
|
||||
// Expiring the first 3
|
||||
uint256 expirationDate = w.expirationDate(idCommitment + 2);
|
||||
vm.warp(expirationDate + 1);
|
||||
for (uint256 i = 0; i < 5; i++) {
|
||||
if (i <= 2) {
|
||||
assertTrue(w.isExpired(idCommitment + i));
|
||||
} else {
|
||||
assertFalse(w.isExpired(idCommitment + i));
|
||||
}
|
||||
}
|
||||
|
||||
uint256[] memory commitmentsToErase = new uint256[](2);
|
||||
commitmentsToErase[0] = idCommitment + 1;
|
||||
commitmentsToErase[1] = idCommitment + 2;
|
||||
w.eraseMemberships(commitmentsToErase);
|
||||
|
||||
address holder;
|
||||
|
||||
(,,,,,,,, holder,) = w.members(idCommitment + 1);
|
||||
assertEq(holder, address(0));
|
||||
|
||||
(,,,,,,,, holder,) = w.members(idCommitment + 2);
|
||||
assertEq(holder, address(0));
|
||||
|
||||
// Verify list order is correct
|
||||
uint256 prev;
|
||||
uint256 next;
|
||||
(prev, next,,,,,,,,) = w.members(idCommitment);
|
||||
assertEq(prev, 0);
|
||||
assertEq(next, idCommitment + 3);
|
||||
(prev, next,,,,,,,,) = w.members(idCommitment + 3);
|
||||
assertEq(prev, idCommitment);
|
||||
assertEq(next, idCommitment + 4);
|
||||
(prev, next,,,,,,,,) = w.members(idCommitment + 4);
|
||||
assertEq(prev, idCommitment + 3);
|
||||
assertEq(next, 0);
|
||||
assertEq(w.head(), idCommitment);
|
||||
assertEq(w.tail(), idCommitment + 4);
|
||||
|
||||
// Attempting to call erase when some of the commitments can't be erased yet
|
||||
// idCommitment can be erased (in grace period), but idCommitment + 4 is still active
|
||||
(,,,, uint256 gracePeriodStartDate,,,,,) = w.members(idCommitment + 4);
|
||||
vm.warp(gracePeriodStartDate - 1);
|
||||
commitmentsToErase[0] = idCommitment;
|
||||
commitmentsToErase[1] = idCommitment + 4;
|
||||
vm.expectRevert(abi.encodeWithSelector(CantEraseMembership.selector, idCommitment + 4));
|
||||
w.eraseMemberships(commitmentsToErase);
|
||||
}
|
||||
|
||||
function test__RemoveAllExpiredMemberships(uint32 idCommitmentsLength) external {
|
||||
vm.pauseGasMetering();
|
||||
vm.assume(idCommitmentsLength > 1 && idCommitmentsLength <= 100);
|
||||
uint32 userMessageLimit = w.minRateLimitPerMembership();
|
||||
uint32 numberOfPeriods = 5;
|
||||
(, uint256 price) = w.priceCalculator().calculate(userMessageLimit, numberOfPeriods);
|
||||
vm.resumeGasMetering();
|
||||
|
||||
uint256 time = block.timestamp;
|
||||
for (uint256 i = 1; i <= idCommitmentsLength; i++) {
|
||||
w.register{ value: price }(i, userMessageLimit, numberOfPeriods);
|
||||
time += 100;
|
||||
vm.warp(time);
|
||||
}
|
||||
|
||||
uint256 expirationDate = w.expirationDate(idCommitmentsLength);
|
||||
vm.warp(expirationDate + 1);
|
||||
for (uint256 i = 1; i <= 5; i++) {
|
||||
assertTrue(w.isExpired(i));
|
||||
}
|
||||
|
||||
uint256[] memory commitmentsToErase = new uint256[](idCommitmentsLength);
|
||||
for (uint256 i = 0; i < idCommitmentsLength; i++) {
|
||||
commitmentsToErase[i] = i + 1;
|
||||
}
|
||||
w.eraseMemberships(commitmentsToErase);
|
||||
|
||||
// No memberships registered
|
||||
assertEq(w.head(), 0);
|
||||
assertEq(w.tail(), 0);
|
||||
|
||||
for (uint256 i = 10; i <= idCommitmentsLength + 10; i++) {
|
||||
w.register{ value: price }(i, userMessageLimit, numberOfPeriods);
|
||||
assertEq(w.tail(), i);
|
||||
}
|
||||
|
||||
// Verify list order is correct
|
||||
assertEq(w.head(), 10);
|
||||
assertEq(w.tail(), idCommitmentsLength + 10);
|
||||
uint256 prev;
|
||||
uint256 next;
|
||||
(prev, next,,,,,,,,) = w.members(10);
|
||||
assertEq(prev, 0);
|
||||
assertEq(next, 11);
|
||||
(prev, next,,,,,,,,) = w.members(idCommitmentsLength + 10);
|
||||
assertEq(prev, idCommitmentsLength + 9);
|
||||
assertEq(next, 0);
|
||||
}
|
||||
|
||||
function test__WithdrawETH(uint32 userMessageLimit, uint32 numberOfPeriods) external {
|
||||
vm.pauseGasMetering();
|
||||
uint256 idCommitment = 2;
|
||||
vm.assume(numberOfPeriods > 0 && numberOfPeriods < 100);
|
||||
(, uint256 price) = w.priceCalculator().calculate(userMessageLimit, numberOfPeriods);
|
||||
vm.assume(
|
||||
userMessageLimit >= w.minRateLimitPerMembership() && userMessageLimit <= w.maxRateLimitPerMembership()
|
||||
);
|
||||
vm.assume(w.isValidUserMessageLimit(userMessageLimit));
|
||||
vm.resumeGasMetering();
|
||||
|
||||
w.register{ value: price }(idCommitment, userMessageLimit, numberOfPeriods);
|
||||
|
||||
(,,,, uint256 gracePeriodStartDate,,,,,) = w.members(idCommitment);
|
||||
|
||||
vm.warp(gracePeriodStartDate);
|
||||
|
||||
uint256[] memory commitmentsToErase = new uint256[](1);
|
||||
commitmentsToErase[0] = idCommitment;
|
||||
w.eraseMemberships(commitmentsToErase);
|
||||
|
||||
uint256 availableBalance = w.balancesToWithdraw(address(this), address(0));
|
||||
|
||||
assertEq(availableBalance, price);
|
||||
assertEq(address(w).balance, price);
|
||||
|
||||
uint256 balanceBeforeWithdraw = address(this).balance;
|
||||
|
||||
w.withdraw(address(0));
|
||||
|
||||
uint256 balanceAfterWithdraw = address(this).balance;
|
||||
|
||||
availableBalance = w.balancesToWithdraw(address(this), address(0));
|
||||
assertEq(availableBalance, 0);
|
||||
assertEq(address(w).balance, 0);
|
||||
assertEq(balanceBeforeWithdraw + price, balanceAfterWithdraw);
|
||||
}
|
||||
|
||||
function test__WithdrawToken(uint32 userMessageLimit, uint32 numberOfPeriods) external {
|
||||
vm.pauseGasMetering();
|
||||
uint256 idCommitment = 2;
|
||||
LinearPriceCalculator priceCalculator = LinearPriceCalculator(address(w.priceCalculator()));
|
||||
vm.assume(numberOfPeriods > 0 && numberOfPeriods < 100);
|
||||
vm.prank(priceCalculator.owner());
|
||||
priceCalculator.setTokenAndPrice(address(token), 5 wei);
|
||||
(, uint256 price) = w.priceCalculator().calculate(userMessageLimit, numberOfPeriods);
|
||||
token.mint(address(this), price);
|
||||
vm.assume(
|
||||
userMessageLimit >= w.minRateLimitPerMembership() && userMessageLimit <= w.maxRateLimitPerMembership()
|
||||
);
|
||||
vm.assume(w.isValidUserMessageLimit(userMessageLimit));
|
||||
vm.resumeGasMetering();
|
||||
|
||||
token.approve(address(w), price);
|
||||
w.register(idCommitment, userMessageLimit, numberOfPeriods);
|
||||
|
||||
(,,,, uint256 gracePeriodStartDate,,,,,) = w.members(idCommitment);
|
||||
|
||||
vm.warp(gracePeriodStartDate);
|
||||
|
||||
uint256[] memory commitmentsToErase = new uint256[](1);
|
||||
commitmentsToErase[0] = idCommitment;
|
||||
w.eraseMemberships(commitmentsToErase);
|
||||
|
||||
uint256 availableBalance = w.balancesToWithdraw(address(this), address(token));
|
||||
|
||||
assertEq(availableBalance, price);
|
||||
assertEq(token.balanceOf(address(w)), price);
|
||||
|
||||
uint256 balanceBeforeWithdraw = token.balanceOf(address(this));
|
||||
|
||||
w.withdraw(address(token));
|
||||
|
||||
uint256 balanceAfterWithdraw = token.balanceOf(address(this));
|
||||
|
||||
availableBalance = w.balancesToWithdraw(address(this), address(token));
|
||||
assertEq(availableBalance, 0);
|
||||
assertEq(token.balanceOf(address(w)), 0);
|
||||
assertEq(balanceBeforeWithdraw + price, balanceAfterWithdraw);
|
||||
}
|
||||
|
||||
function test__InvalidRegistration__DuplicateIdCommitment() external {
|
||||
vm.pauseGasMetering();
|
||||
uint256 idCommitment = 2;
|
||||
uint32 userMessageLimit = 2;
|
||||
w.register(idCommitment, userMessageLimit);
|
||||
uint32 userMessageLimit = w.minRateLimitPerMembership();
|
||||
uint32 numberOfPeriods = 2;
|
||||
(, uint256 price) = w.priceCalculator().calculate(userMessageLimit, numberOfPeriods);
|
||||
vm.resumeGasMetering();
|
||||
|
||||
w.register{ value: price }(idCommitment, userMessageLimit, numberOfPeriods);
|
||||
vm.expectRevert(DuplicateIdCommitment.selector);
|
||||
w.register(idCommitment, userMessageLimit);
|
||||
w.register{ value: price }(idCommitment, userMessageLimit, numberOfPeriods);
|
||||
}
|
||||
|
||||
// TODO: state has changed due to adding new variables. Update this
|
||||
/*
|
||||
function test__InvalidRegistration__FullTree() external {
|
||||
vm.pauseGasMetering();
|
||||
uint32 userMessageLimit = 2;
|
||||
(, uint256 price) = w.priceCalculator().calculate(userMessageLimit);
|
||||
vm.resumeGasMetering();
|
||||
|
||||
// we progress the tree to the last leaf
|
||||
/*| Name | Type | Slot | Offset | Bytes |
|
||||
|---------------------|-----------------------------------------------------|------|--------|-------|
|
||||
@ -143,13 +609,15 @@ contract WakuRlnV2Test is Test {
|
||||
| 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 commitmentIndex to 4294967295 (1 << 20) (altered)
|
||||
vm.store(address(w), bytes32(uint256(201)), 0x0000000000000000000000000000000000000000ffffffffffffffff00000014);
|
||||
// we set MAX_MESSAGE_LIMIT to 20 (unaltered)
|
||||
// we set SET_SIZE to 4294967295 (1 << 20) (unaltered)
|
||||
// we set commitmentIndex to 4294967295 (1 << 20) (altered)
|
||||
/* vm.store(address(w), bytes32(uint256(201)),
|
||||
0x0000000000000000000000000000000000000000ffffffffffffffff00000014);
|
||||
vm.expectRevert(FullTree.selector);
|
||||
w.register(1, userMessageLimit);
|
||||
w.register{ value: price }(1, userMessageLimit);
|
||||
}
|
||||
*/
|
||||
|
||||
function test__InvalidPaginationQuery__StartIndexGTEndIndex() external {
|
||||
vm.expectRevert(abi.encodeWithSelector(InvalidPaginationQuery.selector, 1, 0));
|
||||
@ -162,9 +630,14 @@ contract WakuRlnV2Test is Test {
|
||||
}
|
||||
|
||||
function test__ValidPaginationQuery__OneElement() external {
|
||||
uint32 userMessageLimit = 2;
|
||||
vm.pauseGasMetering();
|
||||
uint256 idCommitment = 1;
|
||||
w.register(idCommitment, userMessageLimit);
|
||||
uint32 userMessageLimit = w.minRateLimitPerMembership();
|
||||
uint32 numberOfPeriods = 2;
|
||||
(, uint256 price) = w.priceCalculator().calculate(userMessageLimit, numberOfPeriods);
|
||||
vm.resumeGasMetering();
|
||||
|
||||
w.register{ value: price }(idCommitment, userMessageLimit, numberOfPeriods);
|
||||
uint256[] memory commitments = w.getCommitments(0, 0);
|
||||
assertEq(commitments.length, 1);
|
||||
uint256 rateCommitment = PoseidonT3.hash([idCommitment, userMessageLimit]);
|
||||
@ -172,12 +645,14 @@ contract WakuRlnV2Test is Test {
|
||||
}
|
||||
|
||||
function test__ValidPaginationQuery(uint32 idCommitmentsLength) external {
|
||||
vm.assume(idCommitmentsLength > 0 && idCommitmentsLength <= 100);
|
||||
uint32 userMessageLimit = 2;
|
||||
|
||||
vm.pauseGasMetering();
|
||||
vm.assume(idCommitmentsLength > 0 && idCommitmentsLength <= 100);
|
||||
uint32 userMessageLimit = w.minRateLimitPerMembership();
|
||||
uint32 numberOfPeriods = 2;
|
||||
(, uint256 price) = w.priceCalculator().calculate(userMessageLimit, numberOfPeriods);
|
||||
|
||||
for (uint256 i = 0; i < idCommitmentsLength; i++) {
|
||||
w.register(i + 1, userMessageLimit);
|
||||
w.register{ value: price }(i + 1, userMessageLimit, numberOfPeriods);
|
||||
}
|
||||
vm.resumeGasMetering();
|
||||
|
||||
@ -191,7 +666,7 @@ contract WakuRlnV2Test is Test {
|
||||
|
||||
function test__Upgrade() external {
|
||||
address testImpl = address(new WakuRlnV2());
|
||||
bytes memory data = abi.encodeCall(WakuRlnV2.initialize, 20);
|
||||
bytes memory data = abi.encodeCall(WakuRlnV2.initialize, (address(0), 100, 1, 10, 10 minutes, 4 minutes));
|
||||
address proxy = address(new ERC1967Proxy(testImpl, data));
|
||||
|
||||
address newImpl = address(new WakuRlnV2());
|
||||
@ -207,4 +682,6 @@ contract WakuRlnV2Test is Test {
|
||||
);
|
||||
assertEq(fetchedImpl, newImpl);
|
||||
}
|
||||
|
||||
receive() external payable { }
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user