mirror of
https://github.com/logos-messaging/logos-messaging-rlnv2-contract.git
synced 2026-01-25 09:13:06 +00:00
299 lines
12 KiB
Solidity
299 lines
12 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity 0.8.24;
|
|
|
|
import { LazyIMT, LazyIMTData } from "@zk-kit/imt.sol/LazyIMT.sol";
|
|
import { PoseidonT3 } from "poseidon-solidity/PoseidonT3.sol";
|
|
|
|
import { Ownable2StepUpgradeable } from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
|
|
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
|
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
|
|
|
import { MembershipUpgradeable } from "./Membership.sol";
|
|
import { IPriceCalculator } from "./IPriceCalculator.sol";
|
|
|
|
/// The tree is full
|
|
error FullTree();
|
|
|
|
/// Member 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, Ownable2StepUpgradeable, 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 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 deployed block number
|
|
uint32 public deployedBlockNumber;
|
|
|
|
/// @notice the stored imt data
|
|
LazyIMTData public imtData;
|
|
|
|
/// 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
|
|
modifier onlyValidIdCommitment(uint256 idCommitment) {
|
|
if (!isValidCommitment(idCommitment)) revert InvalidIdCommitment(idCommitment);
|
|
_;
|
|
}
|
|
|
|
constructor() {
|
|
_disableInitializers();
|
|
}
|
|
|
|
/// @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 initialize(
|
|
address _priceCalculator,
|
|
uint32 _maxTotalRateLimitPerEpoch,
|
|
uint32 _minRateLimitPerMembership,
|
|
uint32 _maxRateLimitPerMembership,
|
|
uint32 _expirationTerm,
|
|
uint32 _gracePeriod
|
|
)
|
|
public
|
|
initializer
|
|
{
|
|
__Ownable_init();
|
|
__UUPSUpgradeable_init();
|
|
__MembershipUpgradeable_init(
|
|
_priceCalculator,
|
|
_maxTotalRateLimitPerEpoch,
|
|
_minRateLimitPerMembership,
|
|
_maxRateLimitPerMembership,
|
|
_expirationTerm,
|
|
_gracePeriod
|
|
);
|
|
|
|
SET_SIZE = uint32(1 << DEPTH);
|
|
deployedBlockNumber = uint32(block.number);
|
|
LazyIMT.init(imtData, DEPTH);
|
|
nextCommitmentIndex = 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 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 mdetails = members[idCommitment];
|
|
// we cannot call indexToCommitment for 0 index if the member doesn't exist
|
|
if (mdetails.userMessageLimit == 0) {
|
|
return (0, 0, 0);
|
|
}
|
|
return (mdetails.userMessageLimit, mdetails.index, indexToCommitment(mdetails.index));
|
|
}
|
|
|
|
/// @notice Checks if a member exists
|
|
/// @param idCommitment The idCommitment of the member
|
|
/// @return true if the member exists, false otherwise
|
|
function memberExists(uint256 idCommitment) public view returns (bool) {
|
|
(,, uint256 rateCommitment) = idCommitmentToMetadata(idCommitment);
|
|
return rateCommitment != 0;
|
|
}
|
|
|
|
/// @notice Allows a user to register as a member
|
|
/// @param idCommitment The idCommitment of the member
|
|
/// @param userMessageLimit The message limit of the member
|
|
function register(uint256 idCommitment, uint32 userMessageLimit) external onlyValidIdCommitment(idCommitment) {
|
|
if (memberExists(idCommitment)) revert DuplicateIdCommitment();
|
|
|
|
uint32 index;
|
|
bool reusedIndex;
|
|
(index, reusedIndex) = _acquireMembership(_msgSender(), idCommitment, userMessageLimit);
|
|
|
|
_register(idCommitment, userMessageLimit, index, reusedIndex);
|
|
}
|
|
|
|
/// @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 membershipsToErase List of expired idCommitments to erase
|
|
function register(
|
|
uint256 idCommitment,
|
|
uint32 userMessageLimit,
|
|
uint256[] calldata membershipsToErase
|
|
)
|
|
external
|
|
onlyValidIdCommitment(idCommitment)
|
|
{
|
|
if (memberExists(idCommitment)) revert DuplicateIdCommitment();
|
|
|
|
for (uint256 i = 0; i < membershipsToErase.length; i++) {
|
|
uint256 idCommitmentToErase = membershipsToErase[i];
|
|
MembershipInfo memory mdetails = members[idCommitmentToErase];
|
|
if (mdetails.userMessageLimit == 0) revert InvalidIdCommitment(idCommitmentToErase);
|
|
_eraseMembership(_msgSender(), idCommitmentToErase, mdetails);
|
|
LazyIMT.update(imtData, 0, mdetails.index);
|
|
}
|
|
|
|
uint32 index;
|
|
bool reusedIndex;
|
|
(index, reusedIndex) = _acquireMembership(_msgSender(), idCommitment, userMessageLimit);
|
|
|
|
_register(idCommitment, userMessageLimit, index, reusedIndex);
|
|
}
|
|
|
|
/// @dev Registers a member
|
|
/// @param idCommitment The idCommitment of the member
|
|
/// @param userMessageLimit The message limit of the member
|
|
/// @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 (nextCommitmentIndex >= SET_SIZE) revert FullTree();
|
|
|
|
uint256 rateCommitment = PoseidonT3.hash([idCommitment, userMessageLimit]);
|
|
if (reusedIndex) {
|
|
LazyIMT.update(imtData, rateCommitment, index);
|
|
} else {
|
|
LazyIMT.insert(imtData, rateCommitment);
|
|
nextCommitmentIndex += 1;
|
|
}
|
|
|
|
emit MemberRegistered(rateCommitment, index);
|
|
}
|
|
|
|
/// @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 > nextCommitmentIndex) revert InvalidPaginationQuery(startIndex, endIndex);
|
|
|
|
uint256[] memory commitments = new uint256[](endIndex - startIndex + 1);
|
|
for (uint32 i = startIndex; i <= endIndex; i++) {
|
|
commitments[i - startIndex] = indexToCommitment(i);
|
|
}
|
|
return commitments;
|
|
}
|
|
|
|
/// @notice Returns the root of the IMT
|
|
/// @return The root of the IMT
|
|
function root() external view returns (uint256) {
|
|
return LazyIMT.root(imtData, DEPTH);
|
|
}
|
|
|
|
/// @notice Returns the merkle proof elements of a given membership
|
|
/// @param index The index of the member
|
|
/// @return The merkle proof elements of the member
|
|
function merkleProofElements(uint40 index) public view returns (uint256[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];
|
|
}
|
|
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];
|
|
if (mdetails.userMessageLimit == 0) revert InvalidIdCommitment(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 {
|
|
require(_maxTotalRateLimitPerEpoch >= maxRateLimitPerMembership);
|
|
maxTotalRateLimitPerEpoch = _maxTotalRateLimitPerEpoch;
|
|
}
|
|
|
|
/// @notice Set the maximum rate limit of one membership
|
|
/// @param _maxRateLimitPerMembership new value
|
|
function setMaxRateLimitPerMembership(uint32 _maxRateLimitPerMembership) external onlyOwner {
|
|
require(_maxRateLimitPerMembership >= minRateLimitPerMembership);
|
|
maxRateLimitPerMembership = _maxRateLimitPerMembership;
|
|
}
|
|
|
|
/// @notice Set the minimum rate limit of one membership
|
|
/// @param _minRateLimitPerMembership new value
|
|
function setMinRateLimitPerMembership(uint32 _minRateLimitPerMembership) external onlyOwner {
|
|
require(_minRateLimitPerMembership > 0);
|
|
minRateLimitPerMembership = _minRateLimitPerMembership;
|
|
}
|
|
|
|
/// @notice Set the membership expiration term
|
|
/// @param _expirationTerm new value
|
|
function setExpirationTerm(uint32 _expirationTerm) external onlyOwner {
|
|
require(_expirationTerm > 0);
|
|
expirationTerm = _expirationTerm;
|
|
}
|
|
|
|
/// @notice Set the membership grace period
|
|
/// @param _gracePeriod new value
|
|
function setGracePeriod(uint32 _gracePeriod) external onlyOwner {
|
|
gracePeriod = _gracePeriod;
|
|
}
|
|
}
|