feat(RLN): implement KarmaRLN

Closes #217, #218, #219
This commit is contained in:
Ricardo Guilherme Schmidt
2025-06-04 23:13:11 -03:00
committed by r4bbit
parent 382b1c1643
commit d74854eb6a
5 changed files with 379 additions and 528 deletions

View File

@@ -4,13 +4,13 @@
+=================================================================================================================================================+
| Deployment Cost | Deployment Size | | | | |
|-------------------------------------------------------------------------------------------+-----------------+-------+--------+--------+---------|
| 0 | 1374 | | | | |
| 295493 | 1374 | | | | |
|-------------------------------------------------------------------------------------------+-----------------+-------+--------+--------+---------|
| | | | | | |
|-------------------------------------------------------------------------------------------+-----------------+-------+--------+--------+---------|
| Function Name | Min | Avg | Median | Max | # Calls |
|-------------------------------------------------------------------------------------------+-----------------+-------+--------+--------+---------|
| fallback | 5145 | 65850 | 33119 | 193478 | 3440 |
| fallback | 5145 | 64591 | 33119 | 193478 | 3571 |
╰-------------------------------------------------------------------------------------------+-----------------+-------+--------+--------+---------╯
╭-----------------------------------------------------+-----------------+---------+---------+---------+---------╮
@@ -24,7 +24,7 @@
|-----------------------------------------------------+-----------------+---------+---------+---------+---------|
| Function Name | Min | Avg | Median | Max | # Calls |
|-----------------------------------------------------+-----------------+---------+---------+---------+---------|
| run | 4666141 | 4666141 | 4666141 | 4666141 | 176 |
| run | 4666141 | 4666141 | 4666141 | 4666141 | 186 |
╰-----------------------------------------------------+-----------------+---------+---------+---------+---------╯
╭-----------------------------------------------------------+-----------------+---------+---------+---------+---------╮
@@ -80,7 +80,7 @@
|---------------------------------------------------------+-----------------+------+--------+------+---------|
| Function Name | Min | Avg | Median | Max | # Calls |
|---------------------------------------------------------+-----------------+------+--------+------+---------|
| activeNetworkConfig | 455 | 2076 | 455 | 4455 | 528 |
| activeNetworkConfig | 455 | 2090 | 455 | 4455 | 548 |
╰---------------------------------------------------------+-----------------+------+--------+------+---------╯
╭---------------------------------------------------------------------+-----------------+---------+---------+---------+---------╮
@@ -114,11 +114,11 @@
|------------------------------+-----------------+--------+--------+--------+---------|
| OPERATOR_ROLE | 262 | 262 | 262 | 262 | 2 |
|------------------------------+-----------------+--------+--------+--------+---------|
| SLASHER_ROLE | 262 | 262 | 262 | 262 | 24 |
| SLASHER_ROLE | 262 | 262 | 262 | 262 | 34 |
|------------------------------+-----------------+--------+--------+--------+---------|
| accountSlashAmount | 2611 | 2611 | 2611 | 2611 | 2 |
|------------------------------+-----------------+--------+--------+--------+---------|
| addRewardDistributor | 29975 | 63645 | 70903 | 70903 | 284 |
| addRewardDistributor | 29975 | 63560 | 70903 | 70903 | 304 |
|------------------------------+-----------------+--------+--------+--------+---------|
| allowance | 573 | 573 | 573 | 573 | 8 |
|------------------------------+-----------------+--------+--------+--------+---------|
@@ -130,13 +130,13 @@
|------------------------------+-----------------+--------+--------+--------+---------|
| getRewardDistributors | 5132 | 7710 | 9644 | 9644 | 21 |
|------------------------------+-----------------+--------+--------+--------+---------|
| grantRole | 29490 | 29490 | 29490 | 29490 | 29 |
| grantRole | 29490 | 29490 | 29490 | 29490 | 39 |
|------------------------------+-----------------+--------+--------+--------+---------|
| hasRole | 2754 | 2754 | 2754 | 2754 | 4 |
|------------------------------+-----------------+--------+--------+--------+---------|
| initialize | 116796 | 116796 | 116796 | 116796 | 176 |
| initialize | 116796 | 116796 | 116796 | 116796 | 186 |
|------------------------------+-----------------+--------+--------+--------+---------|
| mint | 4869 | 50368 | 51342 | 51342 | 550 |
| mint | 4869 | 50370 | 51342 | 51342 | 551 |
|------------------------------+-----------------+--------+--------+--------+---------|
| removeRewardDistributor | 5080 | 22644 | 29995 | 30358 | 28 |
|------------------------------+-----------------+--------+--------+--------+---------|
@@ -144,7 +144,7 @@
|------------------------------+-----------------+--------+--------+--------+---------|
| setReward | 4845 | 144102 | 166754 | 166754 | 319 |
|------------------------------+-----------------+--------+--------+--------+---------|
| slash | 4803 | 103614 | 85757 | 123125 | 519 |
| slash | 4803 | 103655 | 85757 | 123125 | 520 |
|------------------------------+-----------------+--------+--------+--------+---------|
| slashedAmountOf | 17682 | 28099 | 28120 | 28120 | 516 |
|------------------------------+-----------------+--------+--------+--------+---------|
@@ -396,7 +396,7 @@
+===============================================================================================================================================+
| Deployment Cost | Deployment Size | | | | |
|------------------------------------------------------------------------------------------+-----------------+-------+--------+-------+---------|
| 1204853 | 6207 | | | | |
| 1204853 | 6015 | | | | |
|------------------------------------------------------------------------------------------+-----------------+-------+--------+-------+---------|
| | | | | | |
|------------------------------------------------------------------------------------------+-----------------+-------+--------+-------+---------|
@@ -436,39 +436,35 @@
+=====================================================================================+
| Deployment Cost | Deployment Size | | | | |
|------------------------------+-----------------+--------+--------+--------+---------|
| 1396408 | 7094 | | | | |
| 1982512 | 9143 | | | | |
|------------------------------+-----------------+--------+--------+--------+---------|
| | | | | | |
|------------------------------+-----------------+--------+--------+--------+---------|
| Function Name | Min | Avg | Median | Max | # Calls |
|------------------------------+-----------------+--------+--------+--------+---------|
| FEE_PERCENTAGE | 217 | 217 | 217 | 217 | 1 |
| DEFAULT_ADMIN_ROLE | 262 | 262 | 262 | 262 | 10 |
|------------------------------+-----------------+--------+--------+--------+---------|
| FEE_RECEIVER | 226 | 226 | 226 | 226 | 1 |
| REGISTER_ROLE | 262 | 262 | 262 | 262 | 10 |
|------------------------------+-----------------+--------+--------+--------+---------|
| FREEZE_PERIOD | 218 | 218 | 218 | 218 | 1 |
| SET_SIZE | 2339 | 2339 | 2339 | 2339 | 1 |
|------------------------------+-----------------+--------+--------+--------+---------|
| MINIMAL_DEPOSIT | 241 | 241 | 241 | 241 | 1 |
| SLASHER_ROLE | 262 | 262 | 262 | 262 | 10 |
|------------------------------+-----------------+--------+--------+--------+---------|
| SET_SIZE | 284 | 284 | 284 | 284 | 1 |
| exit | 7068 | 16233 | 18694 | 22939 | 3 |
|------------------------------+-----------------+--------+--------+--------+---------|
| identityCommitmentIndex | 2362 | 2362 | 2362 | 2362 | 27 |
| hasRole | 2707 | 2707 | 2707 | 2707 | 30 |
|------------------------------+-----------------+--------+--------+--------+---------|
| members | 6826 | 6826 | 6826 | 6826 | 18 |
| identityCommitmentIndex | 2340 | 2340 | 2340 | 2340 | 5 |
|------------------------------+-----------------+--------+--------+--------+---------|
| register | 23877 | 105927 | 123897 | 126709 | 18 |
| initialize | 165036 | 165036 | 165036 | 165036 | 11 |
|------------------------------+-----------------+--------+--------+--------+---------|
| release | 28093 | 37708 | 28192 | 66355 | 4 |
| members | 4669 | 4669 | 4669 | 4669 | 6 |
|------------------------------+-----------------+--------+--------+--------+---------|
| slash | 23015 | 49020 | 34650 | 99415 | 6 |
| register | 6997 | 45345 | 53001 | 55801 | 11 |
|------------------------------+-----------------+--------+--------+--------+---------|
| token | 292 | 292 | 292 | 292 | 1 |
| slash | 7111 | 54764 | 18737 | 138444 | 3 |
|------------------------------+-----------------+--------+--------+--------+---------|
| verifier | 272 | 272 | 272 | 272 | 1 |
|------------------------------+-----------------+--------+--------+--------+---------|
| withdraw | 29336 | 79495 | 106774 | 106774 | 8 |
|------------------------------+-----------------+--------+--------+--------+---------|
| withdrawals | 6792 | 6792 | 6792 | 6792 | 5 |
| verifier | 2359 | 2359 | 2359 | 2359 | 1 |
╰------------------------------+-----------------+--------+--------+--------+---------╯
╭--------------------------------------+-----------------+-------+--------+-------+---------╮
@@ -484,29 +480,11 @@
|--------------------------------------+-----------------+-------+--------+-------+---------|
| changeResult | 21649 | 21649 | 21649 | 21649 | 2 |
|--------------------------------------+-----------------+-------+--------+-------+---------|
| result | 2298 | 2298 | 2298 | 2298 | 5 |
| result | 2298 | 2298 | 2298 | 2298 | 3 |
|--------------------------------------+-----------------+-------+--------+-------+---------|
| verifyProof | 4790 | 4790 | 4790 | 4790 | 9 |
| verifyProof | 4790 | 4790 | 4790 | 4790 | 4 |
╰--------------------------------------+-----------------+-------+--------+-------+---------╯
╭-----------------------------------+-----------------+-------+--------+-------+---------╮
| test/RLN.t.sol:TestERC20 Contract | | | | | |
+========================================================================================+
| Deployment Cost | Deployment Size | | | | |
|-----------------------------------+-----------------+-------+--------+-------+---------|
| 765176 | 3558 | | | | |
|-----------------------------------+-----------------+-------+--------+-------+---------|
| | | | | | |
|-----------------------------------+-----------------+-------+--------+-------+---------|
| Function Name | Min | Avg | Median | Max | # Calls |
|-----------------------------------+-----------------+-------+--------+-------+---------|
| approve | 46175 | 46179 | 46175 | 46199 | 18 |
|-----------------------------------+-----------------+-------+--------+-------+---------|
| balanceOf | 2561 | 2561 | 2561 | 2561 | 62 |
|-----------------------------------+-----------------+-------+--------+-------+---------|
| mint | 51064 | 64368 | 68164 | 68188 | 18 |
╰-----------------------------------+-----------------+-------+--------+-------+---------╯
╭-------------------------------------------------------------------+-----------------+-------+--------+-------+---------╮
| test/mocks/KarmaDistributorMock.sol:KarmaDistributorMock Contract | | | | | |
+========================================================================================================================+
@@ -518,11 +496,11 @@
|-------------------------------------------------------------------+-----------------+-------+--------+-------+---------|
| Function Name | Min | Avg | Median | Max | # Calls |
|-------------------------------------------------------------------+-----------------+-------+--------+-------+---------|
| rewardsBalanceOfAccount | 549 | 1986 | 2549 | 2549 | 3675 |
| rewardsBalanceOfAccount | 549 | 1985 | 2549 | 2549 | 3679 |
|-------------------------------------------------------------------+-----------------+-------+--------+-------+---------|
| setTotalKarmaShares | 43589 | 43589 | 43589 | 43589 | 48 |
|-------------------------------------------------------------------+-----------------+-------+--------+-------+---------|
| setUserKarmaShare | 24210 | 44068 | 44134 | 44266 | 530 |
| setUserKarmaShare | 24210 | 44107 | 44134 | 44266 | 531 |
|-------------------------------------------------------------------+-----------------+-------+--------+-------+---------|
| totalRewardsSupply | 2324 | 2324 | 2324 | 2324 | 48 |
╰-------------------------------------------------------------------+-----------------+-------+--------+-------+---------╯
@@ -546,7 +524,7 @@
+==================================================================================================+
| Deployment Cost | Deployment Size | | | | |
|---------------------------------------------+-----------------+-------+--------+-------+---------|
| 770657 | 3987 | | | | |
| 770741 | 3987 | | | | |
|---------------------------------------------+-----------------+-------+--------+-------+---------|
| | | | | | |
|---------------------------------------------+-----------------+-------+--------+-------+---------|

