feat: store membership details

This commit is contained in:
Richard Ramos 2024-08-27 11:58:49 -04:00
parent e800707d6c
commit 9d2518dc16
No known key found for this signature in database
GPG Key ID: 1CE87DB518195760
3 changed files with 162 additions and 41 deletions

View File

@ -5,4 +5,4 @@ interface IPriceCalculator {
/// Returns the token and price to pay in `token` for some `_rateLimit`
/// @param _rateLimit the rate limit the user wants to acquire
function calculate(uint _rateLimit) external view returns (address, uint);
}
}

View File

@ -12,85 +12,208 @@ error TokenMismatch();
error InvalidRateLimit();
error ExceedMaxRateLimitPerEpoch();
error NotInGracePeriod(uint256 membershipMapIdx);
error NotHolder(uint256 membershipMapIdx);
contract Membership {
using SafeERC20 for IERC20;
IPriceCalculator public priceCalculator;
// TODO START - add owned setters to all these variables
uint public maxTotalRateLimitPerEpoch;
IPriceCalculator public priceCalculator;
uint256 public maxTotalRateLimitPerEpoch;
uint16 public maxRateLimitPerMembership;
uint16 public minRateLimitPerMembership;
enum MembershipStatus { Undefined, Active, GracePeriod, Expired, ErasedAwaitsWithdrawal, Erased }
uint256 public expirationTerm;
uint256 public gracePeriod;
// TODO END
enum MembershipStatus {
NonExistent,
Active,
GracePeriod,
Expired,
ErasedAwaitsWithdrawal,
Erased
}
uint public totalRateLimitPerEpoch;
mapping(uint256 => MembershipDetails) public memberships;
uint256 public oldestMembership = 1;
uint256 public newestMembership = 1;
struct MembershipDetails {
address holder;
// TODO: should we store the commitment?
uint256 expirationDate;
address token;
uint256 amount;
}
event ExpiredMembership(uint256 membershipMapIndex, address holder); // TODO: should it contain the commitment?
event MembershipExtended(
uint256 membershipMapIndex,
uint256 newExpirationDate
); // TODO: should it contain the commitment?
function __Membership_init(
address _priceCalculator,
uint _maxTotalRateLimitPerEpoch,
uint16 _maxRateLimitPerMembership,
uint16 _minRateLimitPerMembership
uint16 _minRateLimitPerMembership,
uint _expirationTerm,
uint _gracePeriod
) internal {
priceCalculator = IPriceCalculator(_priceCalculator);
maxTotalRateLimitPerEpoch = _maxTotalRateLimitPerEpoch;
maxRateLimitPerMembership = _maxRateLimitPerMembership;
minRateLimitPerMembership = _minRateLimitPerMembership;
expirationTerm = _expirationTerm;
gracePeriod = _gracePeriod;
}
function transferMembershipFees(address _from, uint _rateLimit) internal {
(address token, uint price) = priceCalculator.calculate(_rateLimit);
if (token == address(0)) {
if (msg.value != price) revert IncorrectAmount();
function registerMembership(
address _sender,
uint256[] memory commitments,
uint _rateLimit
) internal {
// TODO: for each commitment?
(address token, uint amount) = priceCalculator.calculate(_rateLimit);
acquireRateLimit(_sender, commitments, _rateLimit, token, amount);
transferMembershipFees(
_sender,
token,
amount * _rateLimit * commitments.length
);
}
function transferMembershipFees(
address _from,
address _token,
uint _amount
) internal {
if (_token == address(0)) {
if (msg.value != _amount) revert IncorrectAmount();
} else {
if (msg.value != 0) revert OnlyTokensAccepted();
IERC20(token).safeTransferFrom(_from, address(this), price);
IERC20(_token).safeTransferFrom(_from, address(this), _amount);
}
}
function acquireRateLimit(uint256[] memory commitments, uint _rateLimit) internal {
function acquireRateLimit(
address _sender,
uint256[] memory _commitments,
uint256 _rateLimit,
address _token,
uint256 _amount
) internal {
// TODO: for each commitment?
if (
_rateLimit < minRateLimitPerMembership ||
_rateLimit > maxRateLimitPerMembership
) revert InvalidRateLimit();
uint newTotalRateLimitPerEpoch = totalRateLimitPerEpoch + _rateLimit;
if (newTotalRateLimitPerEpoch > maxTotalRateLimitPerEpoch) revert ExceedMaxRateLimitPerEpoch();
// TODO: store _rateLimit
// TODO:
// Epoch length epoch 10 minutes
// Membership expiration term T 180 days
// Membership grace period G 30 days
if (newTotalRateLimitPerEpoch > maxTotalRateLimitPerEpoch) {
// Determine if there are any available spot in the membership map
// by looking at the oldest membership. If it's expired, we can use it
MembershipDetails storage oldestMembershipDetails = memberships[
oldestMembership
];
if (isExpired(oldestMembershipDetails.expirationDate)) {
emit ExpiredMembership(
oldestMembership,
memberships[oldestMembership].holder
);
deleteOldestMembership();
// TODO: move balance from expired to the current holder
} else {
revert ExceedMaxRateLimitPerEpoch();
}
}
newestMembership += 1;
memberships[newestMembership] = MembershipDetails({
holder: _sender,
expirationDate: block.timestamp + expirationTerm,
token: _token,
amount: _amount
});
}
mapping (uint256 => TODO) memberships;
uint256 private oldest = 1;
uint256 private newest = 1;
function extendMembership(
address _sender,
uint256[] memory membershipMapIdx
) public {
for (uint256 i = 0; i < membershipMapIdx.length; i++) {
uint256 currentMembershipMapIdx = membershipMapIdx[i];
// TODO - use a struct to store the membership details: commitment, date, status
// TODO - keep track of balances, use msg.sender?
MembershipDetails storage mdetails = memberships[
currentMembershipMapIdx
];
function pushMembership(TODO data) internal {
newest += 1;
memberships[newest] = data;
if (!_isGracePeriod(mdetails.expirationDate))
revert NotInGracePeriod(currentMembershipMapIdx);
if (_sender != mdetails.holder)
revert NotHolder(currentMembershipMapIdx);
uint256 newExpirationDate = block.timestamp + expirationTerm;
// TODO: remove current membership
// TODO: add membership at the end (since it will be the newest)
emit MembershipExtended(currentMembershipMapIdx, newExpirationDate);
}
}
function oldestMembership() public TODO {
return memberships[oldest];
function _isExpired(uint256 expirationDate) internal view returns (bool) {
return expirationDate + gracePeriod > block.timestamp;
}
function popOldestMembership() public returns (uint256) {
TODO data;
require(newest > oldest);
data = memberships[oldest];
delete memberships[oldest];
oldest += 1;
return data;
function isExpired(uint256 membershipMapIdx) public view returns (bool) {
return _isExpired(memberships[membershipMapIdx].expirationDate);
}
function length() public view returns (uint256) {
return newest - oldest;
function _isGracePeriod(
uint256 expirationDate
) internal view returns (bool) {
return
block.timestamp >= expirationDate &&
block.timestamp <= expirationDate + gracePeriod;
}
function isGracePeriod(
uint256 membershipMapIdx
) public view returns (bool) {
uint256 expirationDate = memberships[membershipMapIdx].expirationDate;
return _isGracePeriod(expirationDate);
}
function withdraw() public {}
// TODO - keep track of balances, use msg.sender
function getOldestMembership()
public
view
returns (MembershipDetails memory)
{
return memberships[oldestMembership];
}
function deleteOldestMembership() internal {
require(newestMembership > oldestMembership);
delete memberships[oldestMembership];
oldestMembership += 1;
}
function getMembershipLength() public view returns (uint256) {
return newestMembership - oldestMembership;
}
}

View File

@ -83,8 +83,7 @@ contract WakuRlnRegistry is OwnableUpgradeable, UUPSUpgradeable, Membership {
function register(uint16 storageIndex, uint256[] calldata commitments) external onlyValidStorageIndex(storageIndex) {
// TODO: modify function to receive the ratelimit to buy
uint _rateLimit = 4;
acquireRateLimit(commitments, _rateLimit);
transferMembershipFees(_msgSender(), _rateLimit * commitments.length);
registerMembership(_msgSender(), commitments, _rateLimit);
WakuRln(storages[storageIndex]).register(commitments);
}
@ -95,8 +94,7 @@ contract WakuRlnRegistry is OwnableUpgradeable, UUPSUpgradeable, Membership {
// TODO: modify function to receive the number of messages
uint _rateLimit = 4;
acquireRateLimit(commitments, _rateLimit);
transferMembershipFees(_msgSender(), _rateLimit);
registerMembership(_msgSender(), commitments, _rateLimit);
WakuRln(storages[storageIndex]).register(commitments);
}