mirror of
https://github.com/logos-messaging/logos-messaging-rlnv2-contract.git
synced 2026-01-02 14:03:07 +00:00
feat: membership (#13)
This commit is contained in:
parent
1c72717bc9
commit
afb8585f62
@ -1,14 +1,24 @@
|
||||
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: 23299)
|
||||
WakuRlnV2Test:test__InvalidPaginationQuery__EndIndexGTnextCommitmentIndex() (gas: 18307)
|
||||
WakuRlnV2Test:test__InvalidPaginationQuery__StartIndexGTEndIndex() (gas: 16131)
|
||||
WakuRlnV2Test:test__InvalidRegistration__DuplicateIdCommitment() (gas: 272654)
|
||||
WakuRlnV2Test:test__InvalidRegistration__FullTree() (gas: 190004)
|
||||
WakuRlnV2Test:test__InvalidRegistration__InvalidIdCommitment__LargerThanField() (gas: 36492)
|
||||
WakuRlnV2Test:test__InvalidRegistration__InvalidIdCommitment__Zero() (gas: 35192)
|
||||
WakuRlnV2Test:test__InvalidRegistration__InvalidUserMessageLimit__MinMax() (gas: 55026)
|
||||
WakuRlnV2Test:test__InvalidTokenAmount(uint256,uint32) (runs: 1006, μ: 158053, ~: 158053)
|
||||
WakuRlnV2Test:test__LinearPriceCalculation(uint32) (runs: 1015, μ: 26026, ~: 26026)
|
||||
WakuRlnV2Test:test__RegistrationWhenMaxRateLimitIsReached() (gas: 527384)
|
||||
WakuRlnV2Test:test__RemoveAllExpiredMemberships(uint32) (runs: 1004, μ: 3577547, ~: 653139)
|
||||
WakuRlnV2Test:test__RemoveExpiredMemberships(uint32) (runs: 1003, μ: 1044941, ~: 1044943)
|
||||
WakuRlnV2Test:test__Upgrade() (gas: 6932864)
|
||||
WakuRlnV2Test:test__ValidPaginationQuery(uint32) (runs: 1005, μ: 227459, ~: 52991)
|
||||
WakuRlnV2Test:test__ValidPaginationQuery__OneElement() (gas: 269528)
|
||||
WakuRlnV2Test:test__ValidRegistration(uint32) (runs: 1003, μ: 275279, ~: 275279)
|
||||
WakuRlnV2Test:test__ValidRegistrationExpiry(uint32) (runs: 1003, μ: 256301, ~: 256301)
|
||||
WakuRlnV2Test:test__ValidRegistrationExtend(uint32) (runs: 1003, μ: 474309, ~: 474309)
|
||||
WakuRlnV2Test:test__ValidRegistrationExtendSingleMembership(uint32) (runs: 1003, μ: 263787, ~: 263787)
|
||||
WakuRlnV2Test:test__ValidRegistrationWithEraseList() (gas: 1380002)
|
||||
WakuRlnV2Test:test__ValidRegistration__kats() (gas: 245878)
|
||||
WakuRlnV2Test:test__WithdrawToken(uint32) (runs: 1003, μ: 260362, ~: 260364)
|
||||
WakuRlnV2Test:test__indexReuse_eraseMemberships(uint32) (runs: 1004, μ: 2377343, ~: 975838)
|
||||
@ -69,7 +69,14 @@ $ forge coverage
|
||||
#### Deploy to Anvil:
|
||||
|
||||
```sh
|
||||
$ forge script script/Deploy.s.sol --broadcast --fork-url http://localhost:8545
|
||||
$ TOKEN_ADDRESS=0x1122334455667788990011223344556677889900 forge script script/Deploy.s.sol --broadcast --rpc-url localhost --tc Deploy
|
||||
```
|
||||
|
||||
Replace the `TOKEN_ADDRESS` value by a token address you have deployed on anvil. A `TestToken` is available in
|
||||
`test/TestToken.sol` and can be deployed with
|
||||
|
||||
```sh
|
||||
forge script test/TestToken.sol --broadcast --rpc-url localhost --tc TestTokenFactory
|
||||
```
|
||||
|
||||
For this script to work, you need to have a `MNEMONIC` environment variable set to a valid
|
||||
|
||||
2453
pnpm-lock.yaml
generated
2453
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -2,21 +2,38 @@
|
||||
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) {
|
||||
address _token = _getTokenAddress();
|
||||
return deploy(_token);
|
||||
}
|
||||
|
||||
function deploy(address _token) public returns (WakuRlnV2 w, address impl) {
|
||||
address priceCalcAddr = address(new LinearPriceCalculator(_token, 0.05 ether));
|
||||
impl = address(new WakuRlnV2());
|
||||
bytes memory data = abi.encodeCall(WakuRlnV2.initialize, 100);
|
||||
bytes memory data = abi.encodeCall(WakuRlnV2.initialize, (priceCalcAddr, 160_000, 20, 600, 180 days, 30 days));
|
||||
address proxy = address(new ERC1967Proxy(impl, data));
|
||||
w = WakuRlnV2(proxy);
|
||||
}
|
||||
|
||||
function _getTokenAddress() internal view returns (address) {
|
||||
try vm.envAddress("TOKEN_ADDRESS") returns (address passedAddress) {
|
||||
return passedAddress;
|
||||
} catch {
|
||||
if (block.chainid == 1) {
|
||||
return 0x6B175474E89094C44Da98b954EedeAC495271d0F; // DAI address on mainnet
|
||||
} else {
|
||||
revert("no TOKEN_ADDRESS was specified");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contract DeployLibs is BaseScript {
|
||||
|
||||
10
src/IPriceCalculator.sol
Normal file
10
src/IPriceCalculator.sol
Normal file
@ -0,0 +1,10 @@
|
||||
// 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
|
||||
/// @return address of the erc20 token
|
||||
/// @return uint price to pay for acquiring the specified `_rateLimit`
|
||||
function calculate(uint32 _rateLimit) external view returns (address, uint256);
|
||||
}
|
||||
45
src/LinearPriceCalculator.sol
Normal file
45
src/LinearPriceCalculator.sol
Normal file
@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.24;
|
||||
|
||||
import { Ownable } from "openzeppelin-contracts/contracts/access/Ownable.sol";
|
||||
import { IPriceCalculator } from "./IPriceCalculator.sol";
|
||||
|
||||
/// Address 0x0000...0000 was used instead of an ERC20 token address
|
||||
error OnlyTokensAllowed();
|
||||
|
||||
/// @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 public token;
|
||||
|
||||
/// @notice The price per message per epoch
|
||||
uint256 public pricePerMessagePerEpoch;
|
||||
|
||||
constructor(address _token, uint256 _pricePerMessagePerEpoch) Ownable() {
|
||||
_setTokenAndPrice(_token, _pricePerMessagePerEpoch);
|
||||
}
|
||||
|
||||
/// Set accepted token and price per message per epoch per period
|
||||
/// @param _token The token accepted by RLN membership management
|
||||
/// @param _pricePerMessagePerEpoch Price per message per epoch
|
||||
function setTokenAndPrice(address _token, uint256 _pricePerMessagePerEpoch) external onlyOwner {
|
||||
_setTokenAndPrice(_token, _pricePerMessagePerEpoch);
|
||||
}
|
||||
|
||||
/// Set accepted token and price per message per epoch per period
|
||||
/// @param _token The token accepted by RLN membership management
|
||||
/// @param _pricePerMessagePerEpoch Price per message per epoch
|
||||
function _setTokenAndPrice(address _token, uint256 _pricePerMessagePerEpoch) internal {
|
||||
if (_token == address(0)) revert OnlyTokensAllowed();
|
||||
token = _token;
|
||||
pricePerMessagePerEpoch = _pricePerMessagePerEpoch;
|
||||
}
|
||||
|
||||
/// Returns the token and price to pay in `token` for some `_rateLimit`
|
||||
/// @param _rateLimit the rate limit the user wants to acquire
|
||||
/// @return address of the erc20 token
|
||||
/// @return uint price to pay for acquiring the specified `_rateLimit`
|
||||
function calculate(uint32 _rateLimit) external view returns (address, uint256) {
|
||||
return (token, uint256(_rateLimit) * pricePerMessagePerEpoch);
|
||||
}
|
||||
}
|
||||
360
src/Membership.sol
Normal file
360
src/Membership.sol
Normal file
@ -0,0 +1,360 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.24;
|
||||
|
||||
import { IPriceCalculator } from "./IPriceCalculator.sol";
|
||||
import { IERC20 } from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
import { SafeERC20 } from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
||||
|
||||
// The rate limit is outside the expected limits
|
||||
error InvalidMembershipRateLimit();
|
||||
|
||||
// Cannot acquire the rate limit for a new membership due to exceeding the expected limits
|
||||
// even after attempting to erase expired memberships
|
||||
error CannotExceedMaxTotalRateLimit();
|
||||
|
||||
// The membership is not in its grace period (cannot be extended)
|
||||
error CannotExtendNonGracePeriodMembership(uint256 idCommitment);
|
||||
|
||||
// The sender is not the holder of this membership (cannot extend)
|
||||
error NonHolderCannotExtend(uint256 idCommitment);
|
||||
|
||||
// The membership is still active (cannot be erased)
|
||||
error CannotEraseActiveMembership(uint256 idCommitment);
|
||||
|
||||
// The sender is not the holder of this membership (cannot erase)
|
||||
error NonHolderCannotEraseGracePeriodMembership(uint256 idCommitment);
|
||||
|
||||
// The membership does not exist
|
||||
error MembershipDoesNotExist(uint256 idCommitment);
|
||||
|
||||
abstract contract MembershipUpgradeable is Initializable {
|
||||
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 membership set (messages per epoch)
|
||||
uint32 public maxTotalRateLimit;
|
||||
|
||||
/// @notice Maximum rate limit of one membership
|
||||
uint32 public maxMembershipRateLimit;
|
||||
|
||||
/// @notice Minimum rate limit of one membership
|
||||
uint32 public minMembershipRateLimit;
|
||||
|
||||
/// @notice Membership active period duration (A in the spec)
|
||||
uint32 public activeDurationForNewMemberships;
|
||||
|
||||
/// @notice Membership grace period duration (G in the spec)
|
||||
uint32 public gracePeriodDurationForNewMemberships;
|
||||
|
||||
/// @notice Deposits available for withdrawal
|
||||
/// Note: amount of deposits unavailable for withdrawal are stored in MembershipInfo elements.
|
||||
mapping(address holder => mapping(address token => uint256 balance)) public depositsToWithdraw;
|
||||
|
||||
/// @notice Current total rate limit of all memberships in the membership set (messages per epoch)
|
||||
uint256 public currentTotalRateLimit;
|
||||
|
||||
/// @notice List of memberships in the membership set
|
||||
mapping(uint256 idCommitment => MembershipInfo membership) public memberships;
|
||||
|
||||
/// @notice The index in the membership set for the next membership to be registered
|
||||
uint32 public nextFreeIndex;
|
||||
|
||||
/// @notice Indices of erased memberships that can be reused for new registrations
|
||||
uint32[] public indicesOfLazilyErasedMemberships;
|
||||
|
||||
struct MembershipInfo {
|
||||
/// @notice the deposit amount (in tokens) to register this membership
|
||||
uint256 depositAmount;
|
||||
/// @notice the duration of the active period of this membership
|
||||
uint32 activeDuration;
|
||||
/// @notice the start of the grace period (= the end of the active period)
|
||||
uint256 gracePeriodStartTimestamp;
|
||||
/// @notice the duration of the grace period of this membership
|
||||
uint32 gracePeriodDuration;
|
||||
/// @notice the membership rate limit
|
||||
uint32 rateLimit;
|
||||
/// @notice the index of the membership in the membership set
|
||||
uint32 index;
|
||||
/// @notice the address of the holder of this membership
|
||||
address holder;
|
||||
/// @notice the token used to make the deposit to register this membership
|
||||
address token;
|
||||
}
|
||||
|
||||
/// Emitted when a new membership is added to the membership set
|
||||
/// @param idCommitment the idCommitment of the membership
|
||||
/// @param membershipRateLimit the rate limit of this membership
|
||||
/// @param index The index of the membership in the membership set
|
||||
event MembershipRegistered(uint256 idCommitment, uint256 membershipRateLimit, uint32 index);
|
||||
|
||||
/// @notice Emitted when a membership is expired (exceeded its grace period and not extended)
|
||||
/// @param idCommitment the idCommitment of the membership
|
||||
/// @param membershipRateLimit the rate limit of this membership
|
||||
/// @param index the index of the membership in the membership set
|
||||
event MembershipExpired(uint256 idCommitment, uint32 membershipRateLimit, uint32 index);
|
||||
|
||||
/// @notice Emitted when a membership is erased by its holder during grace period
|
||||
/// @param idCommitment the idCommitment of the membership
|
||||
/// @param membershipRateLimit the rate limit of this membership
|
||||
/// @param index the index of the membership in the membership set
|
||||
event MembershipErased(uint256 idCommitment, uint32 membershipRateLimit, uint32 index);
|
||||
|
||||
/// @notice Emitted when a membership in its grace period is extended (i.e., is back to Active state)
|
||||
/// @param idCommitment the idCommitment of the membership
|
||||
/// @param membershipRateLimit the rate limit of this membership
|
||||
/// @param index the index of the membership in the membership set
|
||||
/// @param newGracePeriodStartTimestamp the new grace period start timestamp of this membership
|
||||
event MembershipExtended(
|
||||
uint256 idCommitment, uint32 membershipRateLimit, uint32 index, uint256 newGracePeriodStartTimestamp
|
||||
);
|
||||
|
||||
/// @dev contract initializer
|
||||
/// @param _priceCalculator Address of an instance of IPriceCalculator
|
||||
/// @param _maxTotalRateLimit Maximum total rate limit of all memberships in the membership set
|
||||
/// @param _minMembershipRateLimit Minimum rate limit of each membership
|
||||
/// @param _maxMembershipRateLimit Maximum rate limit of each membership
|
||||
/// @param _activeDurationForNewMemberships Active state duration of each membership
|
||||
/// @param _gracePeriodDurationForNewMemberships Grace period duration of each membership
|
||||
function __MembershipUpgradeable_init(
|
||||
address _priceCalculator,
|
||||
uint32 _maxTotalRateLimit,
|
||||
uint32 _minMembershipRateLimit,
|
||||
uint32 _maxMembershipRateLimit,
|
||||
uint32 _activeDurationForNewMemberships,
|
||||
uint32 _gracePeriodDurationForNewMemberships
|
||||
)
|
||||
internal
|
||||
onlyInitializing
|
||||
{
|
||||
__MembershipUpgradeable_init_unchained(
|
||||
_priceCalculator,
|
||||
_maxTotalRateLimit,
|
||||
_minMembershipRateLimit,
|
||||
_maxMembershipRateLimit,
|
||||
_activeDurationForNewMemberships,
|
||||
_gracePeriodDurationForNewMemberships
|
||||
);
|
||||
}
|
||||
|
||||
function __MembershipUpgradeable_init_unchained(
|
||||
address _priceCalculator,
|
||||
uint32 _maxTotalRateLimit,
|
||||
uint32 _minMembershipRateLimit,
|
||||
uint32 _maxMembershipRateLimit,
|
||||
uint32 _activeDurationForNewMemberships,
|
||||
uint32 _gracePeriodDurationForNewMemberships
|
||||
)
|
||||
internal
|
||||
onlyInitializing
|
||||
{
|
||||
require(0 < _minMembershipRateLimit);
|
||||
require(_minMembershipRateLimit <= _maxMembershipRateLimit);
|
||||
require(_maxMembershipRateLimit <= _maxTotalRateLimit);
|
||||
require(_activeDurationForNewMemberships > 0);
|
||||
// Note: grace period duration may be equal to zero
|
||||
|
||||
priceCalculator = IPriceCalculator(_priceCalculator);
|
||||
maxTotalRateLimit = _maxTotalRateLimit;
|
||||
minMembershipRateLimit = _minMembershipRateLimit;
|
||||
maxMembershipRateLimit = _maxMembershipRateLimit;
|
||||
activeDurationForNewMemberships = _activeDurationForNewMemberships;
|
||||
gracePeriodDurationForNewMemberships = _gracePeriodDurationForNewMemberships;
|
||||
}
|
||||
|
||||
/// @dev acquire a membership and transfer the deposit to the contract
|
||||
/// @param _sender the address of the transaction sender
|
||||
/// @param _idCommitment the idCommitment of the new membership
|
||||
/// @param _rateLimit the membership rate limit
|
||||
/// @return index the index of the new membership in the membership set
|
||||
/// @return indexReused true if the index was reused, false otherwise
|
||||
function _acquireMembership(
|
||||
address _sender,
|
||||
uint256 _idCommitment,
|
||||
uint32 _rateLimit
|
||||
)
|
||||
internal
|
||||
returns (uint32 index, bool indexReused)
|
||||
{
|
||||
// Check if the rate limit is valid
|
||||
if (!isValidMembershipRateLimit(_rateLimit)) {
|
||||
revert InvalidMembershipRateLimit();
|
||||
}
|
||||
|
||||
currentTotalRateLimit += _rateLimit;
|
||||
|
||||
// Determine if we exceed the total rate limit
|
||||
if (currentTotalRateLimit > maxTotalRateLimit) {
|
||||
revert CannotExceedMaxTotalRateLimit();
|
||||
}
|
||||
|
||||
(address token, uint256 depositAmount) = priceCalculator.calculate(_rateLimit);
|
||||
|
||||
// Possibly reuse an index of an erased membership
|
||||
(index, indexReused) = _getFreeIndex();
|
||||
|
||||
memberships[_idCommitment] = MembershipInfo({
|
||||
holder: _sender,
|
||||
activeDuration: activeDurationForNewMemberships,
|
||||
gracePeriodStartTimestamp: block.timestamp + uint256(activeDurationForNewMemberships),
|
||||
gracePeriodDuration: gracePeriodDurationForNewMemberships,
|
||||
token: token,
|
||||
depositAmount: depositAmount,
|
||||
rateLimit: _rateLimit,
|
||||
index: index
|
||||
});
|
||||
|
||||
IERC20(token).safeTransferFrom(_sender, address(this), depositAmount);
|
||||
}
|
||||
|
||||
/// @notice Checks if a rate limit is within the allowed bounds
|
||||
/// @param rateLimit The rate limit
|
||||
/// @return true if the rate limit is within the allowed bounds, false otherwise
|
||||
function isValidMembershipRateLimit(uint32 rateLimit) public view returns (bool) {
|
||||
return minMembershipRateLimit <= rateLimit && rateLimit <= maxMembershipRateLimit;
|
||||
}
|
||||
|
||||
/// @dev Get a free index (possibly reuse an index of an erased membership)
|
||||
/// @return index index to be used for the new membership registration
|
||||
/// @return indexReused indicates whether the index was reused from an erased membership
|
||||
function _getFreeIndex() internal returns (uint32 index, bool indexReused) {
|
||||
uint256 numIndices = indicesOfLazilyErasedMemberships.length;
|
||||
if (numIndices != 0) {
|
||||
// Reuse the index of the latest erased membership
|
||||
index = indicesOfLazilyErasedMemberships[numIndices - 1];
|
||||
indicesOfLazilyErasedMemberships.pop();
|
||||
indexReused = true;
|
||||
} else {
|
||||
index = nextFreeIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Extend a grace-period membership
|
||||
/// @param _sender the address of the transaction sender
|
||||
/// @param _idCommitment the idCommitment of the membership
|
||||
function _extendMembership(address _sender, uint256 _idCommitment) public {
|
||||
MembershipInfo storage membership = memberships[_idCommitment];
|
||||
|
||||
if (!_isInPeriod(membership.gracePeriodStartTimestamp, membership.gracePeriodDuration)) {
|
||||
revert CannotExtendNonGracePeriodMembership(_idCommitment);
|
||||
}
|
||||
|
||||
if (_sender != membership.holder) revert NonHolderCannotExtend(_idCommitment);
|
||||
|
||||
// Note: we add the new active period to the end of the ongoing grace period
|
||||
uint256 newGracePeriodStartTimestamp =
|
||||
membership.gracePeriodStartTimestamp + membership.gracePeriodDuration + uint256(membership.activeDuration);
|
||||
|
||||
membership.gracePeriodStartTimestamp = newGracePeriodStartTimestamp;
|
||||
|
||||
emit MembershipExtended(
|
||||
_idCommitment, membership.rateLimit, membership.index, membership.gracePeriodStartTimestamp
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Erase expired memberships or owned grace-period memberships.
|
||||
/// @param _sender the address of the transaction sender
|
||||
/// @param _idCommitment idCommitment of the membership to erase
|
||||
function _eraseMembershipLazily(address _sender, uint256 _idCommitment) internal returns (uint32 index) {
|
||||
MembershipInfo memory membership = memberships[_idCommitment];
|
||||
|
||||
if (membership.rateLimit == 0) revert MembershipDoesNotExist(_idCommitment);
|
||||
|
||||
bool membershipExpired = _isAfterPeriod(membership.gracePeriodStartTimestamp, membership.gracePeriodDuration);
|
||||
bool membershipIsInGracePeriod =
|
||||
_isInPeriod(membership.gracePeriodStartTimestamp, membership.gracePeriodDuration);
|
||||
bool isHolder = (membership.holder == _sender);
|
||||
|
||||
if (!membershipExpired && !membershipIsInGracePeriod) {
|
||||
revert CannotEraseActiveMembership(_idCommitment);
|
||||
} else if (membershipIsInGracePeriod && !isHolder) {
|
||||
revert NonHolderCannotEraseGracePeriodMembership(_idCommitment);
|
||||
}
|
||||
|
||||
// Move deposit balance from the membership to be erased to holder deposit balance
|
||||
depositsToWithdraw[membership.holder][membership.token] += membership.depositAmount;
|
||||
|
||||
// Deduct the rate limit of this membership from the total rate limit
|
||||
currentTotalRateLimit -= membership.rateLimit;
|
||||
|
||||
// Mark this membership as reusable
|
||||
indicesOfLazilyErasedMemberships.push(membership.index);
|
||||
|
||||
// Erase this membership from the memberships mapping
|
||||
delete memberships[_idCommitment];
|
||||
|
||||
if (membershipExpired) {
|
||||
emit MembershipExpired(_idCommitment, membership.rateLimit, membership.index);
|
||||
}
|
||||
emit MembershipErased(_idCommitment, membership.rateLimit, membership.index);
|
||||
|
||||
// This index will be used to erase the data from the Merkle tree that represents the membership set
|
||||
return membership.index;
|
||||
}
|
||||
|
||||
/// @notice Determine if a membership is in its grace period
|
||||
/// @param _idCommitment the idCommitment of the membership
|
||||
function isInGracePeriod(uint256 _idCommitment) public view returns (bool) {
|
||||
MembershipInfo memory membership = memberships[_idCommitment];
|
||||
return _isInPeriod(membership.gracePeriodStartTimestamp, membership.gracePeriodDuration);
|
||||
}
|
||||
|
||||
/// @dev Determine whether the current timestamp is within a given period
|
||||
/// @param _start timestamp in which the period starts (inclusive)
|
||||
/// @param _duration duration of the period (end timestamp exclusive)
|
||||
function _isInPeriod(uint256 _start, uint32 _duration) internal view returns (bool) {
|
||||
uint256 timeNow = block.timestamp;
|
||||
return (_start <= timeNow && timeNow < _start + uint256(_duration));
|
||||
}
|
||||
|
||||
/// @notice Determine if a membership is expired
|
||||
/// @param _idCommitment the idCommitment of the membership
|
||||
function isExpired(uint256 _idCommitment) public view returns (bool) {
|
||||
MembershipInfo memory membership = memberships[_idCommitment];
|
||||
return _isAfterPeriod(membership.gracePeriodStartTimestamp, membership.gracePeriodDuration);
|
||||
}
|
||||
|
||||
/// @dev Determine whether the current timestamp is after a given period
|
||||
/// @param _start timestamp in which the period starts (inclusive)
|
||||
/// @param _duration duration of the period (end timestamp exclusive)
|
||||
function _isAfterPeriod(uint256 _start, uint32 _duration) internal view returns (bool) {
|
||||
uint256 timeNow = block.timestamp;
|
||||
return (_timestampAfterPeriod(_start, _duration) <= timeNow);
|
||||
}
|
||||
|
||||
/// @notice Returns the timestamp on which a membership can be considered expired (i.e. when its grace period ends)
|
||||
/// @param _idCommitment the idCommitment of the membership
|
||||
function membershipExpirationTimestamp(uint256 _idCommitment) public view returns (uint256) {
|
||||
MembershipInfo memory membership = memberships[_idCommitment];
|
||||
return _timestampAfterPeriod(membership.gracePeriodStartTimestamp, membership.gracePeriodDuration);
|
||||
}
|
||||
|
||||
/// @dev Returns the first timestamp after a specified period
|
||||
/// @param _start timestamp in which the period starts (inclusive)
|
||||
/// @param _duration duration of the period (exclusive)
|
||||
function _timestampAfterPeriod(uint256 _start, uint32 _duration) internal pure returns (uint256) {
|
||||
return _start + uint256(_duration);
|
||||
}
|
||||
|
||||
/// @dev Withdraw any available deposit balance in tokens after a membership is erased.
|
||||
/// @param _sender the address of the transaction sender (who withdraws their tokens)
|
||||
/// @param _token the address of the token to withdraw
|
||||
function _withdraw(address _sender, address _token) internal {
|
||||
require(_token != address(0), "ETH is not allowed");
|
||||
|
||||
uint256 amount = depositsToWithdraw[_sender][_token];
|
||||
require(amount > 0, "Insufficient deposit balance");
|
||||
|
||||
depositsToWithdraw[_sender][_token] = 0;
|
||||
IERC20(_token).safeTransfer(_sender, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev This empty reserved space is put in place to allow future versions to add new
|
||||
* variables without shifting down storage in the inheritance chain.
|
||||
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
|
||||
*/
|
||||
uint256[50] private __gap;
|
||||
}
|
||||
@ -8,71 +8,52 @@ 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";
|
||||
|
||||
/// The tree is full
|
||||
error FullTree();
|
||||
import { MembershipUpgradeable } from "./Membership.sol";
|
||||
import { IPriceCalculator } from "./IPriceCalculator.sol";
|
||||
|
||||
/// Member is already registered
|
||||
/// A membership with this idCommitment is already registered
|
||||
error DuplicateIdCommitment();
|
||||
|
||||
/// Invalid idCommitment
|
||||
error InvalidIdCommitment(uint256 idCommitment);
|
||||
|
||||
/// Invalid userMessageLimit
|
||||
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, MembershipUpgradeable {
|
||||
/// @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 that stores rate commitments of memberships
|
||||
uint8 public constant MERKLE_TREE_DEPTH = 20;
|
||||
|
||||
/// @notice The depth of the merkle tree
|
||||
uint8 public constant DEPTH = 20;
|
||||
/// @notice The maximum membership set size is the size of the Merkle tree (2 ^ depth)
|
||||
uint32 public MAX_MEMBERSHIP_SET_SIZE;
|
||||
|
||||
/// @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
|
||||
/// @notice The block number at which this contract was deployed
|
||||
uint32 public deployedBlockNumber;
|
||||
|
||||
/// @notice the stored imt data
|
||||
LazyIMTData public imtData;
|
||||
/// @notice The Merkle tree that stores rate commitments of memberships
|
||||
LazyIMTData public merkleTree;
|
||||
|
||||
/// Emitted when a new member is added to the set
|
||||
/// @param rateCommitment the rateCommitment of the member
|
||||
/// @param index The index of the member in the set
|
||||
event MemberRegistered(uint256 rateCommitment, uint32 index);
|
||||
|
||||
/// @notice the modifier to check if the idCommitment is valid
|
||||
/// @param idCommitment The idCommitment of the member
|
||||
/// @notice Сheck if the idCommitment is valid
|
||||
/// @param idCommitment The idCommitment of the membership
|
||||
modifier onlyValidIdCommitment(uint256 idCommitment) {
|
||||
if (!isValidCommitment(idCommitment)) revert InvalidIdCommitment(idCommitment);
|
||||
if (!isValidIdCommitment(idCommitment)) revert InvalidIdCommitment(idCommitment);
|
||||
_;
|
||||
}
|
||||
|
||||
/// @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);
|
||||
/// @notice Сheck that the membership with this idCommitment is not already in the membership set
|
||||
/// @param idCommitment The idCommitment of the membership
|
||||
modifier noDuplicateMembership(uint256 idCommitment) {
|
||||
require(!isInMembershipSet(idCommitment), "Duplicate idCommitment: membership already exists");
|
||||
_;
|
||||
}
|
||||
|
||||
/// @notice Check that the membership set is not full
|
||||
modifier membershipSetNotFull() {
|
||||
require(nextFreeIndex < MAX_MEMBERSHIP_SET_SIZE, "Membership set is full");
|
||||
_;
|
||||
}
|
||||
|
||||
@ -80,119 +61,246 @@ contract WakuRlnV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable {
|
||||
_disableInitializers();
|
||||
}
|
||||
|
||||
function initialize(uint32 maxMessageLimit) public initializer {
|
||||
/// @dev Contract initializer
|
||||
/// @param _priceCalculator Address of an instance of IPriceCalculator
|
||||
/// @param _maxTotalRateLimit Maximum total rate limit of all memberships in the membership set
|
||||
/// @param _minMembershipRateLimit Minimum rate limit of one membership
|
||||
/// @param _maxMembershipRateLimit Maximum rate limit of one membership
|
||||
/// @param _activeDuration Membership active duration
|
||||
/// @param _gracePeriod Membership grace period
|
||||
function initialize(
|
||||
address _priceCalculator,
|
||||
uint32 _maxTotalRateLimit,
|
||||
uint32 _minMembershipRateLimit,
|
||||
uint32 _maxMembershipRateLimit,
|
||||
uint32 _activeDuration,
|
||||
uint32 _gracePeriod
|
||||
)
|
||||
public
|
||||
initializer
|
||||
{
|
||||
__Ownable_init();
|
||||
__UUPSUpgradeable_init();
|
||||
MAX_MESSAGE_LIMIT = maxMessageLimit;
|
||||
SET_SIZE = uint32(1 << DEPTH);
|
||||
__MembershipUpgradeable_init(
|
||||
_priceCalculator,
|
||||
_maxTotalRateLimit,
|
||||
_minMembershipRateLimit,
|
||||
_maxMembershipRateLimit,
|
||||
_activeDuration,
|
||||
_gracePeriod
|
||||
);
|
||||
|
||||
MAX_MEMBERSHIP_SET_SIZE = uint32(1 << MERKLE_TREE_DEPTH);
|
||||
deployedBlockNumber = uint32(block.number);
|
||||
LazyIMT.init(imtData, DEPTH);
|
||||
commitmentIndex = 0;
|
||||
LazyIMT.init(merkleTree, MERKLE_TREE_DEPTH);
|
||||
nextFreeIndex = 0;
|
||||
}
|
||||
|
||||
function _authorizeUpgrade(address newImplementation) internal override onlyOwner { } // solhint-disable-line
|
||||
|
||||
/// @notice Checks if a commitment is valid
|
||||
/// @param idCommitment The idCommitment of the member
|
||||
/// @return true if the commitment is valid, false otherwise
|
||||
function isValidCommitment(uint256 idCommitment) public pure returns (bool) {
|
||||
return idCommitment != 0 && idCommitment < Q;
|
||||
/// @notice Checks if an idCommitment is valid (between 0 and Q, both exclusive)
|
||||
/// @param idCommitment The idCommitment of the membership
|
||||
/// @return true if the idCommitment is valid, false otherwise
|
||||
function isValidIdCommitment(uint256 idCommitment) public pure returns (bool) {
|
||||
return 0 < idCommitment && 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];
|
||||
// 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);
|
||||
/// @notice Checks if a membership is in the membership set
|
||||
/// @param idCommitment The idCommitment of the membership
|
||||
/// @return true if the membership is in the membership set, false otherwise
|
||||
function isInMembershipSet(uint256 idCommitment) public view returns (bool) {
|
||||
(,, uint256 rateCommitment) = getMembershipInfo(idCommitment);
|
||||
return rateCommitment != 0;
|
||||
}
|
||||
|
||||
/// Allows a user to register as a member
|
||||
/// @param idCommitment The idCommitment of the member
|
||||
/// @param userMessageLimit The message limit of the member
|
||||
/// @notice Returns the membership info (rate limit, index, rateCommitment) by its idCommitment
|
||||
/// @param idCommitment The idCommitment of the membership
|
||||
/// @return The membership info (rateLimit, index, rateCommitment)
|
||||
function getMembershipInfo(uint256 idCommitment) public view returns (uint32, uint32, uint256) {
|
||||
MembershipInfo memory membership = memberships[idCommitment];
|
||||
// we cannot call getRateCommmitment for 0 index if the membership doesn't exist
|
||||
if (membership.rateLimit == 0) {
|
||||
return (0, 0, 0);
|
||||
}
|
||||
return (membership.rateLimit, membership.index, _getRateCommmitment(membership.index));
|
||||
}
|
||||
|
||||
/// @notice Returns the rateCommitments of memberships within an index range
|
||||
/// @param startIndex The start index of the range (inclusive)
|
||||
/// @param endIndex The end index of the range (inclusive)
|
||||
/// @return The rateCommitments of the memberships
|
||||
function getRateCommitmentsInRangeBoundsInclusive(
|
||||
uint32 startIndex,
|
||||
uint32 endIndex
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory)
|
||||
{
|
||||
if (startIndex > endIndex) revert InvalidPaginationQuery(startIndex, endIndex);
|
||||
if (endIndex >= nextFreeIndex) revert InvalidPaginationQuery(startIndex, endIndex);
|
||||
|
||||
uint256[] memory rateCommitments = new uint256[](endIndex - startIndex + 1);
|
||||
for (uint32 i = startIndex; i <= endIndex; i++) {
|
||||
rateCommitments[i - startIndex] = _getRateCommmitment(i);
|
||||
}
|
||||
return rateCommitments;
|
||||
}
|
||||
|
||||
/// @notice Returns the rateCommitment of a membership at a given index
|
||||
/// @param index The index of the membership in the membership set
|
||||
/// @return The rateCommitment of the membership
|
||||
function _getRateCommmitment(uint32 index) internal view returns (uint256) {
|
||||
return merkleTree.elements[LazyIMT.indexForElement(0, index)];
|
||||
}
|
||||
|
||||
/// @notice Register a membership while erasing some expired memberships to reuse their rate limit
|
||||
/// @param idCommitment The idCommitment of the new membership
|
||||
/// @param rateLimit The rate limit of the new membership
|
||||
/// @param idCommitmentsToErase The list of idCommitments of expired memberships to erase
|
||||
function register(
|
||||
uint256 idCommitment,
|
||||
uint32 userMessageLimit
|
||||
uint32 rateLimit,
|
||||
uint256[] calldata idCommitmentsToErase
|
||||
)
|
||||
external
|
||||
onlyValidIdCommitment(idCommitment)
|
||||
onlyValidUserMessageLimit(userMessageLimit)
|
||||
noDuplicateMembership(idCommitment)
|
||||
membershipSetNotFull
|
||||
{
|
||||
_register(idCommitment, userMessageLimit);
|
||||
// erase memberships without overwriting membership set data to zero (save gas)
|
||||
_eraseMemberships(idCommitmentsToErase, false);
|
||||
_register(idCommitment, rateLimit);
|
||||
}
|
||||
|
||||
/// 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();
|
||||
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;
|
||||
|
||||
emit MemberRegistered(rateCommitment, commitmentIndex);
|
||||
commitmentIndex += 1;
|
||||
}
|
||||
|
||||
/// @notice Returns the commitments of a range of members
|
||||
/// @param startIndex The start index of the range
|
||||
/// @param endIndex The end index of the range
|
||||
/// @return The commitments of the members
|
||||
function getCommitments(uint32 startIndex, uint32 endIndex) public view returns (uint256[] memory) {
|
||||
if (startIndex > endIndex) revert InvalidPaginationQuery(startIndex, endIndex);
|
||||
if (endIndex > commitmentIndex) revert InvalidPaginationQuery(startIndex, endIndex);
|
||||
|
||||
uint256[] memory commitments = new uint256[](endIndex - startIndex + 1);
|
||||
for (uint32 i = startIndex; i <= endIndex; i++) {
|
||||
commitments[i - startIndex] = indexToCommitment(i);
|
||||
/// @dev Register a membership (internal function)
|
||||
/// @param idCommitment The idCommitment of the membership
|
||||
/// @param rateLimit The rate limit of the membership
|
||||
function _register(uint256 idCommitment, uint32 rateLimit) internal {
|
||||
(uint32 index, bool indexReused) = _acquireMembership(_msgSender(), idCommitment, rateLimit);
|
||||
uint256 rateCommitment = PoseidonT3.hash([idCommitment, rateLimit]);
|
||||
if (indexReused) {
|
||||
LazyIMT.update(merkleTree, rateCommitment, index);
|
||||
} else {
|
||||
LazyIMT.insert(merkleTree, rateCommitment);
|
||||
nextFreeIndex += 1;
|
||||
}
|
||||
return commitments;
|
||||
|
||||
emit MembershipRegistered(idCommitment, rateLimit, index);
|
||||
}
|
||||
|
||||
/// @notice Returns the root of the IMT
|
||||
/// @return The root of the IMT
|
||||
/// @notice Returns the root of the Merkle tree that stores rate commitments of memberships
|
||||
/// @return The root of the Merkle tree that stores rate commitments of memberships
|
||||
function root() external view returns (uint256) {
|
||||
return LazyIMT.root(imtData, DEPTH);
|
||||
return LazyIMT.root(merkleTree, MERKLE_TREE_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[DEPTH] memory) {
|
||||
uint256[DEPTH] memory castedProof;
|
||||
uint256[] memory proof = LazyIMT.merkleProofElements(imtData, index, DEPTH);
|
||||
for (uint8 i = 0; i < DEPTH; i++) {
|
||||
castedProof[i] = proof[i];
|
||||
/// @notice Returns the Merkle proof that a given membership is in the membership set
|
||||
/// @param index The index of the membership
|
||||
/// @return The Merkle proof (an array of MERKLE_TREE_DEPTH elements)
|
||||
function getMerkleProof(uint40 index) public view returns (uint256[MERKLE_TREE_DEPTH] memory) {
|
||||
uint256[] memory dynamicSizeProof = LazyIMT.merkleProofElements(merkleTree, index, MERKLE_TREE_DEPTH);
|
||||
uint256[MERKLE_TREE_DEPTH] memory fixedSizeProof;
|
||||
for (uint8 i = 0; i < MERKLE_TREE_DEPTH; i++) {
|
||||
fixedSizeProof[i] = dynamicSizeProof[i];
|
||||
}
|
||||
return castedProof;
|
||||
return fixedSizeProof;
|
||||
}
|
||||
|
||||
/// @notice Extend a grace-period membership under the same conditions
|
||||
/// @param idCommitments list of idCommitments of memberships to extend
|
||||
function extendMemberships(uint256[] calldata idCommitments) external {
|
||||
for (uint256 i = 0; i < idCommitments.length; i++) {
|
||||
_extendMembership(_msgSender(), idCommitments[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Erase expired memberships or owned grace-period memberships
|
||||
/// The user can select expired memberships offchain, and proceed to erase them.
|
||||
/// The holder can use this function to erase their own grace-period memberships.
|
||||
/// The holder can then withdraw the deposited tokens.
|
||||
/// @param idCommitments The list of idCommitments of the memberships to erase
|
||||
/// set
|
||||
function eraseMemberships(uint256[] calldata idCommitments) external {
|
||||
_eraseMemberships(idCommitments, false);
|
||||
}
|
||||
|
||||
/// @notice Erase expired memberships or owned grace-period memberships
|
||||
/// Optionally, also erase rate commitment data from the membership set (clean-up).
|
||||
/// Compared to eraseMemberships(idCommitments),
|
||||
/// this function decreases Merkle tree size and spends more gas (if eraseFromMembershipSet == true).
|
||||
/// @param idCommitments The list of idCommitments of the memberships to erase
|
||||
/// @param eraseFromMembershipSet Indicates whether to erase membership data from the membership set
|
||||
function eraseMemberships(uint256[] calldata idCommitments, bool eraseFromMembershipSet) external {
|
||||
_eraseMemberships(idCommitments, eraseFromMembershipSet);
|
||||
}
|
||||
|
||||
/// @dev Erase memberships from the list of idCommitments
|
||||
/// @param idCommitmentsToErase The idCommitments of memberships to erase from storage
|
||||
/// @param eraseFromMembershipSet Indicates whether to erase membership data from the membership set
|
||||
function _eraseMemberships(uint256[] calldata idCommitmentsToErase, bool eraseFromMembershipSet) internal {
|
||||
// eraseFromMembershipSet == true means full clean-up.
|
||||
// Erase memberships from memberships array (free up the rate limit and index),
|
||||
// and erase the rate commitment from the membership set (reduce the Merkle tree size).
|
||||
// eraseFromMembershipSet == false means lazy erasure.
|
||||
// Only erase memberships from the memberships array (consume less gas).
|
||||
// Merkle tree data will be overwritten when the correspondind index is reused.
|
||||
for (uint256 i = 0; i < idCommitmentsToErase.length; i++) {
|
||||
// Erase the membership from the memberships array in contract storage
|
||||
uint32 indexToErase = _eraseMembershipLazily(_msgSender(), idCommitmentsToErase[i]);
|
||||
// Optionally, also erase the rate commitment data from the membership set.
|
||||
// This does not affect the total rate limit control, or index reusal for new membership registrations.
|
||||
if (eraseFromMembershipSet) {
|
||||
LazyIMT.update(merkleTree, 0, indexToErase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Withdraw any available deposit balance in tokens after a membership is erased
|
||||
/// @param token The address of the token to withdraw
|
||||
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 membership set
|
||||
/// @param _maxTotalRateLimit new maximum total rate limit (messages per epoch)
|
||||
function setMaxTotalRateLimit(uint32 _maxTotalRateLimit) external onlyOwner {
|
||||
require(maxMembershipRateLimit <= _maxTotalRateLimit);
|
||||
maxTotalRateLimit = _maxTotalRateLimit;
|
||||
}
|
||||
|
||||
/// @notice Set the maximum rate limit of one membership
|
||||
/// @param _maxMembershipRateLimit new maximum rate limit per membership (messages per epoch)
|
||||
function setMaxMembershipRateLimit(uint32 _maxMembershipRateLimit) external onlyOwner {
|
||||
require(minMembershipRateLimit <= _maxMembershipRateLimit);
|
||||
maxMembershipRateLimit = _maxMembershipRateLimit;
|
||||
}
|
||||
|
||||
/// @notice Set the minimum rate limit of one membership
|
||||
/// @param _minMembershipRateLimit new minimum rate limit per membership (messages per epoch)
|
||||
function setMinMembershipRateLimit(uint32 _minMembershipRateLimit) external onlyOwner {
|
||||
require(_minMembershipRateLimit > 0);
|
||||
require(_minMembershipRateLimit <= maxMembershipRateLimit);
|
||||
minMembershipRateLimit = _minMembershipRateLimit;
|
||||
}
|
||||
|
||||
/// @notice Set the active duration for new memberships (terms of existing memberships don't change)
|
||||
/// @param _activeDurationForNewMembership new active duration
|
||||
function setActiveDuration(uint32 _activeDurationForNewMembership) external onlyOwner {
|
||||
require(_activeDurationForNewMembership > 0);
|
||||
activeDurationForNewMemberships = _activeDurationForNewMembership;
|
||||
}
|
||||
|
||||
/// @notice Set the grace period for new memberships (terms of existing memberships don't change)
|
||||
/// @param _gracePeriodDurationForNewMembership new grace period duration
|
||||
function setGracePeriodDuration(uint32 _gracePeriodDurationForNewMembership) external onlyOwner {
|
||||
// Note: grace period duration may be equal to zero
|
||||
gracePeriodDurationForNewMemberships = _gracePeriodDurationForNewMembership;
|
||||
}
|
||||
}
|
||||
|
||||
19
test/TestToken.sol
Normal file
19
test/TestToken.sol
Normal file
@ -0,0 +1,19 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity >=0.8.19 <0.9.0;
|
||||
|
||||
import { BaseScript } from "../script/Base.s.sol";
|
||||
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
|
||||
contract TestToken is ERC20 {
|
||||
constructor() ERC20("TestToken", "TTT") { }
|
||||
|
||||
function mint(address to, uint256 amount) public {
|
||||
_mint(to, amount);
|
||||
}
|
||||
}
|
||||
|
||||
contract TestTokenFactory is BaseScript {
|
||||
function run() public broadcast returns (address) {
|
||||
return address(new TestToken());
|
||||
}
|
||||
}
|
||||
@ -5,33 +5,54 @@ 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 { IPriceCalculator } from "../src/IPriceCalculator.sol";
|
||||
import { LinearPriceCalculator } from "../src/LinearPriceCalculator.sol";
|
||||
import { TestToken } from "./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;
|
||||
|
||||
uint256[] noIdCommitmentsToErase = new uint256[](0);
|
||||
|
||||
function setUp() public virtual {
|
||||
token = new TestToken();
|
||||
|
||||
Deploy deployment = new Deploy();
|
||||
(w, impl) = deployment.run();
|
||||
(w, impl) = deployment.deploy(address(token));
|
||||
|
||||
// Minting a large number of tokens to not have to worry about
|
||||
// Not having enough balance
|
||||
token.mint(address(this), 100_000_000 ether);
|
||||
}
|
||||
|
||||
function test__ValidRegistration__kats() external {
|
||||
vm.pauseGasMetering();
|
||||
// Merkle tree leaves are calculated using 2 as rateLimit
|
||||
vm.prank(w.owner());
|
||||
w.setMinMembershipRateLimit(2);
|
||||
|
||||
uint256 idCommitment = 2;
|
||||
uint32 userMessageLimit = 2;
|
||||
uint32 membershipRateLimit = 2;
|
||||
(, uint256 price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
vm.resumeGasMetering();
|
||||
w.register(idCommitment, userMessageLimit);
|
||||
token.approve(address(w), price);
|
||||
w.register(idCommitment, membershipRateLimit, noIdCommitmentsToErase);
|
||||
vm.pauseGasMetering();
|
||||
assertEq(w.commitmentIndex(), 1);
|
||||
assertEq(w.memberExists(idCommitment), true);
|
||||
(uint32 fetchedUserMessageLimit, uint32 index) = w.memberInfo(idCommitment);
|
||||
assertEq(fetchedUserMessageLimit, userMessageLimit);
|
||||
assertEq(w.nextFreeIndex(), 1);
|
||||
assertEq(w.isInMembershipSet(idCommitment), true);
|
||||
(,,,, uint32 membershipRateLimit1, uint32 index, address holder,) = w.memberships(idCommitment);
|
||||
assertEq(membershipRateLimit1, membershipRateLimit);
|
||||
assertEq(holder, address(this));
|
||||
assertEq(index, 0);
|
||||
// kats from zerokit
|
||||
uint256 rateCommitment =
|
||||
@ -40,12 +61,11 @@ contract WakuRlnV2Test is Test {
|
||||
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);
|
||||
(uint32 membershipRateLimit2, uint32 index2, uint256 rateCommitment2) = w.getMembershipInfo(idCommitment);
|
||||
assertEq(membershipRateLimit2, membershipRateLimit);
|
||||
assertEq(index2, 0);
|
||||
assertEq(rateCommitment2, rateCommitment);
|
||||
uint256[20] memory proof = w.merkleProofElements(0);
|
||||
uint256[20] memory proof = w.getMerkleProof(0);
|
||||
uint256[20] memory expectedProof = [
|
||||
0,
|
||||
14_744_269_619_966_411_208_579_211_824_598_458_697_587_494_354_926_760_081_771_325_075_741_142_829_156,
|
||||
@ -74,124 +94,675 @@ 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 membershipRateLimit) external {
|
||||
vm.pauseGasMetering();
|
||||
uint256 idCommitment = 2;
|
||||
(, uint256 price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
uint256 minMembershipRateLimit = w.minMembershipRateLimit();
|
||||
uint256 maxMembershipRateLimit = w.maxMembershipRateLimit();
|
||||
vm.assume(minMembershipRateLimit <= membershipRateLimit && membershipRateLimit <= maxMembershipRateLimit);
|
||||
vm.assume(w.isValidMembershipRateLimit(membershipRateLimit));
|
||||
vm.resumeGasMetering();
|
||||
|
||||
assertEq(w.memberExists(idCommitment), false);
|
||||
w.register(idCommitment, userMessageLimit);
|
||||
uint256 rateCommitment = PoseidonT3.hash([idCommitment, userMessageLimit]);
|
||||
assertEq(w.isInMembershipSet(idCommitment), false);
|
||||
token.approve(address(w), price);
|
||||
w.register(idCommitment, membershipRateLimit, noIdCommitmentsToErase);
|
||||
uint256 rateCommitment = PoseidonT3.hash([idCommitment, membershipRateLimit]);
|
||||
|
||||
(uint32 fetchedUserMessageLimit, uint32 index, uint256 fetchedRateCommitment) =
|
||||
w.idCommitmentToMetadata(idCommitment);
|
||||
assertEq(fetchedUserMessageLimit, userMessageLimit);
|
||||
(uint32 fetchedMembershipRateLimit, uint32 index, uint256 fetchedRateCommitment) =
|
||||
w.getMembershipInfo(idCommitment);
|
||||
assertEq(fetchedMembershipRateLimit, membershipRateLimit);
|
||||
assertEq(index, 0);
|
||||
assertEq(fetchedRateCommitment, rateCommitment);
|
||||
|
||||
assertEq(token.balanceOf(address(w)), price);
|
||||
assertEq(w.currentTotalRateLimit(), membershipRateLimit);
|
||||
}
|
||||
|
||||
function test__LinearPriceCalculation(uint32 membershipRateLimit) external view {
|
||||
IPriceCalculator priceCalculator = w.priceCalculator();
|
||||
uint256 pricePerMessagePerPeriod = LinearPriceCalculator(address(priceCalculator)).pricePerMessagePerEpoch();
|
||||
assertNotEq(pricePerMessagePerPeriod, 0);
|
||||
uint256 expectedPrice = uint256(membershipRateLimit) * pricePerMessagePerPeriod;
|
||||
(, uint256 price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
assertEq(price, expectedPrice);
|
||||
}
|
||||
|
||||
function test__InvalidTokenAmount(uint256 idCommitment, uint32 membershipRateLimit) external {
|
||||
vm.pauseGasMetering();
|
||||
uint256 minMembershipRateLimit = w.minMembershipRateLimit();
|
||||
uint256 maxMembershipRateLimit = w.maxMembershipRateLimit();
|
||||
vm.assume(minMembershipRateLimit <= membershipRateLimit && membershipRateLimit <= maxMembershipRateLimit);
|
||||
vm.assume(w.isValidIdCommitment(idCommitment) && w.isValidMembershipRateLimit(membershipRateLimit));
|
||||
(, uint256 price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
vm.resumeGasMetering();
|
||||
|
||||
token.approve(address(w), price - 1);
|
||||
vm.expectRevert(bytes("ERC20: insufficient allowance"));
|
||||
w.register(idCommitment, membershipRateLimit, noIdCommitmentsToErase);
|
||||
}
|
||||
|
||||
function test__IdCommitmentToMetadata__DoesntExist() external view {
|
||||
uint256 idCommitment = 2;
|
||||
(uint32 userMessageLimit, uint32 index, uint256 rateCommitment) = w.idCommitmentToMetadata(idCommitment);
|
||||
assertEq(userMessageLimit, 0);
|
||||
(uint32 membershipRateLimit, uint32 index, uint256 rateCommitment) = w.getMembershipInfo(idCommitment);
|
||||
assertEq(membershipRateLimit, 0);
|
||||
assertEq(index, 0);
|
||||
assertEq(rateCommitment, 0);
|
||||
}
|
||||
|
||||
function test__InvalidRegistration__InvalidIdCommitment__Zero() external {
|
||||
vm.pauseGasMetering();
|
||||
uint256 idCommitment = 0;
|
||||
uint32 userMessageLimit = 2;
|
||||
uint32 membershipRateLimit = 2;
|
||||
(, uint256 price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
vm.resumeGasMetering();
|
||||
|
||||
token.approve(address(w), price);
|
||||
vm.expectRevert(abi.encodeWithSelector(InvalidIdCommitment.selector, 0));
|
||||
w.register(idCommitment, userMessageLimit);
|
||||
w.register(idCommitment, membershipRateLimit, noIdCommitmentsToErase);
|
||||
}
|
||||
|
||||
function test__InvalidRegistration__InvalidIdCommitment__LargerThanField() external {
|
||||
vm.pauseGasMetering();
|
||||
uint32 membershipRateLimit = 20;
|
||||
(, uint256 price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
vm.resumeGasMetering();
|
||||
|
||||
uint256 idCommitment = w.Q() + 1;
|
||||
uint32 userMessageLimit = 2;
|
||||
token.approve(address(w), price);
|
||||
vm.expectRevert(abi.encodeWithSelector(InvalidIdCommitment.selector, idCommitment));
|
||||
w.register(idCommitment, userMessageLimit);
|
||||
w.register(idCommitment, membershipRateLimit, noIdCommitmentsToErase);
|
||||
}
|
||||
|
||||
function test__InvalidRegistration__InvalidUserMessageLimit__Zero() external {
|
||||
function test__InvalidRegistration__InvalidMembershipRateLimit__MinMax() external {
|
||||
uint256 idCommitment = 2;
|
||||
uint32 userMessageLimit = 0;
|
||||
vm.expectRevert(abi.encodeWithSelector(InvalidUserMessageLimit.selector, 0));
|
||||
w.register(idCommitment, userMessageLimit);
|
||||
|
||||
uint32 invalidMin = w.minMembershipRateLimit() - 1;
|
||||
uint32 invalidMax = w.maxMembershipRateLimit() + 1;
|
||||
|
||||
vm.expectRevert(abi.encodeWithSelector(InvalidMembershipRateLimit.selector));
|
||||
w.register(idCommitment, invalidMin, noIdCommitmentsToErase);
|
||||
|
||||
vm.expectRevert(abi.encodeWithSelector(InvalidMembershipRateLimit.selector));
|
||||
w.register(idCommitment, invalidMax, noIdCommitmentsToErase);
|
||||
}
|
||||
|
||||
function test__InvalidRegistration__InvalidUserMessageLimit__LargerThanMax() external {
|
||||
function test__ValidRegistrationExtend(uint32 membershipRateLimit) external {
|
||||
vm.pauseGasMetering();
|
||||
uint256 idCommitment = 2;
|
||||
uint32 userMessageLimit = w.MAX_MESSAGE_LIMIT() + 1;
|
||||
vm.expectRevert(abi.encodeWithSelector(InvalidUserMessageLimit.selector, userMessageLimit));
|
||||
w.register(idCommitment, userMessageLimit);
|
||||
(, uint256 price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
vm.assume(
|
||||
w.minMembershipRateLimit() <= membershipRateLimit && membershipRateLimit <= w.maxMembershipRateLimit()
|
||||
);
|
||||
vm.assume(w.isValidMembershipRateLimit(membershipRateLimit));
|
||||
vm.resumeGasMetering();
|
||||
|
||||
token.approve(address(w), price);
|
||||
w.register(idCommitment, membershipRateLimit, noIdCommitmentsToErase);
|
||||
(,, uint256 gracePeriodStartTimestamp,,,,,) = w.memberships(idCommitment);
|
||||
|
||||
assertFalse(w.isInGracePeriod(idCommitment));
|
||||
assertFalse(w.isExpired(idCommitment));
|
||||
|
||||
vm.warp(gracePeriodStartTimestamp);
|
||||
|
||||
assertTrue(w.isInGracePeriod(idCommitment));
|
||||
assertFalse(w.isExpired(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(NonHolderCannotExtend.selector, commitmentsToExtend[0]));
|
||||
w.extendMemberships(commitmentsToExtend);
|
||||
|
||||
// Attempt to extend the membership (but now we are the owner)
|
||||
vm.expectEmit(true, false, false, false); // only check the first parameter of the event (the idCommitment)
|
||||
emit MembershipUpgradeable.MembershipExtended(idCommitment, 0, 0, 0);
|
||||
|
||||
(, uint256 oldActiveDuration, uint256 oldGracePeriodStartTimestamp, uint32 oldGracePeriodDuration,,,,) =
|
||||
w.memberships(idCommitment);
|
||||
w.extendMemberships(commitmentsToExtend);
|
||||
(, uint256 newActiveDuration, uint256 newGracePeriodStartTimestamp, uint32 newGracePeriodDuration,,,,) =
|
||||
w.memberships(idCommitment);
|
||||
|
||||
assertEq(oldActiveDuration, newActiveDuration);
|
||||
assertEq(oldGracePeriodDuration, newGracePeriodDuration);
|
||||
assertEq(
|
||||
oldGracePeriodStartTimestamp + oldGracePeriodDuration + newActiveDuration, newGracePeriodStartTimestamp
|
||||
);
|
||||
assertFalse(w.isInGracePeriod(idCommitment));
|
||||
assertFalse(w.isExpired(idCommitment));
|
||||
|
||||
// Attempt to extend a non grace period membership
|
||||
token.approve(address(w), price);
|
||||
w.register(idCommitment + 1, membershipRateLimit, noIdCommitmentsToErase);
|
||||
commitmentsToExtend[0] = idCommitment + 1;
|
||||
vm.expectRevert(abi.encodeWithSelector(CannotExtendNonGracePeriodMembership.selector, commitmentsToExtend[0]));
|
||||
w.extendMemberships(commitmentsToExtend);
|
||||
}
|
||||
|
||||
function test__ValidRegistrationNoGracePeriod(uint32 membershipRateLimit) external {
|
||||
vm.pauseGasMetering();
|
||||
uint256 idCommitment = 2;
|
||||
(, uint256 price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
vm.assume(
|
||||
w.minMembershipRateLimit() <= membershipRateLimit && membershipRateLimit <= w.maxMembershipRateLimit()
|
||||
);
|
||||
vm.assume(w.isValidMembershipRateLimit(membershipRateLimit));
|
||||
|
||||
vm.startPrank(w.owner());
|
||||
w.setGracePeriodDuration(0);
|
||||
vm.stopPrank();
|
||||
|
||||
vm.resumeGasMetering();
|
||||
|
||||
token.approve(address(w), price);
|
||||
w.register(idCommitment, membershipRateLimit, noIdCommitmentsToErase);
|
||||
|
||||
(,, uint256 gracePeriodStartTimestamp, uint32 gracePeriodDuration,,,,) = w.memberships(idCommitment);
|
||||
|
||||
assertEq(gracePeriodDuration, 0);
|
||||
|
||||
assertFalse(w.isInGracePeriod(idCommitment));
|
||||
assertFalse(w.isExpired(idCommitment));
|
||||
|
||||
uint256 expectedExpirationTimestamp = gracePeriodStartTimestamp + uint256(gracePeriodDuration);
|
||||
uint256 membershipExpirationTimestamp = w.membershipExpirationTimestamp(idCommitment);
|
||||
|
||||
assertEq(expectedExpirationTimestamp, membershipExpirationTimestamp);
|
||||
|
||||
vm.warp(membershipExpirationTimestamp);
|
||||
|
||||
assertFalse(w.isInGracePeriod(idCommitment));
|
||||
assertTrue(w.isExpired(idCommitment));
|
||||
}
|
||||
|
||||
function test__ValidRegistrationExtendSingleMembership(uint32 membershipRateLimit) external {
|
||||
vm.pauseGasMetering();
|
||||
uint256 idCommitment = 2;
|
||||
(, uint256 price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
vm.assume(
|
||||
w.minMembershipRateLimit() <= membershipRateLimit && membershipRateLimit <= w.maxMembershipRateLimit()
|
||||
);
|
||||
vm.assume(w.isValidMembershipRateLimit(membershipRateLimit));
|
||||
vm.resumeGasMetering();
|
||||
|
||||
token.approve(address(w), price);
|
||||
w.register(idCommitment, membershipRateLimit, noIdCommitmentsToErase);
|
||||
uint256 ogExpirationTimestamp = w.membershipExpirationTimestamp(idCommitment);
|
||||
(,, uint256 gracePeriodStartTimestamp,,,,,) = w.memberships(idCommitment);
|
||||
|
||||
vm.warp(gracePeriodStartTimestamp);
|
||||
|
||||
uint256[] memory commitmentsToExtend = new uint256[](1);
|
||||
commitmentsToExtend[0] = idCommitment;
|
||||
|
||||
// Extend the membership
|
||||
vm.expectEmit(true, false, false, false); // only check the first parameter of the event (the idCommitment)
|
||||
emit MembershipUpgradeable.MembershipExtended(idCommitment, 0, 0, 0);
|
||||
w.extendMemberships(commitmentsToExtend);
|
||||
|
||||
(,, uint256 newGracePeriodStartTimestamp, uint32 newGracePeriodDuration,,,,) = w.memberships(idCommitment);
|
||||
uint256 expectedExpirationTimestamp = newGracePeriodStartTimestamp + uint256(newGracePeriodDuration);
|
||||
uint256 membershipExpirationTimestamp = w.membershipExpirationTimestamp(idCommitment);
|
||||
assertEq(expectedExpirationTimestamp, membershipExpirationTimestamp);
|
||||
assertTrue(expectedExpirationTimestamp > ogExpirationTimestamp);
|
||||
}
|
||||
|
||||
function test__ValidRegistrationExpiry(uint32 membershipRateLimit) external {
|
||||
vm.pauseGasMetering();
|
||||
uint256 idCommitment = 2;
|
||||
(, uint256 price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
vm.assume(
|
||||
w.minMembershipRateLimit() <= membershipRateLimit && membershipRateLimit <= w.maxMembershipRateLimit()
|
||||
);
|
||||
vm.assume(w.isValidMembershipRateLimit(membershipRateLimit));
|
||||
vm.resumeGasMetering();
|
||||
|
||||
token.approve(address(w), price);
|
||||
w.register(idCommitment, membershipRateLimit, noIdCommitmentsToErase);
|
||||
|
||||
(,, uint256 fetchedgracePeriodStartTimestamp, uint32 fetchedGracePeriod,,,,) = w.memberships(idCommitment);
|
||||
|
||||
uint256 expectedExpirationTimestamp = fetchedgracePeriodStartTimestamp + uint256(fetchedGracePeriod);
|
||||
uint256 membershipExpirationTimestamp = w.membershipExpirationTimestamp(idCommitment);
|
||||
|
||||
assertEq(expectedExpirationTimestamp, membershipExpirationTimestamp);
|
||||
|
||||
vm.warp(membershipExpirationTimestamp);
|
||||
|
||||
assertFalse(w.isInGracePeriod(idCommitment));
|
||||
assertTrue(w.isExpired(idCommitment));
|
||||
}
|
||||
|
||||
function test__ValidRegistrationWithEraseList() external {
|
||||
vm.pauseGasMetering();
|
||||
vm.startPrank(w.owner());
|
||||
w.setMinMembershipRateLimit(20);
|
||||
w.setMaxMembershipRateLimit(100);
|
||||
w.setMaxTotalRateLimit(100);
|
||||
vm.stopPrank();
|
||||
vm.resumeGasMetering();
|
||||
|
||||
(, uint256 priceA) = w.priceCalculator().calculate(20);
|
||||
|
||||
for (uint256 i = 1; i <= 5; i++) {
|
||||
token.approve(address(w), priceA);
|
||||
w.register(i, 20, noIdCommitmentsToErase);
|
||||
// Make sure they're expired
|
||||
vm.warp(w.membershipExpirationTimestamp(i));
|
||||
}
|
||||
|
||||
// Time travel to a point in which the last membership is active
|
||||
(,, uint256 gracePeriodStartTimestamp,,,,,) = w.memberships(5);
|
||||
vm.warp(gracePeriodStartTimestamp - 1);
|
||||
|
||||
// Ensure that this is the case
|
||||
assertTrue(w.isExpired(4));
|
||||
assertFalse(w.isExpired(5));
|
||||
assertFalse(w.isInGracePeriod(5));
|
||||
|
||||
(, uint256 priceB) = w.priceCalculator().calculate(60);
|
||||
token.approve(address(w), priceB);
|
||||
|
||||
// Should fail. There's not enough free rate limit
|
||||
vm.expectRevert(abi.encodeWithSelector(CannotExceedMaxTotalRateLimit.selector));
|
||||
w.register(6, 60, noIdCommitmentsToErase);
|
||||
|
||||
// Attempt to erase 3 memberships including one that can't be erased (the last one)
|
||||
uint256[] memory commitmentsToErase = new uint256[](3);
|
||||
commitmentsToErase[0] = 1;
|
||||
commitmentsToErase[1] = 2;
|
||||
commitmentsToErase[2] = 5; // This one is still active
|
||||
token.approve(address(w), priceB);
|
||||
vm.expectRevert(abi.encodeWithSelector(CannotEraseActiveMembership.selector, 5));
|
||||
w.register(6, 60, commitmentsToErase);
|
||||
|
||||
// Attempt to erase 3 memberships that can be erased
|
||||
commitmentsToErase[2] = 4;
|
||||
vm.expectEmit(true, false, false, false);
|
||||
emit MembershipUpgradeable.MembershipExpired(1, 0, 0);
|
||||
vm.expectEmit(true, false, false, false);
|
||||
emit MembershipUpgradeable.MembershipExpired(2, 0, 0);
|
||||
vm.expectEmit(true, false, false, false);
|
||||
emit MembershipUpgradeable.MembershipExpired(4, 0, 0);
|
||||
w.register(6, 60, commitmentsToErase);
|
||||
|
||||
// Ensure that the chosen memberships were erased and others unaffected
|
||||
address holder;
|
||||
(,,,,,, holder,) = w.memberships(1);
|
||||
assertEq(holder, address(0));
|
||||
(,,,,,, holder,) = w.memberships(2);
|
||||
assertEq(holder, address(0));
|
||||
(,,,,,, holder,) = w.memberships(3);
|
||||
assertEq(holder, address(this));
|
||||
(,,,,,, holder,) = w.memberships(4);
|
||||
assertEq(holder, address(0));
|
||||
(,,,,,, holder,) = w.memberships(5);
|
||||
assertEq(holder, address(this));
|
||||
(,,,,,, holder,) = w.memberships(6);
|
||||
assertEq(holder, address(this));
|
||||
|
||||
// The balance available for withdrawal should match the amount of the expired membership
|
||||
uint256 availableBalance = w.depositsToWithdraw(address(this), address(token));
|
||||
assertEq(availableBalance, priceA * 3);
|
||||
}
|
||||
|
||||
function test__RegistrationWhenMaxRateLimitIsReached() external {
|
||||
vm.pauseGasMetering();
|
||||
vm.startPrank(w.owner());
|
||||
w.setMinMembershipRateLimit(1);
|
||||
w.setMaxMembershipRateLimit(5);
|
||||
w.setMaxTotalRateLimit(5);
|
||||
vm.stopPrank();
|
||||
vm.resumeGasMetering();
|
||||
|
||||
bool isValid = w.isValidMembershipRateLimit(6);
|
||||
assertFalse(isValid);
|
||||
|
||||
// Exceeds the max rate limit per membership
|
||||
uint32 membershipRateLimit = 10;
|
||||
(, uint256 price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
token.approve(address(w), price);
|
||||
vm.expectRevert(abi.encodeWithSelector(InvalidMembershipRateLimit.selector));
|
||||
w.register(1, membershipRateLimit, noIdCommitmentsToErase);
|
||||
|
||||
// Should register succesfully
|
||||
membershipRateLimit = 4;
|
||||
(, price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
token.approve(address(w), price);
|
||||
w.register(2, membershipRateLimit, noIdCommitmentsToErase);
|
||||
|
||||
// Exceeds the rate limit
|
||||
membershipRateLimit = 2;
|
||||
(, price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
token.approve(address(w), price);
|
||||
vm.expectRevert(abi.encodeWithSelector(CannotExceedMaxTotalRateLimit.selector));
|
||||
w.register(3, membershipRateLimit, noIdCommitmentsToErase);
|
||||
|
||||
// Should register succesfully
|
||||
membershipRateLimit = 1;
|
||||
(, price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
token.approve(address(w), price);
|
||||
w.register(3, membershipRateLimit, noIdCommitmentsToErase);
|
||||
|
||||
// We ran out of rate limit again
|
||||
membershipRateLimit = 1;
|
||||
(, price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
token.approve(address(w), price);
|
||||
vm.expectRevert(abi.encodeWithSelector(CannotExceedMaxTotalRateLimit.selector));
|
||||
w.register(4, membershipRateLimit, noIdCommitmentsToErase);
|
||||
}
|
||||
|
||||
function test__indexReuse_eraseMemberships(uint32 idCommitmentsLength) external {
|
||||
vm.assume(0 < idCommitmentsLength && idCommitmentsLength < 50);
|
||||
|
||||
(, uint256 price) = w.priceCalculator().calculate(20);
|
||||
uint32 index;
|
||||
uint256[] memory commitmentsToErase = new uint256[](idCommitmentsLength);
|
||||
uint256 time = block.timestamp;
|
||||
for (uint256 i = 1; i <= idCommitmentsLength; i++) {
|
||||
token.approve(address(w), price);
|
||||
w.register(i, 20, noIdCommitmentsToErase);
|
||||
(,,,,, index,,) = w.memberships(i);
|
||||
assertEq(index, w.nextFreeIndex() - 1);
|
||||
commitmentsToErase[i - 1] = i;
|
||||
time += 100;
|
||||
vm.warp(time);
|
||||
}
|
||||
|
||||
// None of the commitments can be deleted because they're still active
|
||||
uint256[] memory singleCommitmentToErase = new uint256[](1);
|
||||
for (uint256 i = 1; i <= idCommitmentsLength; i++) {
|
||||
singleCommitmentToErase[0] = i;
|
||||
vm.expectRevert(abi.encodeWithSelector(CannotEraseActiveMembership.selector, i));
|
||||
w.eraseMemberships(singleCommitmentToErase);
|
||||
}
|
||||
|
||||
// Fastfwd to commitment grace period, and try to erase it without being the owner
|
||||
(,, uint256 gracePeriodStartTimestamp,,,,,) = w.memberships(1);
|
||||
vm.warp(gracePeriodStartTimestamp);
|
||||
assertTrue(w.isInGracePeriod(1));
|
||||
singleCommitmentToErase[0] = 1;
|
||||
address randomAddress = vm.addr(block.timestamp);
|
||||
vm.prank(randomAddress);
|
||||
vm.expectRevert(abi.encodeWithSelector(NonHolderCannotEraseGracePeriodMembership.selector, 1));
|
||||
w.eraseMemberships(singleCommitmentToErase);
|
||||
|
||||
// time travel to the moment we can erase all expired memberships
|
||||
uint256 membershipExpirationTimestamp = w.membershipExpirationTimestamp(idCommitmentsLength);
|
||||
vm.warp(membershipExpirationTimestamp);
|
||||
w.eraseMemberships(commitmentsToErase);
|
||||
|
||||
// Verify that expired indices match what we expect
|
||||
for (uint32 i = 0; i < idCommitmentsLength; i++) {
|
||||
assertEq(i, w.indicesOfLazilyErasedMemberships(i));
|
||||
}
|
||||
|
||||
uint32 expectedNextFreeIndex = w.nextFreeIndex();
|
||||
for (uint256 i = 1; i <= idCommitmentsLength; i++) {
|
||||
uint256 idCommitment = i + 10;
|
||||
uint256 expectedindexReusedPos = idCommitmentsLength - i;
|
||||
uint32 expectedReusedIndex = w.indicesOfLazilyErasedMemberships(expectedindexReusedPos);
|
||||
token.approve(address(w), price);
|
||||
w.register(idCommitment, 20, noIdCommitmentsToErase);
|
||||
(,,,,, index,,) = w.memberships(idCommitment);
|
||||
assertEq(expectedReusedIndex, index);
|
||||
// Should have been removed from the list
|
||||
vm.expectRevert();
|
||||
w.indicesOfLazilyErasedMemberships(expectedindexReusedPos);
|
||||
// Should not have been affected
|
||||
assertEq(expectedNextFreeIndex, w.nextFreeIndex());
|
||||
}
|
||||
|
||||
// No indices should be available for reuse
|
||||
vm.expectRevert();
|
||||
w.indicesOfLazilyErasedMemberships(0);
|
||||
|
||||
// Should use a new index since we got rid of all reusable indexes
|
||||
token.approve(address(w), price);
|
||||
w.register(100, 20, noIdCommitmentsToErase);
|
||||
(,,,,, index,,) = w.memberships(100);
|
||||
assertEq(index, expectedNextFreeIndex);
|
||||
assertEq(expectedNextFreeIndex + 1, w.nextFreeIndex());
|
||||
}
|
||||
|
||||
function test__RemoveExpiredMemberships(uint32 membershipRateLimit) external {
|
||||
vm.pauseGasMetering();
|
||||
uint256 idCommitment = 2;
|
||||
(, uint256 price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
vm.assume(
|
||||
w.minMembershipRateLimit() <= membershipRateLimit && membershipRateLimit <= w.maxMembershipRateLimit()
|
||||
);
|
||||
vm.assume(w.isValidMembershipRateLimit(membershipRateLimit));
|
||||
vm.resumeGasMetering();
|
||||
|
||||
uint256 time = block.timestamp;
|
||||
for (uint256 i = 0; i < 5; i++) {
|
||||
token.approve(address(w), price);
|
||||
w.register(idCommitment + i, membershipRateLimit, noIdCommitmentsToErase);
|
||||
time += 100;
|
||||
vm.warp(time);
|
||||
}
|
||||
|
||||
// Expiring the first 3 memberships
|
||||
uint256 membershipExpirationTimestamp = w.membershipExpirationTimestamp(idCommitment + 2);
|
||||
vm.warp(membershipExpirationTimestamp);
|
||||
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;
|
||||
|
||||
vm.expectEmit(true, false, false, false); // only check the first parameter of the event (the idCommitment)
|
||||
emit MembershipUpgradeable.MembershipExpired(commitmentsToErase[0], 0, 0);
|
||||
vm.expectEmit(true, false, false, false); // only check the first parameter of the event (the idCommitment)
|
||||
emit MembershipUpgradeable.MembershipExpired(commitmentsToErase[0], 0, 0);
|
||||
w.eraseMemberships(commitmentsToErase);
|
||||
|
||||
address holder;
|
||||
|
||||
(,,,,,, holder,) = w.memberships(idCommitment + 1);
|
||||
assertEq(holder, address(0));
|
||||
|
||||
(,,,,,, holder,) = w.memberships(idCommitment + 2);
|
||||
assertEq(holder, address(0));
|
||||
|
||||
// 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 gracePeriodStartTimestamp,,,,,) = w.memberships(idCommitment + 4);
|
||||
vm.warp(gracePeriodStartTimestamp - 1);
|
||||
commitmentsToErase[0] = idCommitment;
|
||||
commitmentsToErase[1] = idCommitment + 4;
|
||||
vm.expectRevert(abi.encodeWithSelector(CannotEraseActiveMembership.selector, idCommitment + 4));
|
||||
w.eraseMemberships(commitmentsToErase);
|
||||
}
|
||||
|
||||
function test__RemoveAllExpiredMemberships(uint32 idCommitmentsLength) external {
|
||||
vm.pauseGasMetering();
|
||||
vm.assume(1 < idCommitmentsLength && idCommitmentsLength <= 100);
|
||||
uint32 membershipRateLimit = w.minMembershipRateLimit();
|
||||
(, uint256 price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
vm.resumeGasMetering();
|
||||
|
||||
uint256 time = block.timestamp;
|
||||
for (uint256 i = 1; i <= idCommitmentsLength; i++) {
|
||||
token.approve(address(w), price);
|
||||
w.register(i, membershipRateLimit, noIdCommitmentsToErase);
|
||||
time += 100;
|
||||
vm.warp(time);
|
||||
}
|
||||
|
||||
uint256 membershipExpirationTimestamp = w.membershipExpirationTimestamp(idCommitmentsLength);
|
||||
vm.warp(membershipExpirationTimestamp);
|
||||
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;
|
||||
vm.expectEmit(true, false, false, false); // only check the first parameter of the event (the idCommitment)
|
||||
emit MembershipUpgradeable.MembershipExpired(i + 1, 0, 0);
|
||||
}
|
||||
|
||||
w.eraseMemberships(commitmentsToErase);
|
||||
|
||||
// Erased memberships are gone!
|
||||
for (uint256 i = 0; i < commitmentsToErase.length; i++) {
|
||||
(,,,, uint32 fetchedMembershipRateLimit,,,) = w.memberships(commitmentsToErase[i]);
|
||||
assertEq(fetchedMembershipRateLimit, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function test__WithdrawToken(uint32 membershipRateLimit) external {
|
||||
vm.pauseGasMetering();
|
||||
uint256 idCommitment = 2;
|
||||
LinearPriceCalculator priceCalculator = LinearPriceCalculator(address(w.priceCalculator()));
|
||||
vm.prank(priceCalculator.owner());
|
||||
priceCalculator.setTokenAndPrice(address(token), 5 wei);
|
||||
(, uint256 price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
token.mint(address(this), price);
|
||||
vm.assume(
|
||||
w.minMembershipRateLimit() <= membershipRateLimit && membershipRateLimit <= w.maxMembershipRateLimit()
|
||||
);
|
||||
vm.assume(w.isValidMembershipRateLimit(membershipRateLimit));
|
||||
vm.resumeGasMetering();
|
||||
|
||||
token.approve(address(w), price);
|
||||
w.register(idCommitment, membershipRateLimit, noIdCommitmentsToErase);
|
||||
|
||||
(,, uint256 gracePeriodStartTimestamp,,,,,) = w.memberships(idCommitment);
|
||||
|
||||
vm.warp(gracePeriodStartTimestamp);
|
||||
|
||||
uint256[] memory commitmentsToErase = new uint256[](1);
|
||||
commitmentsToErase[0] = idCommitment;
|
||||
w.eraseMemberships(commitmentsToErase);
|
||||
|
||||
uint256 availableBalance = w.depositsToWithdraw(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.depositsToWithdraw(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);
|
||||
vm.expectRevert(DuplicateIdCommitment.selector);
|
||||
w.register(idCommitment, userMessageLimit);
|
||||
uint32 membershipRateLimit = w.minMembershipRateLimit();
|
||||
(, uint256 price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
vm.resumeGasMetering();
|
||||
|
||||
token.approve(address(w), price);
|
||||
w.register(idCommitment, membershipRateLimit, noIdCommitmentsToErase);
|
||||
|
||||
token.approve(address(w), price);
|
||||
vm.expectRevert(bytes("Duplicate idCommitment: membership already exists"));
|
||||
w.register(idCommitment, membershipRateLimit, noIdCommitmentsToErase);
|
||||
}
|
||||
|
||||
function test__InvalidRegistration__FullTree() external {
|
||||
uint32 userMessageLimit = 2;
|
||||
vm.pauseGasMetering();
|
||||
uint32 membershipRateLimit = 20;
|
||||
(, uint256 price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
vm.resumeGasMetering();
|
||||
|
||||
// we progress the tree to the last leaf
|
||||
|
||||
/*| Name | Type | Slot | Offset | Bytes |
|
||||
|---------------------|-----------------------------------------------------|------|--------|-------|
|
||||
| MAX_MESSAGE_LIMIT | uint32 | 0 | 0 | 4 |
|
||||
| SET_SIZE | uint32 | 0 | 4 | 4 |
|
||||
| commitmentIndex | uint32 | 0 | 8 | 4 |
|
||||
| memberInfo | mapping(uint256 => struct WakuRlnV2.MembershipInfo) | 1 | 0 | 32 |
|
||||
| deployedBlockNumber | uint32 | 2 | 0 | 4 |
|
||||
| imtData | struct LazyIMTData | 3 | 0 | 64 |*/
|
||||
// we set MAX_MESSAGE_LIMIT to 20 (unaltered)
|
||||
// we set SET_SIZE to 4294967295 (1 << 20) (unaltered)
|
||||
// we set commitmentIndex to 4294967295 (1 << 20) (altered)
|
||||
vm.store(address(w), bytes32(uint256(201)), 0x0000000000000000000000000000000000000000ffffffffffffffff00000014);
|
||||
vm.expectRevert(FullTree.selector);
|
||||
w.register(1, userMessageLimit);
|
||||
| nextFreeIndex | uint32 | 206 | 0 | 4 | */
|
||||
/*
|
||||
Pro tip: to easily find the storage slot of a variable, without having to calculate the storage layout
|
||||
based on the variable declaration, set the variable to an easily grepable value like 0xDEADBEEF, and then
|
||||
execute:
|
||||
```
|
||||
for (uint256 i = 0; i <= 500; i++) {
|
||||
bytes32 slot0Value = vm.load(address(w), bytes32(i));
|
||||
console.log("%s", i);
|
||||
console.logBytes32(slot0Value);
|
||||
}
|
||||
revert();
|
||||
```
|
||||
Search the value in the output (i.e. `DEADBEEF`) to determine the storage slot being used.
|
||||
If the storage layout changes, update the next line accordingly
|
||||
*/
|
||||
|
||||
// we set nextFreeIndex to 4294967295 (1 << 20) = 0x00100000
|
||||
vm.store(address(w), bytes32(uint256(206)), 0x0000000000000000000000000000000000000000000000000000000000100000);
|
||||
token.approve(address(w), price);
|
||||
vm.expectRevert(bytes("Membership set is full"));
|
||||
w.register(1, membershipRateLimit, noIdCommitmentsToErase);
|
||||
}
|
||||
|
||||
function test__InvalidPaginationQuery__StartIndexGTEndIndex() external {
|
||||
vm.expectRevert(abi.encodeWithSelector(InvalidPaginationQuery.selector, 1, 0));
|
||||
w.getCommitments(1, 0);
|
||||
w.getRateCommitmentsInRangeBoundsInclusive(1, 0);
|
||||
}
|
||||
|
||||
function test__InvalidPaginationQuery__EndIndexGTcommitmentIndex() external {
|
||||
function test__InvalidPaginationQuery__EndIndexGTNextFreeIndex() external {
|
||||
vm.expectRevert(abi.encodeWithSelector(InvalidPaginationQuery.selector, 0, 2));
|
||||
w.getCommitments(0, 2);
|
||||
w.getRateCommitmentsInRangeBoundsInclusive(0, 2);
|
||||
}
|
||||
|
||||
function test__ValidPaginationQuery__OneElement() external {
|
||||
uint32 userMessageLimit = 2;
|
||||
vm.pauseGasMetering();
|
||||
uint256 idCommitment = 1;
|
||||
w.register(idCommitment, userMessageLimit);
|
||||
uint256[] memory commitments = w.getCommitments(0, 0);
|
||||
uint32 membershipRateLimit = w.minMembershipRateLimit();
|
||||
(, uint256 price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
vm.resumeGasMetering();
|
||||
|
||||
token.approve(address(w), price);
|
||||
w.register(idCommitment, membershipRateLimit, noIdCommitmentsToErase);
|
||||
uint256[] memory commitments = w.getRateCommitmentsInRangeBoundsInclusive(0, 0);
|
||||
assertEq(commitments.length, 1);
|
||||
uint256 rateCommitment = PoseidonT3.hash([idCommitment, userMessageLimit]);
|
||||
uint256 rateCommitment = PoseidonT3.hash([idCommitment, membershipRateLimit]);
|
||||
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.assume(0 < idCommitmentsLength && idCommitmentsLength <= 100);
|
||||
uint32 membershipRateLimit = w.minMembershipRateLimit();
|
||||
(, uint256 price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
|
||||
for (uint256 i = 0; i <= idCommitmentsLength; i++) {
|
||||
token.approve(address(w), price);
|
||||
w.register(i + 1, membershipRateLimit, noIdCommitmentsToErase);
|
||||
}
|
||||
vm.resumeGasMetering();
|
||||
|
||||
uint256[] memory commitments = w.getCommitments(0, idCommitmentsLength);
|
||||
assertEq(commitments.length, idCommitmentsLength + 1);
|
||||
uint256[] memory rateCommitments = w.getRateCommitmentsInRangeBoundsInclusive(0, idCommitmentsLength - 1);
|
||||
assertEq(rateCommitments.length, idCommitmentsLength);
|
||||
for (uint256 i = 0; i < idCommitmentsLength; i++) {
|
||||
uint256 rateCommitment = PoseidonT3.hash([i + 1, userMessageLimit]);
|
||||
assertEq(commitments[i], rateCommitment);
|
||||
uint256 rateCommitment = PoseidonT3.hash([i + 1, membershipRateLimit]);
|
||||
assertEq(rateCommitments[i], rateCommitment);
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user