View File

@@ -112,23 +112,16 @@ OverflowTest:testTotalSupply() (gas: 359391)
OverflowTest:testTransfersNotAllowed() (gas: 61925)
OverflowTest:test_RevertWhen_MintingCausesOverflow() (gas: 129592)
OverflowTest:test_RevertWhen_SettingRewardCausesOverflow() (gas: 127920)
RLNTest:test_initial_state() (gas: 65400)
RLNTest:test_register_fails_when_amount_lt_minimal_deposit() (gas: 161453)
RLNTest:test_register_fails_when_duplicate_identity_commitments() (gas: 444949)
RLNTest:test_register_fails_when_index_exceeds_set_size() (gas: 2824923)
RLNTest:test_register_succeeds() (gas: 579610)
RLNTest:test_release_fails_when_freeze_period() (gas: 529887)
RLNTest:test_release_fails_when_no_withdrawal() (gas: 38343)
RLNTest:test_release_succeeds() (gas: 576511)
RLNTest:test_slash_fails_when_invalid_proof() (gas: 399809)
RLNTest:test_slash_fails_when_not_registered() (gas: 59667)
RLNTest:test_slash_fails_when_receiver_is_zero() (gas: 351215)
RLNTest:test_slash_fails_when_self_slashing() (gas: 360067)
RLNTest:test_slash_succeeds() (gas: 1024025)
RLNTest:test_withdraw_fails_when_already_underways() (gas: 468594)
RLNTest:test_withdraw_fails_when_invalid_proof() (gas: 399356)
RLNTest:test_withdraw_fails_when_not_registered() (gas: 57129)
RLNTest:test_withdraw_succeeds() (gas: 480413)
RLNTest:test_exit_fails_when_invalid_proof() (gas: 195432)
RLNTest:test_exit_fails_when_not_registered() (gas: 64843)
RLNTest:test_exit_succeeds() (gas: 187485)
RLNTest:test_initial_state() (gas: 60571)
RLNTest:test_register_fails_when_duplicate_identity_commitment() (gas: 131752)
RLNTest:test_register_fails_when_index_exceeds_set_size() (gas: 2569410)
RLNTest:test_register_succeeds() (gas: 272680)
RLNTest:test_slash_fails_when_invalid_proof() (gas: 195506)
RLNTest:test_slash_fails_when_not_registered() (gas: 64886)
RLNTest:test_slash_succeeds() (gas: 438591)
RemoveRewardDistributorTest:testAddKarmaDistributorOnlyAdmin() (gas: 438248)
RemoveRewardDistributorTest:testBalanceOf() (gas: 456715)
RemoveRewardDistributorTest:testBalanceOfWithNoSystemTotalKarma() (gas: 83783)
@@ -156,7 +149,7 @@ SetRewardTest:test_RevertWhen_SenderIsNotOperator() (gas: 61893)
SlashAmountOfTest:testAddKarmaDistributorOnlyAdmin() (gas: 438224)
SlashAmountOfTest:testBalanceOf() (gas: 456642)
SlashAmountOfTest:testBalanceOfWithNoSystemTotalKarma() (gas: 83783)
SlashAmountOfTest:testFuzz_SlashAmountOf(uint256,uint256,uint256) (runs: 1000, μ: 407786, ~: 408571)
SlashAmountOfTest:testFuzz_SlashAmountOf(uint256,uint256,uint256) (runs: 1000, μ: 408297, ~: 409081)
SlashAmountOfTest:testMintOnlyAdmin() (gas: 429075)
SlashAmountOfTest:testRemoveKarmaDistributorOnlyOwner() (gas: 163437)
SlashAmountOfTest:testRemoveUnknownKarmaDistributor() (gas: 41654)

