feat: membership

This commit is contained in:
Richard Ramos 2024-08-26 15:49:58 -04:00
parent 3d44ca94b9
commit 4a26f8ff0e
No known key found for this signature in database
GPG Key ID: 1CE87DB518195760
8 changed files with 157 additions and 10 deletions

View File

@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
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

@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol";
import {IPriceCalculator} from "./IPriceCalculator.sol";
/// @title Linear Price Calculator to determine the price to acquire a membership
contract LinearPriceCalculator is IPriceCalculator, Ownable {
address private token;
uint private pricePerMessage;
constructor(address _token, uint16 _price) Ownable() {
token = _token;
pricePerMessage = _price;
}
/// Set accepted token and price per message
/// @param _token The token accepted by the membership management for RLN
/// @param _price Price per message per epoch
function setTokenAndPrice(address _token, uint _price) external onlyOwner {
token = _token;
pricePerMessage = _price;
}
function calculate(uint _rateLimit) external view returns (address, uint) {
return (token, _rateLimit * pricePerMessage);
}
}

64
contracts/Membership.sol Normal file
View File

@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import {IPriceCalculator} from "./IPriceCalculator.sol";
import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import "openzeppelin-contracts/contracts/utils/Context.sol";
error IncorrectAmount();
error OnlyTokensAccepted();
error TokenMismatch();
error InvalidRateLimit();
error ExceedMaxRateLimitPerEpoch();
contract Membership {
using SafeERC20 for IERC20;
IPriceCalculator public priceCalculator;
uint public maxTotalRateLimitPerEpoch;
uint16 public maxRateLimitPerMembership;
uint16 public minRateLimitPerMembership;
uint public totalRateLimitPerEpoch;
function __Membership_init(
address _priceCalculator,
uint _maxTotalRateLimitPerEpoch,
uint16 _maxRateLimitPerMembership,
uint16 _minRateLimitPerMembership
) internal {
priceCalculator = IPriceCalculator(_priceCalculator);
maxTotalRateLimitPerEpoch = _maxTotalRateLimitPerEpoch;
maxRateLimitPerMembership = _maxRateLimitPerMembership;
minRateLimitPerMembership = _minRateLimitPerMembership;
}
function transferMembershipFees(address _from, uint _rateLimit) internal {
(address token, uint price) = priceCalculator.calculate(_rateLimit);
if (token == address(0)) {
if (msg.value != price) revert IncorrectAmount();
} else {
if (msg.value != 0) revert OnlyTokensAccepted();
IERC20(token).safeTransferFrom(_from, address(this), price);
}
}
function acquireRateLimit(uint256[] memory commitments, uint _rateLimit) internal {
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
}
}

View File