View File

@@ -1,27 +1,33 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import "forge-std/Script.sol";
import "../src/rln/RLN.sol";
import "../src/rln/Verifier.sol";
import { BaseScript } from "./Base.s.sol";
import { DeploymentConfig } from "./DeploymentConfig.s.sol";
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import { Groth16Verifier } from "../src/rln/Verifier.sol";
import { RLN } from "../src/rln/RLN.sol";
contract DeployRLNScript is BaseScript {
function run() public returns (RLN, DeploymentConfig) {
DeploymentConfig deploymentConfig = new DeploymentConfig(broadcaster);
(address deployer,) = deploymentConfig.activeNetworkConfig();
contract RLNScript is Script {
function run() public {
uint256 minimalDeposit = vm.envUint("MINIMAL_DEPOSIT");
uint256 maximalRate = vm.envUint("MAXIMAL_RATE");
uint256 depth = vm.envUint("DEPTH");
uint8 feePercentage = uint8(vm.envUint("FEE_PERCENTAGE"));
address feeReceiver = vm.envAddress("FEE_RECEIVER");
uint256 freezePeriod = vm.envUint("FREEZE_PERIOD");
address token = vm.envAddress("ERC20TOKEN");
address karmaAddress = vm.envAddress("KARMA_ADDRESS");
vm.startBroadcast();
Groth16Verifier verifier = new Groth16Verifier();
RLN rln = new RLN(
minimalDeposit, maximalRate, depth, feePercentage, feeReceiver, freezePeriod, token, address(verifier)
);
vm.startBroadcast(deployer);
address verifier = (address)(new Groth16Verifier());
// Deploy Karma logic contract
bytes memory initializeData =
abi.encodeCall(RLN.initialize, (deployer, deployer, deployer, depth, verifier, karmaAddress));
address impl = address(new RLN());
// Create upgradeable proxy
address proxy = address(new ERC1967Proxy(impl, initializeData));
vm.stopBroadcast();
return (RLN(proxy), deploymentConfig);
}
}

View File

@@ -1,211 +1,168 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
pragma solidity ^0.8.26;
pragma solidity 0.8.26;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Karma } from "../Karma.sol";
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { IVerifier } from "./IVerifier.sol";
/// @title Rate-Limiting Nullifier registry contract
/// @dev This contract allows you to register RLN commitment and withdraw/slash.
contract RLN {
using SafeERC20 for IERC20;
contract RLN is Initializable, UUPSUpgradeable, AccessControlUpgradeable {
bytes32 public constant SLASHER_ROLE = keccak256("SLASHER_ROLE");
bytes32 public constant REGISTER_ROLE = keccak256("REGISTER_ROLE");
error RLN__InvalidProof();
error RLN__MemberNotFound();
error RLN__IdCommitmentAlreadyRegistered();
error RLN__SetIsFull();
error RLN__Unauthorized();
/// @dev User metadata struct.
/// @param userAddress: address of depositor;
/// @param messageLimit: user's message limit (stakeAmount / MINIMAL_DEPOSIT).
struct User {
address userAddress;
uint256 messageLimit;
uint256 index;
}
/// @dev Withdrawal time-lock struct
/// @param blockNumber: number of block when a withdraw was initialized;
/// @param messageLimit: amount of tokens to freeze/release;
/// @param receiver: address of receiver.
struct Withdrawal {
uint256 blockNumber;
uint256 amount;
address receiver;
}
/// @dev Minimal membership deposit (stake amount) value - cost of 1 message.
uint256 public immutable MINIMAL_DEPOSIT;
/// @dev Maximal rate.
uint256 public immutable MAXIMAL_RATE;
/// @dev Registry set size (1 << DEPTH).
uint256 public immutable SET_SIZE;
/// @dev Address of the fee receiver.
address public immutable FEE_RECEIVER;
/// @dev Fee percentage.
uint8 public immutable FEE_PERCENTAGE;
/// @dev Freeze period - number of blocks for which the withdrawal of money is frozen.
uint256 public immutable FREEZE_PERIOD;
uint256 public SET_SIZE;
/// @dev Current index where identityCommitment will be stored.
uint256 public identityCommitmentIndex;
/// @dev Registry set. The keys are `identityCommitment`s.
/// The values are addresses of accounts that call `register` transaction.
mapping(uint256 => User) public members;
mapping(uint256 commitment => User user) public members;
/// @dev Withdrawals logic.
mapping(uint256 => Withdrawal) public withdrawals;
/// @dev ERC20 Token used for staking.
IERC20 public immutable token;
/// @dev Karma Token used for registering.
Karma public karma;
/// @dev Groth16 verifier.
IVerifier public immutable verifier;
IVerifier public verifier;
/// @dev Emmited when a new member registered.
/// @param identityCommitment: `identityCommitment`;
/// @param messageLimit: user's message limit;
/// @param index: idCommitmentIndex value.
event MemberRegistered(uint256 identityCommitment, uint256 messageLimit, uint256 index);
event MemberRegistered(uint256 identityCommitment, uint256 index);
/// @dev Emmited when a member was withdrawn.
/// @param index: index of `identityCommitment`;
event MemberWithdrawn(uint256 index);
event MemberExited(uint256 index);
/// @dev Emmited when a member was slashed.
/// @param index: index of `identityCommitment`;
/// @param slasher: address of slasher (msg.sender).
event MemberSlashed(uint256 index, address slasher);
/// @param minimalDeposit: minimal membership deposit;
/// @param maximalRate: maximal rate;
constructor() {
_disableInitializers();
}
/// @dev Constructor.
/// @param _owner: address of the owner of the contract;
/// @param _slasher: address of the slasher;
/// @param _register: address of the register;
/// @param depth: depth of the merkle tree;
/// @param feePercentage: fee percentage;
/// @param feeReceiver: address of the fee receiver;
/// @param freezePeriod: amount of blocks for withdrawal time-lock;
/// @param _token: address of the ERC20 contract;
/// @param _verifier: address of the Groth16 Verifier.
constructor(
uint256 minimalDeposit,
uint256 maximalRate,
function initialize(
address _owner,
address _slasher,
address _register,
uint256 depth,
uint8 feePercentage,
address feeReceiver,
uint256 freezePeriod,
address _token,
address _verifier
) {
require(feeReceiver != address(0), "RLN, constructor: fee receiver cannot be 0x0 address");
MINIMAL_DEPOSIT = minimalDeposit;
MAXIMAL_RATE = maximalRate;
address _verifier,
address _token
)
public
initializer
{
__UUPSUpgradeable_init();
__AccessControl_init();
_setupRole(DEFAULT_ADMIN_ROLE, _owner);
_setupRole(SLASHER_ROLE, _slasher);
_setupRole(REGISTER_ROLE, _register);
SET_SIZE = 1 << depth;
FEE_PERCENTAGE = feePercentage;
FEE_RECEIVER = feeReceiver;
FREEZE_PERIOD = freezePeriod;
token = IERC20(_token);
karma = Karma(_token);
verifier = IVerifier(_verifier);
}
/**
* @notice Authorizes contract upgrades via UUPS.
* @dev This function is only callable by the owner.
*/
function _authorizeUpgrade(address) internal view override {
if (!hasRole(DEFAULT_ADMIN_ROLE, msg.sender)) {
revert RLN__Unauthorized();
}
}
/// @dev Adds `identityCommitment` to the registry set and takes the necessary stake amount.
///
/// NOTE: The set must not be full.
///
/// @param identityCommitment: `identityCommitment`;
/// @param amount: stake amount.
function register(uint256 identityCommitment, uint256 amount) external {
function register(uint256 identityCommitment, address user) external onlyRole(REGISTER_ROLE) {
uint256 index = identityCommitmentIndex;
if (index >= SET_SIZE) {
revert RLN__SetIsFull();
}
if (members[identityCommitment].userAddress != address(0)) {
revert RLN__IdCommitmentAlreadyRegistered();
}
require(index < SET_SIZE, "RLN, register: set is full");
require(amount >= MINIMAL_DEPOSIT, "RLN, register: amount is lower than minimal deposit");
require(amount % MINIMAL_DEPOSIT == 0, "RLN, register: amount should be a multiple of minimal deposit");
require(
members[identityCommitment].userAddress == address(0), "RLN, register: idCommitment already registered"
);
uint256 messageLimit = amount / MINIMAL_DEPOSIT;
require(messageLimit <= MAXIMAL_RATE, "RLN, register: message limit cannot be more than MAXIMAL_RATE");
token.safeTransferFrom(msg.sender, address(this), amount);
members[identityCommitment] = User(msg.sender, messageLimit, index);
emit MemberRegistered(identityCommitment, messageLimit, index);
members[identityCommitment] = User(user, index);
emit MemberRegistered(identityCommitment, index);
unchecked {
identityCommitmentIndex = index + 1;
}
}
/// @dev Request for withdraw and freeze the stake to prevent self-slashing. Stake can be
/// released after FREEZE_PERIOD blocks.
/// @dev Request for exit.
/// @param identityCommitment: `identityCommitment`;
/// @param proof: snarkjs's format generated proof (without public inputs) packed consequently.
function withdraw(uint256 identityCommitment, uint256[8] calldata proof) external {
function exit(uint256 identityCommitment, uint256[8] calldata proof) external onlyRole(REGISTER_ROLE) {
User memory member = members[identityCommitment];
require(member.userAddress != address(0), "RLN, withdraw: member doesn't exist");
require(withdrawals[identityCommitment].blockNumber == 0, "RLN, release: such withdrawal exists");
require(_verifyProof(identityCommitment, member.userAddress, proof), "RLN, withdraw: invalid proof");
if (member.userAddress == address(0)) {
revert RLN__MemberNotFound();
}
if (!_verifyProof(identityCommitment, proof)) {
revert RLN__InvalidProof();
}
uint256 withdrawAmount = member.messageLimit * MINIMAL_DEPOSIT;
withdrawals[identityCommitment] = Withdrawal(block.number, withdrawAmount, member.userAddress);
emit MemberWithdrawn(member.index);
}
/// @dev Releases stake amount.
/// @param identityCommitment: `identityCommitment` of withdrawn user.
function release(uint256 identityCommitment) external {
Withdrawal memory withdrawal = withdrawals[identityCommitment];
require(withdrawal.blockNumber != 0, "RLN, release: no such withdrawals");
require(block.number - withdrawal.blockNumber > FREEZE_PERIOD, "RLN, release: cannot release yet");
delete withdrawals[identityCommitment];
delete members[identityCommitment];
token.safeTransfer(withdrawal.receiver, withdrawal.amount);
emit MemberExited(member.index);
}
/// @dev Slashes identity with identityCommitment.
/// @param identityCommitment: `identityCommitment`;
/// @param receiver: stake receiver;
/// @param proof: snarkjs's format generated proof (without public inputs) packed consequently.
function slash(uint256 identityCommitment, address receiver, uint256[8] calldata proof) external {
require(receiver != address(0), "RLN, slash: empty receiver address");
function slash(uint256 identityCommitment, uint256[8] calldata proof) external onlyRole(SLASHER_ROLE) {
User memory member = members[identityCommitment];
require(member.userAddress != address(0), "RLN, slash: member doesn't exist");
require(member.userAddress != receiver, "RLN, slash: self-slashing is prohibited");
require(_verifyProof(identityCommitment, receiver, proof), "RLN, slash: invalid proof");
if (member.userAddress == address(0)) {
revert RLN__MemberNotFound();
}
if (!_verifyProof(identityCommitment, proof)) {
revert RLN__InvalidProof();
}
karma.slash(member.userAddress);
delete members[identityCommitment];
delete withdrawals[identityCommitment];
uint256 withdrawAmount = member.messageLimit * MINIMAL_DEPOSIT;
uint256 feeAmount = (FEE_PERCENTAGE * withdrawAmount) / 100;
token.safeTransfer(receiver, withdrawAmount - feeAmount);
token.safeTransfer(FEE_RECEIVER, feeAmount);
emit MemberSlashed(member.index, receiver);
emit MemberSlashed(member.index, msg.sender);
}
/// @dev Groth16 proof verification
function _verifyProof(
uint256 identityCommitment,
address receiver,
uint256[8] calldata proof
)
internal
view
returns (bool)
{
function _verifyProof(uint256 identityCommitment, uint256[8] calldata proof) internal view returns (bool) {
return verifier.verifyProof(
[proof[0], proof[1]],
[[proof[2], proof[3]], [proof[4], proof[5]]],
[proof[6], proof[7]],
[identityCommitment, uint256(uint160(receiver))]
[identityCommitment, uint256(0)]
);
}
}

View File

@@ -1,23 +1,18 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
pragma solidity ^0.8.26;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "forge-std/Test.sol";
import "../src/rln/RLN.sol";
import { Test } from "forge-std/Test.sol";
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import { RLN } from "../src/rln/RLN.sol";
import { IVerifier } from "../src/rln/IVerifier.sol";
import { Karma } from "../src/Karma.sol";
import { KarmaDistributorMock } from "./mocks/KarmaDistributorMock.sol";
import { DeployKarmaScript } from "../script/DeployKarma.s.sol";
import { DeployRLNScript } from "../script/RLN.s.sol";
// A ERC20 token contract which allows arbitrary minting for testing
contract TestERC20 is ERC20 {
constructor() ERC20("TestERC20", "TST") { }
import { DeploymentConfig } from "../script/DeploymentConfig.s.sol";
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}
// A mock verifier which makes us skip the proof verification.
/// @dev A mock verifier that allows toggling proof validity.
contract MockVerifier is IVerifier {
bool public result;
@@ -33,6 +28,7 @@ contract MockVerifier is IVerifier {
)
external
view
override
returns (bool)
{
return result;
@@ -44,352 +40,273 @@ contract MockVerifier is IVerifier {
}
contract RLNTest is Test {
event MemberRegistered(uint256 identityCommitment, uint256 messageLimit, uint256 index);
event MemberWithdrawn(uint256 index);
event MemberSlashed(uint256 index, address slasher);
RLN public rln;
MockVerifier public verifier;
RLN rln;
TestERC20 token;
MockVerifier verifier;
uint256 private constant DEPTH = 2; // for most tests
uint256 private constant SMALL_DEPTH = 1; // for “full” test
uint256 rlnInitialTokenBalance = 1_000_000;
uint256 minimalDeposit = 100;
uint256 maximalRate = 1 << 16 - 1;
uint256 depth = 20;
uint8 feePercentage = 10;
address feeReceiver = makeAddr("feeReceiver");
uint256 freezePeriod = 1;
// Sample identity commitments
uint256 private identityCommitment0 = 1234;
uint256 private identityCommitment1 = 5678;
uint256 private identityCommitment2 = 9999;
uint256 identityCommitment0 = 1234;
uint256 identityCommitment1 = 5678;
address user0 = makeAddr("user0");
address user1 = makeAddr("user1");
address slashedReceiver = makeAddr("slashedReceiver");
uint256 messageLimit0 = 2;
uint256 messageLimit1 = 3;
uint256[8] mockProof =
// Sample SNARK proof (8element array)
uint256[8] private mockProof =
[uint256(0), uint256(1), uint256(2), uint256(3), uint256(4), uint256(5), uint256(6), uint256(7)];
// Roleholders
address private owner;
Karma private karma;
KarmaDistributorMock public distributor1;
KarmaDistributorMock public distributor2;
address private adminAddr;
address private registerAddr;
address private slasherAddr;
address private user1Addr = makeAddr("user1");
address private user2Addr = makeAddr("user2");
address private user3Addr = makeAddr("user3");
function setUp() public {
token = new TestERC20();
DeployKarmaScript karmaDeployment = new DeployKarmaScript();
(Karma _karma, DeploymentConfig deploymentConfig) = karmaDeployment.run();
karma = _karma;
(address deployer,) = deploymentConfig.activeNetworkConfig();
owner = deployer;
distributor1 = new KarmaDistributorMock();
distributor2 = new KarmaDistributorMock();
// Assign deterministic addresses
adminAddr = makeAddr("admin");
registerAddr = makeAddr("register");
slasherAddr = makeAddr("slasher");
// Deploy mock verifier
verifier = new MockVerifier();
rln = new RLN(
minimalDeposit,
maximalRate,
depth,
feePercentage,
feeReceiver,
freezePeriod,
address(token),
address(verifier)
);
// Deploy RLN via UUPS proxy with DEPTH = 2
rln = _deployRLN(DEPTH, address(verifier), karma);
// Sanitycheck that roles were assigned correctly
assertTrue(rln.hasRole(rln.DEFAULT_ADMIN_ROLE(), adminAddr));
assertTrue(rln.hasRole(rln.REGISTER_ROLE(), registerAddr));
assertTrue(rln.hasRole(rln.SLASHER_ROLE(), slasherAddr));
vm.startBroadcast(owner);
karma.addRewardDistributor(address(distributor1));
karma.addRewardDistributor(address(distributor2));
karma.grantRole(karma.SLASHER_ROLE(), address(rln));
vm.stopBroadcast();
}
/// @dev Deploys a new RLN instance (behind ERC1967Proxy).
function _deployRLN(uint256 depth, address verifierAddr, Karma karmaToken) internal returns (RLN) {
bytes memory initData = abi.encodeCall(
RLN.initialize,
(
adminAddr,
slasherAddr,
registerAddr,
depth,
verifierAddr,
address(karmaToken) // token address unused in these tests
)
);
address impl = address(new RLN());
address proxy = address(new ERC1967Proxy(impl, initData));
return RLN(proxy);
}
/* ---------- INITIAL STATE ---------- */
function test_initial_state() public {
assertEq(rln.MINIMAL_DEPOSIT(), minimalDeposit);
assertEq(rln.SET_SIZE(), 1 << depth);
assertEq(rln.FEE_PERCENTAGE(), feePercentage);
assertEq(rln.FEE_RECEIVER(), feeReceiver);
assertEq(rln.FREEZE_PERIOD(), freezePeriod);
assertEq(address(rln.token()), address(token));
assertEq(address(rln.verifier()), address(verifier));
// SET_SIZE should be 2^DEPTH = 4
assertEq(rln.SET_SIZE(), uint256(1) << DEPTH);
// No identities registered yet
assertEq(rln.identityCommitmentIndex(), 0);
// members(...) should return (address(0), 0) for any commitment
(address user0, uint256 idx0) = _memberData(identityCommitment0);
assertEq(user0, address(0));
assertEq(idx0, 0);
// Verifier address matches
assertEq(address(rln.verifier()), address(verifier));
}
/* register */
/* ---------- REGISTER ---------- */
function test_register_succeeds() public {
// Test: register one user
register(user0, identityCommitment0, messageLimit0);
// Test: register second user
register(user1, identityCommitment1, messageLimit1);
// Register first identity
uint256 indexBefore = rln.identityCommitmentIndex();
vm.startPrank(registerAddr);
vm.expectEmit(true, false, false, true);
emit RLN.MemberRegistered(identityCommitment0, indexBefore);
rln.register(identityCommitment0, user1Addr);
vm.stopPrank();
assertEq(rln.identityCommitmentIndex(), indexBefore + 1);
(address u0, uint256 i0) = _memberData(identityCommitment0);
assertEq(u0, user1Addr);
assertEq(i0, indexBefore);
// Register second identity
indexBefore = rln.identityCommitmentIndex();
vm.startPrank(registerAddr);
vm.expectEmit(true, false, false, true);
emit RLN.MemberRegistered(identityCommitment1, indexBefore);
rln.register(identityCommitment1, user2Addr);
vm.stopPrank();
assertEq(rln.identityCommitmentIndex(), indexBefore + 1);
(address u1, uint256 i1) = _memberData(identityCommitment1);
assertEq(u1, user2Addr);
assertEq(i1, indexBefore);
}
function test_register_fails_when_index_exceeds_set_size() public {
// Set size is (1 << smallDepth) = 2, and thus there can
// only be 2 members, otherwise reverts.
uint256 smallDepth = 1;
TestERC20 _token = new TestERC20();
RLN smallRLN = new RLN(
minimalDeposit, maximalRate, smallDepth, feePercentage, feeReceiver, 0, address(_token), address(verifier)
);
// Deploy a small RLN with depth = 1 => SET_SIZE = 2
RLN smallRLN = _deployRLN(SMALL_DEPTH, address(verifier), karma);
address smallRegister = registerAddr;
// Register the first user
_token.mint(user0, minimalDeposit);
vm.startPrank(user0);
_token.approve(address(smallRLN), minimalDeposit);
smallRLN.register(identityCommitment0, minimalDeposit);
// Fill up both slots
vm.startPrank(smallRegister);
smallRLN.register(identityCommitment0, user1Addr);
smallRLN.register(identityCommitment1, user2Addr);
vm.stopPrank();
// Register the second user
_token.mint(user1, minimalDeposit);
vm.startPrank(user1);
_token.approve(address(smallRLN), minimalDeposit);
smallRLN.register(identityCommitment1, minimalDeposit);
vm.stopPrank();
// Now tree (set) is full. Try register the third. It should revert.
address user2 = makeAddr("user2");
uint256 identityCommitment2 = 9999;
token.mint(user2, minimalDeposit);
vm.startPrank(user2);
token.approve(address(smallRLN), minimalDeposit);
// `register` should revert
vm.expectRevert("RLN, register: set is full");
smallRLN.register(identityCommitment2, minimalDeposit);
// Now the set is full (2 members). Attempt a third registration.
vm.startPrank(smallRegister);
vm.expectRevert(RLN.RLN__SetIsFull.selector);
smallRLN.register(identityCommitment2, user3Addr);
vm.stopPrank();
}
function test_register_fails_when_amount_lt_minimal_deposit() public {
uint256 insufficientAmount = minimalDeposit - 1;
token.mint(user0, rlnInitialTokenBalance);
vm.startPrank(user0);
token.approve(address(rln), rlnInitialTokenBalance);
vm.expectRevert("RLN, register: amount is lower than minimal deposit");
rln.register(identityCommitment0, insufficientAmount);
function test_register_fails_when_duplicate_identity_commitment() public {
// Register once
vm.startPrank(registerAddr);
rln.register(identityCommitment0, user1Addr);
vm.stopPrank();
// Attempt to register the same commitment again
vm.startPrank(registerAddr);
vm.expectRevert(RLN.RLN__IdCommitmentAlreadyRegistered.selector);
rln.register(identityCommitment0, user1Addr);
vm.stopPrank();
}
function test_register_fails_when_duplicate_identity_commitments() public {
// Register first with user0 with identityCommitment0
register(user0, identityCommitment0, messageLimit0);
// Register again with user1 with identityCommitment0
token.mint(user1, rlnInitialTokenBalance);
vm.startPrank(user1);
token.approve(address(rln), rlnInitialTokenBalance);
// `register` should revert
vm.expectRevert("RLN, register: idCommitment already registered");
rln.register(identityCommitment0, rlnInitialTokenBalance);
/* ---------- EXIT ---------- */
function test_exit_succeeds() public {
// Register the identity
vm.startPrank(registerAddr);
rln.register(identityCommitment0, user1Addr);
vm.stopPrank();
// Ensure mock verifier returns true by default
assertTrue(verifier.result());
// Call exit with a valid proof
vm.startPrank(registerAddr);
vm.expectEmit(false, false, false, true);
emit RLN.MemberExited(0);
rln.exit(identityCommitment0, mockProof);
vm.stopPrank();
// After exit, the member record should be cleared
(address u0, uint256 i0) = _memberData(identityCommitment0);
assertEq(u0, address(0));
assertEq(i0, 0);
}
function test_exit_fails_when_not_registered() public {
// Attempt exit without prior registration
vm.startPrank(registerAddr);
vm.expectRevert(RLN.RLN__MemberNotFound.selector);
rln.exit(identityCommitment1, mockProof);
vm.stopPrank();
}
/* withdraw */
function test_exit_fails_when_invalid_proof() public {
// Register the identity
vm.startPrank(registerAddr);
rln.register(identityCommitment0, user1Addr);
vm.stopPrank();
function test_withdraw_succeeds() public {
// Register first
register(user0, identityCommitment0, messageLimit0);
// Make sure proof verification is skipped
assertEq(verifier.result(), true);
// Withdraw user0
// Ensure event is emitted
(,, uint256 index) = rln.members(identityCommitment0);
vm.expectEmit(true, true, false, true);
emit MemberWithdrawn(index);
rln.withdraw(identityCommitment0, mockProof);
// Check withdrawal entry is set correctly
(uint256 blockNumber, uint256 amount, address receiver) = rln.withdrawals(identityCommitment0);
assertEq(blockNumber, block.number);
assertEq(amount, getRegisterAmount(messageLimit0));
assertEq(receiver, user0);
}
function test_withdraw_fails_when_not_registered() public {
// Withdraw fails if the user has not registered before
vm.expectRevert("RLN, withdraw: member doesn't exist");
rln.withdraw(identityCommitment0, mockProof);
}
function test_withdraw_fails_when_already_underways() public {
// Register first
register(user0, identityCommitment0, messageLimit0);
// Withdraw user0
rln.withdraw(identityCommitment0, mockProof);
// Withdraw again and it should fail
vm.expectRevert("RLN, release: such withdrawal exists");
rln.withdraw(identityCommitment0, mockProof);
}
function test_withdraw_fails_when_invalid_proof() public {
// Register first
register(user0, identityCommitment0, messageLimit0);
// Make sure mock verifier always return false
// And thus the proof is always considered invalid
// Make proof invalid
verifier.changeResult(false);
assertEq(verifier.result(), false);
vm.expectRevert("RLN, withdraw: invalid proof");
rln.withdraw(identityCommitment0, mockProof);
assertFalse(verifier.result());
// Attempt exit with invalid proof
vm.startPrank(registerAddr);
vm.expectRevert(RLN.RLN__InvalidProof.selector);
rln.exit(identityCommitment0, mockProof);
vm.stopPrank();
}
/* release */
function test_release_succeeds() public {
// Register first
register(user0, identityCommitment0, messageLimit0);
// Withdraw user0
// Make sure proof verification is skipped
assertEq(verifier.result(), true);
rln.withdraw(identityCommitment0, mockProof);
// Test: release succeeds after freeze period
// Set block.number to `blockNumbersToRelease`
uint256 blockNumbersToRelease = getUnfrozenBlockHeight();
vm.roll(blockNumbersToRelease);
uint256 user0BalanceBefore = token.balanceOf(user0);
uint256 rlnBalanceBefore = token.balanceOf(address(rln));
// Calls release and check balances
rln.release(identityCommitment0);
uint256 user0BalanceDiff = token.balanceOf(user0) - user0BalanceBefore;
uint256 rlnBalanceDiff = rlnBalanceBefore - token.balanceOf(address(rln));
uint256 expectedUser0BalanceDiff = getRegisterAmount(messageLimit0);
assertEq(user0BalanceDiff, expectedUser0BalanceDiff);
assertEq(rlnBalanceDiff, expectedUser0BalanceDiff);
checkUserIsDeleted(identityCommitment0);
}
function test_release_fails_when_no_withdrawal() public {
// Release fails if there is no withdrawal for the user
vm.expectRevert("RLN, release: no such withdrawals");
rln.release(identityCommitment0);
}
function test_release_fails_when_freeze_period() public {
// Register first
register(user0, identityCommitment0, messageLimit0);
// Make sure mock verifier always return true to skip proof verification
assertEq(verifier.result(), true);
// Withdraw user0
rln.withdraw(identityCommitment0, mockProof);
// Ensure withdrawal is set
(uint256 blockNumber, uint256 amount, address receiver) = rln.withdrawals(identityCommitment0);
assertEq(blockNumber, block.number);
assertEq(amount, getRegisterAmount(messageLimit0));
assertEq(receiver, user0);
// Test: release fails in freeze period
vm.expectRevert("RLN, release: cannot release yet");
rln.release(identityCommitment0);
// Set block.number to blockNumbersToRelease - 1, which is still in freeze period
uint256 blockNumbersToRelease = getUnfrozenBlockHeight();
vm.roll(blockNumbersToRelease - 1);
vm.expectRevert("RLN, release: cannot release yet");
rln.release(identityCommitment0);
}
/* slash */
/* ---------- SLASH ---------- */
function test_slash_succeeds() public {
// Test: register and get slashed
register(user0, identityCommitment0, messageLimit0);
uint256 registerAmount = getRegisterAmount(messageLimit0);
uint256 slashFee = getSlashFee(registerAmount);
uint256 slashReward = registerAmount - slashFee;
uint256 slashedReceiverBalanceBefore = token.balanceOf(slashedReceiver);
uint256 rlnBalanceBefore = token.balanceOf(address(rln));
uint256 feeReceiverBalanceBefore = token.balanceOf(feeReceiver);
// ensure event is emitted
(,, uint256 index) = rln.members(identityCommitment0);
vm.expectEmit(true, true, false, true);
emit MemberSlashed(index, slashedReceiver);
// Slash and check balances
rln.slash(identityCommitment0, slashedReceiver, mockProof);
uint256 slashedReceiverBalanceDiff = token.balanceOf(slashedReceiver) - slashedReceiverBalanceBefore;
uint256 rlnBalanceDiff = rlnBalanceBefore - token.balanceOf(address(rln));
uint256 feeReceiverBalanceDiff = token.balanceOf(feeReceiver) - feeReceiverBalanceBefore;
assertEq(slashedReceiverBalanceDiff, slashReward);
assertEq(rlnBalanceDiff, registerAmount);
assertEq(feeReceiverBalanceDiff, slashFee);
// Check the record of user0 has been deleted
checkUserIsDeleted(identityCommitment0);
uint256 distributorBalance = 50 ether;
vm.startPrank(owner);
karma.mint(user2Addr, 10 ether); // Mint Karma tokens to user2
distributor1.setUserKarmaShare(user2Addr, distributorBalance);
vm.stopPrank();
// Test: register, withdraw, ang get slashed before release
register(user1, identityCommitment1, messageLimit1);
rln.withdraw(identityCommitment1, mockProof);
rln.slash(identityCommitment1, slashedReceiver, mockProof);
// Check the record of user1 has been deleted
checkUserIsDeleted(identityCommitment1);
}
// Register the identity first
vm.startPrank(registerAddr);
rln.register(identityCommitment1, user2Addr);
vm.stopPrank();
function test_slash_fails_when_receiver_is_zero() public {
// Register first
register(user0, identityCommitment0, messageLimit0);
// Try slash user0 and it fails because of the zero address
vm.expectRevert("RLN, slash: empty receiver address");
rln.slash(identityCommitment0, address(0), mockProof);
// Retrieve the assigned index
(, uint256 index1) = _memberData(identityCommitment1);
// Slash with a valid proof
vm.startPrank(slasherAddr);
vm.expectEmit(false, true, false, true);
emit RLN.MemberSlashed(index1, slasherAddr);
rln.slash(identityCommitment1, mockProof);
vm.stopPrank();
// After slash, the member record should be cleared
(address u1, uint256 i1) = _memberData(identityCommitment1);
assertEq(u1, address(0));
assertEq(i1, 0);
}
function test_slash_fails_when_not_registered() public {
// It fails if the user is not registered yet
vm.expectRevert("RLN, slash: member doesn't exist");
rln.slash(identityCommitment0, slashedReceiver, mockProof);
}
function test_slash_fails_when_self_slashing() public {
// `slash` fails when receiver is the same as the registered msg.sender
register(user0, identityCommitment0, messageLimit0);
vm.expectRevert("RLN, slash: self-slashing is prohibited");
rln.slash(identityCommitment0, user0, mockProof);
// Attempt to slash a nonexistent identity
vm.startPrank(slasherAddr);
vm.expectRevert(RLN.RLN__MemberNotFound.selector);
rln.slash(identityCommitment0, mockProof);
vm.stopPrank();
}
function test_slash_fails_when_invalid_proof() public {
// It fails if the proof is invalid
// Register first
register(user0, identityCommitment0, messageLimit0);
// Make sure mock verifier always return false
// And thus the proof is always considered invalid
verifier.changeResult(false);
assertEq(verifier.result(), false);
vm.expectRevert("RLN, slash: invalid proof");
// Slash fails because of the invalid proof
rln.slash(identityCommitment0, slashedReceiver, mockProof);
}
/* Helpers */
function getRegisterAmount(uint256 messageLimit) public view returns (uint256) {
return messageLimit * minimalDeposit;
}
function register(address user, uint256 identityCommitment, uint256 messageLimit) public {
// Mint to user first
uint256 registerTokenAmount = getRegisterAmount(messageLimit);
token.mint(user, registerTokenAmount);
// Remember the balance for later check
uint256 tokenRLNBefore = token.balanceOf(address(rln));
uint256 tokenUserBefore = token.balanceOf(user);
uint256 identityCommitmentIndexBefore = rln.identityCommitmentIndex();
// User approves to rln and calls register
vm.startPrank(user);
token.approve(address(rln), registerTokenAmount);
// Ensure event is emitted
vm.expectEmit(true, true, false, true);
emit MemberRegistered(identityCommitment, messageLimit, identityCommitmentIndexBefore);
rln.register(identityCommitment, registerTokenAmount);
// Register the identity
vm.startPrank(registerAddr);
rln.register(identityCommitment0, user1Addr);
vm.stopPrank();
// Check states
uint256 tokenRLNDiff = token.balanceOf(address(rln)) - tokenRLNBefore;
uint256 tokenUserDiff = tokenUserBefore - token.balanceOf(user);
// RLN state
assertEq(rln.identityCommitmentIndex(), identityCommitmentIndexBefore + 1);
assertEq(tokenRLNDiff, registerTokenAmount);
// User state
(address userAddress, uint256 actualMessageLimit, uint256 index) = rln.members(identityCommitment);
assertEq(userAddress, user);
assertEq(actualMessageLimit, messageLimit);
assertEq(index, identityCommitmentIndexBefore);
assertEq(tokenUserDiff, registerTokenAmount);
// Make proof invalid
verifier.changeResult(false);
assertFalse(verifier.result());
// Attempt to slash with invalid proof
vm.startPrank(slasherAddr);
vm.expectRevert(RLN.RLN__InvalidProof.selector);
rln.slash(identityCommitment0, mockProof);
vm.stopPrank();
}
function getUnfrozenBlockHeight() public view returns (uint256) {
return block.number + freezePeriod + 1;
}
/* ========== HELPERS ========== */
function checkUserIsDeleted(uint256 identityCommitment) public {
// User state
(address userAddress, uint256 actualMessageLimit, uint256 index) = rln.members(identityCommitment);
assertEq(userAddress, address(0));
assertEq(actualMessageLimit, 0);
assertEq(index, 0);
// Withdrawal state
(uint256 blockNumber, uint256 amount, address receiver) = rln.withdrawals(identityCommitment);
assertEq(blockNumber, 0);
assertEq(amount, 0);
assertEq(receiver, address(0));
}
function getSlashFee(uint256 registerAmount) public view returns (uint256) {
return registerAmount * feePercentage / 100;
/// @dev Returns (userAddress, index) for a given identityCommitment.
function _memberData(uint256 commitment) internal view returns (address userAddress, uint256 index) {
(userAddress, index) = rln.members(commitment);
return (userAddress, index);
}
}