@ -2,6 +2,7 @@
pragma solidity 0.8.15;
import {WakuRln} from "./WakuRln.sol";
import {Membership} from "./Membership.sol";
import {IPoseidonHasher} from "rln-contract/PoseidonHasher.sol";
import {UUPSUpgradeable} from "openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol";
import {OwnableUpgradeable} from "openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol";
@ -12,7 +13,7 @@ error NoStorageContractAvailable();
error IncompatibleStorage();
error IncompatibleStorageIndex();
contract WakuRlnRegistry is OwnableUpgradeable, UUPSUpgradeable {
contract WakuRlnRegistry is OwnableUpgradeable, UUPSUpgradeable, Membership {
uint16 public nextStorageIndex;
mapping(uint16 => address) public storages;
@ -27,8 +28,14 @@ contract WakuRlnRegistry is OwnableUpgradeable, UUPSUpgradeable {
_;
}
function initialize(address _poseidonHasher) external initializer {
modifier onlyValidStorageIndex(uint16 storageIndex) {
if (storageIndex >= nextStorageIndex) revert NoStorageContractAvailable();
_;
}
function initialize(address _poseidonHasher, address _priceCalculator) external initializer {
poseidonHasher = IPoseidonHasher(_poseidonHasher);
__Membership_init(_priceCalculator);
__Ownable_init();
}
@ -54,6 +61,7 @@ contract WakuRlnRegistry is OwnableUpgradeable, UUPSUpgradeable {
}
function register(uint256[] calldata commitments) external onlyUsableStorage {
// TODO: modify function to receive rate limit
// iteratively check if the storage contract is full, and increment the usingStorageIndex if it is
while (true) {
try WakuRln(storages[usingStorageIndex]).register(commitments) {
@ -72,16 +80,24 @@ contract WakuRlnRegistry is OwnableUpgradeable, UUPSUpgradeable {
}
}
function register(uint16 storageIndex, uint256[] calldata commitments) external {
if (storageIndex >= nextStorageIndex) revert NoStorageContractAvailable();
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);
WakuRln(storages[storageIndex]).register(commitments);
}
function register(uint16 storageIndex, uint256 commitment) external {
if (storageIndex >= nextStorageIndex) revert NoStorageContractAvailable();
function register(uint16 storageIndex, uint256 commitment) external payable onlyValidStorageIndex(storageIndex) {
// optimize the gas used below
uint256[] memory commitments = new uint256[](1);
commitments[0] = commitment;
// TODO: modify function to receive the number of messages
uint _rateLimit = 4;
acquireRateLimit(commitments, _rateLimit);
transferMembershipFees(_msgSender(), _rateLimit);
WakuRln(storages[storageIndex]).register(commitments);
}

View File

@ -0,0 +1,16 @@
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { deployments, getUnnamedAccounts } = hre;
const { deploy } = deployments;
const [deployer] = await getUnnamedAccounts();
await deploy("WakuSimplePriceCalculator", {
from: deployer,
log: true,
});
};
export default func;
func.tags = ["WakuSimplePriceCalculator"];

View File

@ -10,15 +10,24 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const poseidonHasherAddress = (await deployments.get("PoseidonHasher"))
.address;
const priceCalculatorAddress = (
await deployments.get("WakuSimplePriceCalculator")
).address;
const implRes = await deploy("WakuRlnRegistry_Implementation", {
contract: "WakuRlnRegistry",
from: deployer,
log: true,
});
let initializeAbi = ["function initialize(address _poseidonHasher)"];
let initializeAbi = [
"function initialize(address _poseidonHasher, address _priceCalculator)",
];
let iface = new hre.ethers.utils.Interface(initializeAbi);
const data = iface.encodeFunctionData("initialize", [poseidonHasherAddress]);
const data = iface.encodeFunctionData("initialize", [
poseidonHasherAddress,
priceCalculatorAddress,
]);
await deploy("WakuRlnRegistry_Proxy", {
contract: "ERC1967Proxy",
@ -30,4 +39,4 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
export default func;
func.tags = ["WakuRlnRegistry"];
func.dependencies = ["PoseidonHasher"];
func.dependencies = ["PoseidonHasher", "WakuSimplePriceCalculator"];

View File

@ -2,6 +2,8 @@
pragma solidity ^0.8.15;
import "../contracts/WakuRlnRegistry.sol";
import "../contracts/IPriceCalculator.sol";
import "../contracts/LinearPriceCalculator.sol";
import {PoseidonHasher} from "rln-contract/PoseidonHasher.sol";
import {DuplicateIdCommitment, FullTree} from "rln-contract/RlnBase.sol";
import {ERC1967Proxy} from "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol";
@ -15,11 +17,14 @@ contract WakuRlnRegistryTest is Test {
WakuRlnRegistry public wakuRlnRegistry;
PoseidonHasher public poseidonHasher;
IPriceCalculator public priceCalculator;
function setUp() public {
LinearPriceCalculator p = new LinearPriceCalculator();
priceCalculator = IPriceCalculator(address(p));
poseidonHasher = new PoseidonHasher();
address implementation = address(new WakuRlnRegistry());
bytes memory data = abi.encodeCall(WakuRlnRegistry.initialize, address(poseidonHasher));
bytes memory data = abi.encodeCall(WakuRlnRegistry.initialize, (address(poseidonHasher), address(priceCalculator)));
address proxy = address(new ERC1967Proxy(implementation, data));
wakuRlnRegistry = WakuRlnRegistry(proxy);
}