chore(RLN): import rln-contracts

This commit is contained in:
Ricardo Guilherme Schmidt
2025-05-21 07:47:45 -03:00
committed by r4bbit
parent bfba516b28
commit 458e07cc70
10 changed files with 987 additions and 32 deletions

8
.env Normal file
View File

@@ -0,0 +1,8 @@
GOERLI_RPC_URL=https://rpc.ankr.com/eth_goerli
ERC20TOKEN=0xA13dE8dF4Ef9D6016F0826858D48045848429390
MINIMAL_DEPOSIT=100
MAXIMAL_RATE=65535
DEPTH=20
FEE_PERCENTAGE=5
FEE_RECEIVER=0x90579a82BB296A6DA61D4d035E42Fc1aD75f090e
FREEZE_PERIOD=100

8
.env.example Normal file
View File

@@ -0,0 +1,8 @@
GOERLI_RPC_URL=https://rpc.ankr.com/eth_goerli
ERC20TOKEN=0xA13dE8dF4Ef9D6016F0826858D48045848429390
MINIMAL_DEPOSIT=100
MAXIMAL_RATE=65535
DEPTH=20
FEE_PERCENTAGE=5
FEE_RECEIVER=0x90579a82BB296A6DA61D4d035E42Fc1aD75f090e
FREEZE_PERIOD=100

View File

@@ -186,7 +186,7 @@
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| getAccountVaults | 5230 | 5230 | 5230 | 5230 | 4 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| getVault | 13653 | 13653 | 13653 | 13653 | 4182 |
| getVault | 13653 | 13653 | 13653 | 13653 | 4181 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| initialize | 92752 | 92752 | 92752 | 92752 | 95 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
@@ -194,7 +194,7 @@
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| leave | 66348 | 66348 | 66348 | 66348 | 2 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| lock | 7040 | 43591 | 46713 | 87964 | 1034 |
| lock | 7040 | 43473 | 46713 | 87673 | 1034 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| migrateToVault | 9294 | 53513 | 17021 | 170715 | 4 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
@@ -212,7 +212,7 @@
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| rewardStartTime | 2364 | 2364 | 2364 | 2364 | 2 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| rewardsBalanceOf | 20295 | 24430 | 25908 | 26129 | 268 |
| rewardsBalanceOf | 20295 | 24409 | 25908 | 26129 | 268 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| rewardsBalanceOfAccount | 62220 | 62220 | 62220 | 62220 | 1 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
@@ -222,33 +222,33 @@
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| setTrustedCodehash | 24238 | 24238 | 24238 | 24238 | 95 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| stake | 2639 | 131862 | 60725 | 228623 | 2670 |
| stake | 2639 | 132481 | 60725 | 228623 | 2670 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| stakedBalanceOf | 2622 | 2622 | 2622 | 2622 | 1 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| totalMP | 6805 | 8257 | 8257 | 9710 | 6 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| totalMPAccrued | 2385 | 2385 | 2385 | 2385 | 4162 |
| totalMPAccrued | 2385 | 2385 | 2385 | 2385 | 4161 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| totalMPStaked | 2429 | 2429 | 2429 | 2429 | 4165 |
| totalMPStaked | 2429 | 2429 | 2429 | 2429 | 4164 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| totalMaxMP | 2407 | 2407 | 2407 | 2407 | 4162 |
| totalMaxMP | 2407 | 2407 | 2407 | 2407 | 4161 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| totalRewardsAccrued | 2407 | 2407 | 2407 | 2407 | 3 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| totalRewardsSupply | 6737 | 11068 | 11792 | 11903 | 290 |
| totalRewardsSupply | 6737 | 11058 | 11792 | 11903 | 290 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| totalShares | 4597 | 4597 | 4597 | 4597 | 6 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| totalStaked | 2408 | 2408 | 2408 | 2408 | 4169 |
| totalStaked | 2408 | 2408 | 2408 | 2408 | 4168 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| unstake | 9886 | 41391 | 39781 | 79550 | 271 |
| unstake | 9886 | 41170 | 39781 | 79550 | 271 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| updateAccount | 347677 | 347677 | 347677 | 347677 | 1 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| updateGlobalState | 15820 | 25876 | 29230 | 29230 | 8 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| updateVault | 31948 | 34543 | 31948 | 110579 | 1024 |
| updateVault | 31948 | 34668 | 31948 | 110579 | 1023 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
| upgradeTo | 10279 | 10772 | 10279 | 12745 | 5 |
|--------------------------------------------+-----------------+--------+--------+--------+---------|
@@ -276,9 +276,9 @@
|----------------------------------------+-----------------+--------+--------+--------+---------|
| leave | 12223 | 113137 | 84120 | 356508 | 5 |
|----------------------------------------+-----------------+--------+--------+--------+---------|
| lock | 12151 | 59083 | 62251 | 103499 | 1035 |
| lock | 12151 | 58965 | 62251 | 103208 | 1035 |
|----------------------------------------+-----------------+--------+--------+--------+---------|
| lockUntil | 2363 | 2363 | 2363 | 2363 | 7769 |
| lockUntil | 2363 | 2363 | 2363 | 2363 | 7766 |
|----------------------------------------+-----------------+--------+--------+--------+---------|
| migrateToVault | 24910 | 77530 | 32637 | 219937 | 4 |
|----------------------------------------+-----------------+--------+--------+--------+---------|
@@ -286,15 +286,15 @@
|----------------------------------------+-----------------+--------+--------+--------+---------|
| register | 12742 | 78218 | 78761 | 78761 | 374 |
|----------------------------------------+-----------------+--------+--------+--------+---------|
| stake | 12131 | 166249 | 76290 | 284275 | 2671 |
| stake | 12131 | 167018 | 76290 | 284275 | 2671 |
|----------------------------------------+-----------------+--------+--------+--------+---------|
| stakeManager | 393 | 393 | 393 | 393 | 373 |
|----------------------------------------+-----------------+--------+--------+--------+---------|
| trustStakeManager | 7650 | 7650 | 7650 | 7650 | 1 |
|----------------------------------------+-----------------+--------+--------+--------+---------|
| unstake | 12108 | 58059 | 55296 | 110656 | 272 |
| unstake | 12108 | 57840 | 55296 | 110656 | 272 |
|----------------------------------------+-----------------+--------+--------+--------+---------|
| updateLockUntil | 4432 | 20722 | 21532 | 21532 | 524 |
| updateLockUntil | 4432 | 20702 | 21532 | 21532 | 532 |
|----------------------------------------+-----------------+--------+--------+--------+---------|
| withdraw | 20817 | 20817 | 20817 | 20817 | 1 |
|----------------------------------------+-----------------+--------+--------+--------+---------|
@@ -312,9 +312,9 @@
|----------------------------------------------------+-----------------+-------+--------+--------+---------|
| Function Name | Min | Avg | Median | Max | # Calls |
|----------------------------------------------------+-----------------+-------+--------+--------+---------|
| fallback | 5208 | 12842 | 7353 | 374054 | 23167 |
| fallback | 5208 | 12845 | 7353 | 374054 | 23161 |
|----------------------------------------------------+-----------------+-------+--------+--------+---------|
| implementation | 346 | 2131 | 2346 | 2346 | 4886 |
| implementation | 346 | 2128 | 2346 | 2346 | 4894 |
╰----------------------------------------------------+-----------------+-------+--------+--------+---------╯
╭--------------------------------------------+-----------------+--------+--------+--------+---------╮
@@ -373,6 +373,82 @@
| urlSuffix | 3228 | 3228 | 3228 | 3228 | 1 |
╰------------------------------------------------------------------------------------------+-----------------+-------+--------+-------+---------╯
╭------------------------------+-----------------+--------+--------+--------+---------╮
| src/rln/RLN.sol:RLN Contract | | | | | |
+=====================================================================================+
| Deployment Cost | Deployment Size | | | | |
|------------------------------+-----------------+--------+--------+--------+---------|
| 1396408 | 7094 | | | | |
|------------------------------+-----------------+--------+--------+--------+---------|
| | | | | | |
|------------------------------+-----------------+--------+--------+--------+---------|
| Function Name | Min | Avg | Median | Max | # Calls |
|------------------------------+-----------------+--------+--------+--------+---------|
| FEE_PERCENTAGE | 217 | 217 | 217 | 217 | 1 |
|------------------------------+-----------------+--------+--------+--------+---------|
| FEE_RECEIVER | 226 | 226 | 226 | 226 | 1 |
|------------------------------+-----------------+--------+--------+--------+---------|
| FREEZE_PERIOD | 218 | 218 | 218 | 218 | 1 |
|------------------------------+-----------------+--------+--------+--------+---------|
| MINIMAL_DEPOSIT | 241 | 241 | 241 | 241 | 1 |
|------------------------------+-----------------+--------+--------+--------+---------|
| SET_SIZE | 284 | 284 | 284 | 284 | 1 |
|------------------------------+-----------------+--------+--------+--------+---------|
| identityCommitmentIndex | 2362 | 2362 | 2362 | 2362 | 27 |
|------------------------------+-----------------+--------+--------+--------+---------|
| members | 6826 | 6826 | 6826 | 6826 | 18 |
|------------------------------+-----------------+--------+--------+--------+---------|
| register | 23877 | 105927 | 123897 | 126709 | 18 |
|------------------------------+-----------------+--------+--------+--------+---------|
| release | 28093 | 37708 | 28192 | 66355 | 4 |
|------------------------------+-----------------+--------+--------+--------+---------|
| slash | 23015 | 49020 | 34650 | 99415 | 6 |
|------------------------------+-----------------+--------+--------+--------+---------|
| token | 292 | 292 | 292 | 292 | 1 |
|------------------------------+-----------------+--------+--------+--------+---------|
| verifier | 272 | 272 | 272 | 272 | 1 |
|------------------------------+-----------------+--------+--------+--------+---------|
| withdraw | 29336 | 79495 | 106774 | 106774 | 8 |
|------------------------------+-----------------+--------+--------+--------+---------|
| withdrawals | 6792 | 6792 | 6792 | 6792 | 5 |
╰------------------------------+-----------------+--------+--------+--------+---------╯
╭--------------------------------------+-----------------+-------+--------+-------+---------╮
| test/RLN.t.sol:MockVerifier Contract | | | | | |
+===========================================================================================+
| Deployment Cost | Deployment Size | | | | |
|--------------------------------------+-----------------+-------+--------+-------+---------|
| 204321 | 642 | | | | |
|--------------------------------------+-----------------+-------+--------+-------+---------|
| | | | | | |
|--------------------------------------+-----------------+-------+--------+-------+---------|
| Function Name | Min | Avg | Median | Max | # Calls |
|--------------------------------------+-----------------+-------+--------+-------+---------|
| changeResult | 21649 | 21649 | 21649 | 21649 | 2 |
|--------------------------------------+-----------------+-------+--------+-------+---------|
| result | 2298 | 2298 | 2298 | 2298 | 5 |
|--------------------------------------+-----------------+-------+--------+-------+---------|
| verifyProof | 4790 | 4790 | 4790 | 4790 | 9 |
╰--------------------------------------+-----------------+-------+--------+-------+---------╯
╭-----------------------------------+-----------------+-------+--------+-------+---------╮
| 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 | | | | | |
+========================================================================================================================+
@@ -418,11 +494,11 @@
|---------------------------------------------+-----------------+-------+--------+-------+---------|
| Function Name | Min | Avg | Median | Max | # Calls |
|---------------------------------------------+-----------------+-------+--------+-------+---------|
| approve | 29075 | 31544 | 29183 | 46259 | 2676 |
| approve | 29075 | 31546 | 29183 | 46259 | 2676 |
|---------------------------------------------+-----------------+-------+--------+-------+---------|
| balanceOf | 2561 | 2561 | 2561 | 2561 | 4960 |
| balanceOf | 2561 | 2561 | 2561 | 2561 | 4959 |
|---------------------------------------------+-----------------+-------+--------+-------+---------|
| mint | 33964 | 37189 | 34072 | 68248 | 2685 |
| mint | 33964 | 37192 | 34072 | 68248 | 2685 |
╰---------------------------------------------+-----------------+-------+--------+-------+---------╯
╭-----------------------------------------------------------------------------+-----------------+--------+--------+--------+---------╮

View File

@@ -16,15 +16,15 @@ EmergencyExitTest:test_EmergencyExitToAlternateAddress() (gas: 479110)
EmergencyExitTest:test_EmergencyExitWithLock() (gas: 452444)
EmergencyExitTest:test_EmergencyExitWithRewards() (gas: 484810)
EmergencyExitTest:test_OnlyOwnerCanEnableEmergencyMode() (gas: 39176)
FuzzTests:testFuzz_AccrueMP(uint128,uint64,uint64) (runs: 1009, μ: 586777, ~: 549070)
FuzzTests:testFuzz_AccrueMP_Relock(uint128,uint64,uint64,uint64) (runs: 1009, μ: 811994, ~: 777237)
FuzzTests:testFuzz_EmergencyExit(uint256,uint256) (runs: 1001, μ: 588323, ~: 578267)
FuzzTests:testFuzz_Lock(uint256,uint64) (runs: 1008, μ: 961825, ~: 961235)
FuzzTests:testFuzz_Relock(uint256,uint64,uint64) (runs: 1008, μ: 598425, ~: 574225)
FuzzTests:testFuzz_Rewards(uint256,uint256,uint256,uint16,uint16) (runs: 1001, μ: 650378, ~: 653205)
FuzzTests:testFuzz_Stake(uint256,uint64) (runs: 1008, μ: 375317, ~: 346086)
FuzzTests:testFuzz_Unstake(uint128,uint64,uint16,uint128) (runs: 1009, μ: 806735, ~: 780622)
FuzzTests:testFuzz_UpdateVault(uint128,uint64,uint64) (runs: 1009, μ: 586800, ~: 549093)
FuzzTests:testFuzz_AccrueMP(uint128,uint64,uint64) (runs: 1024, μ: 587151, ~: 549070)
FuzzTests:testFuzz_AccrueMP_Relock(uint128,uint64,uint64,uint64) (runs: 1024, μ: 812396, ~: 777234)
FuzzTests:testFuzz_EmergencyExit(uint256,uint256) (runs: 1007, μ: 588350, ~: 578267)
FuzzTests:testFuzz_Lock(uint256,uint64) (runs: 1025, μ: 961806, ~: 961235)
FuzzTests:testFuzz_Relock(uint256,uint64,uint64) (runs: 1025, μ: 599078, ~: 574234)
FuzzTests:testFuzz_Rewards(uint256,uint256,uint256,uint16,uint16) (runs: 1001, μ: 650369, ~: 653205)
FuzzTests:testFuzz_Stake(uint256,uint64) (runs: 1025, μ: 376001, ~: 346086)
FuzzTests:testFuzz_Unstake(uint128,uint64,uint16,uint128) (runs: 1024, μ: 806573, ~: 780622)
FuzzTests:testFuzz_UpdateVault(uint128,uint64,uint64) (runs: 1024, μ: 587174, ~: 549093)
IntegrationTest:testStakeFoo() (gas: 2348931)
KarmaNFTTest:testApproveNotAllowed() (gas: 10507)
KarmaNFTTest:testGetApproved() (gas: 10531)
@@ -58,7 +58,7 @@ LeaveTest:test_LeaveShouldKeepFundsLockedInStakeVault() (gas: 9938411)
LeaveTest:test_LeaveShouldProperlyUpdateAccounting() (gas: 10011059)
LeaveTest:test_RevertWhenStakeManagerIsTrusted() (gas: 333238)
LeaveTest:test_TrustNewStakeManager() (gas: 9944491)
LockTest:test_LockFailsWithInvalidPeriod(uint256) (runs: 1008, μ: 384561, ~: 384588)
LockTest:test_LockFailsWithInvalidPeriod(uint256) (runs: 1025, μ: 384562, ~: 384588)
LockTest:test_LockFailsWithNoStake() (gas: 89700)
LockTest:test_LockFailsWithZero() (gas: 343310)
LockTest:test_LockMultipleTimesExceedMaxLock() (gas: 746921)
@@ -89,6 +89,23 @@ OverflowTest:testTotalSupply() (gas: 359166)
OverflowTest:testTransfersNotAllowed() (gas: 61763)
OverflowTest:test_RevertWhen_MintingCausesOverflow() (gas: 129464)
OverflowTest:test_RevertWhen_SettingRewardCausesOverflow() (gas: 127792)
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)
RemoveRewardDistributorTest:testAddKarmaDistributorOnlyAdmin() (gas: 438045)
RemoveRewardDistributorTest:testBalanceOf() (gas: 449366)
RemoveRewardDistributorTest:testBalanceOfWithNoSystemTotalKarma() (gas: 69633)

1
.gitignore vendored
View File

@@ -4,7 +4,6 @@ node_modules
out
# files
*.env
*.log
.DS_Store
.pnp.*

27
script/RLN.s.sol Normal file
View File

@@ -0,0 +1,27 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
pragma solidity ^0.8.17;
import "forge-std/Script.sol";
import "../src/rln/RLN.sol";
import "../src/rln/Verifier.sol";
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");
vm.startBroadcast();
Groth16Verifier verifier = new Groth16Verifier();
RLN rln = new RLN(
minimalDeposit, maximalRate, depth, feePercentage, feeReceiver, freezePeriod, token, address(verifier)
);
vm.stopBroadcast();
}
}

14
src/rln/IVerifier.sol Normal file
View File

@@ -0,0 +1,14 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
pragma solidity ^0.8.17;
interface IVerifier {
function verifyProof(
uint256[2] calldata a,
uint256[2][2] calldata b,
uint256[2] calldata c,
uint256[2] calldata input
)
external
view
returns (bool);
}

209
src/rln/RLN.sol Normal file
View File

@@ -0,0 +1,209 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
pragma solidity ^0.8.17;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.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;
/// @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;
/// @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;
/// @dev Withdrawals logic.
mapping(uint256 => Withdrawal) public withdrawals;
/// @dev ERC20 Token used for staking.
IERC20 public immutable token;
/// @dev Groth16 verifier.
IVerifier public immutable 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);
/// @dev Emmited when a member was withdrawn.
/// @param index: index of `identityCommitment`;
event MemberWithdrawn(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;
/// @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,
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;
SET_SIZE = 1 << depth;
FEE_PERCENTAGE = feePercentage;
FEE_RECEIVER = feeReceiver;
FREEZE_PERIOD = freezePeriod;
token = IERC20(_token);
verifier = IVerifier(_verifier);
}
/// @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 {
uint256 index = identityCommitmentIndex;
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);
unchecked {
identityCommitmentIndex = index + 1;
}
}
/// @dev Request for withdraw and freeze the stake to prevent self-slashing. Stake can be
/// released after FREEZE_PERIOD blocks.
/// @param identityCommitment: `identityCommitment`;
/// @param proof: snarkjs's format generated proof (without public inputs) packed consequently.
function withdraw(uint256 identityCommitment, uint256[8] calldata proof) external {
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");
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);
}
/// @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");
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");
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);
}
/// @dev Groth16 proof verification
function _verifyProof(
uint256 identityCommitment,
address receiver,
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))]
);
}
}

202
src/rln/Verifier.sol Normal file
View File

@@ -0,0 +1,202 @@
// SPDX-License-Identifier: GPL-3.0
/*
Copyright 2021 0KIMS association.
This file is generated with [snarkJS](https://github.com/iden3/snarkjs).
snarkJS is a free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
snarkJS is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with snarkJS. If not, see <https://www.gnu.org/licenses/>.
*/
pragma solidity >=0.7.0 <0.9.0;
contract Groth16Verifier {
// Scalar field size
uint256 constant r =
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;
// Base field size
uint256 constant q =
21_888_242_871_839_275_222_246_405_745_257_275_088_696_311_157_297_823_662_689_037_894_645_226_208_583;
// Verification Key data
uint256 constant alphax =
20_491_192_805_390_485_299_153_009_773_594_534_940_189_261_866_228_447_918_068_658_471_970_481_763_042;
uint256 constant alphay =
9_383_485_363_053_290_200_918_347_156_157_836_566_562_967_994_039_712_273_449_902_621_266_178_545_958;
uint256 constant betax1 =
4_252_822_878_758_300_859_123_897_981_450_591_353_533_073_413_197_771_768_651_442_665_752_259_397_132;
uint256 constant betax2 =
6_375_614_351_688_725_206_403_948_262_868_962_793_625_744_043_794_305_715_222_011_528_459_656_738_731;
uint256 constant betay1 =
21_847_035_105_528_745_403_288_232_691_147_584_728_191_162_732_299_865_338_377_159_692_350_059_136_679;
uint256 constant betay2 =
10_505_242_626_370_262_277_552_901_082_094_356_697_409_835_680_220_590_971_873_171_140_371_331_206_856;
uint256 constant gammax1 =
11_559_732_032_986_387_107_991_004_021_392_285_783_925_812_861_821_192_530_917_403_151_452_391_805_634;
uint256 constant gammax2 =
10_857_046_999_023_057_135_944_570_762_232_829_481_370_756_359_578_518_086_990_519_993_285_655_852_781;
uint256 constant gammay1 =
4_082_367_875_863_433_681_332_203_403_145_435_568_316_851_327_593_401_208_105_741_076_214_120_093_531;
uint256 constant gammay2 =
8_495_653_923_123_431_417_604_973_247_489_272_438_418_190_587_263_600_148_770_280_649_306_958_101_930;
uint256 constant deltax1 =
11_551_021_181_461_167_826_461_759_140_322_976_337_427_023_987_127_500_996_455_457_136_635_486_400_849;
uint256 constant deltax2 =
16_440_331_697_014_556_916_876_897_072_677_669_680_121_386_347_171_004_539_353_252_116_876_383_229_648;
uint256 constant deltay1 =
16_935_666_883_311_644_237_478_257_793_713_075_046_951_222_500_094_443_222_621_864_730_929_684_383_196;
uint256 constant deltay2 =
15_542_122_065_551_809_636_611_024_015_159_689_732_218_122_471_819_623_718_488_423_844_152_861_811_271;
uint256 constant IC0x =
19_490_069_286_251_317_200_471_893_224_761_952_280_235_157_078_692_599_655_063_040_494_106_083_015_102;
uint256 constant IC0y =
15_613_730_057_977_833_735_664_106_983_317_680_013_118_142_165_231_654_768_046_521_650_638_333_652_991;
uint256 constant IC1x =
1_563_543_155_852_853_229_359_605_494_188_815_884_199_915_022_658_219_002_707_722_789_976_065_966_419;
uint256 constant IC1y =
858_819_375_930_654_753_672_617_171_465_307_097_688_802_650_498_051_619_587_167_586_479_724_200_799;
uint256 constant IC2x =
3_808_889_614_445_935_800_597_561_392_085_733_302_718_838_702_771_107_544_944_545_050_886_958_022_904;
uint256 constant IC2y =
13_293_649_293_049_947_010_793_838_294_353_767_499_934_999_769_633_605_908_974_566_715_226_392_122_400;
// Memory data
uint16 constant pVk = 0;
uint16 constant pPairing = 128;
uint16 constant pLastMem = 896;
function verifyProof(
uint256[2] calldata _pA,
uint256[2][2] calldata _pB,
uint256[2] calldata _pC,
uint256[2] calldata _pubSignals
)
public
view
returns (bool)
{
assembly {
function checkField(v) {
if iszero(lt(v, q)) {
mstore(0, 0)
return(0, 0x20)
}
}
// G1 function to multiply a G1 value(x,y) to value in an address
function g1_mulAccC(pR, x, y, s) {
let success
let mIn := mload(0x40)
mstore(mIn, x)
mstore(add(mIn, 32), y)
mstore(add(mIn, 64), s)
success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64)
if iszero(success) {
mstore(0, 0)
return(0, 0x20)
}
mstore(add(mIn, 64), mload(pR))
mstore(add(mIn, 96), mload(add(pR, 32)))
success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64)
if iszero(success) {
mstore(0, 0)
return(0, 0x20)
}
}
function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk {
let _pPairing := add(pMem, pPairing)
let _pVk := add(pMem, pVk)
mstore(_pVk, IC0x)
mstore(add(_pVk, 32), IC0y)
// Compute the linear combination vk_x
g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0)))
g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32)))
// -A
mstore(_pPairing, calldataload(pA))
mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q))
// B
mstore(add(_pPairing, 64), calldataload(pB))
mstore(add(_pPairing, 96), calldataload(add(pB, 32)))
mstore(add(_pPairing, 128), calldataload(add(pB, 64)))
mstore(add(_pPairing, 160), calldataload(add(pB, 96)))
// alpha1
mstore(add(_pPairing, 192), alphax)
mstore(add(_pPairing, 224), alphay)
// beta2
mstore(add(_pPairing, 256), betax1)
mstore(add(_pPairing, 288), betax2)
mstore(add(_pPairing, 320), betay1)
mstore(add(_pPairing, 352), betay2)
// vk_x
mstore(add(_pPairing, 384), mload(add(pMem, pVk)))
mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32))))
// gamma2
mstore(add(_pPairing, 448), gammax1)
mstore(add(_pPairing, 480), gammax2)
mstore(add(_pPairing, 512), gammay1)
mstore(add(_pPairing, 544), gammay2)
// C
mstore(add(_pPairing, 576), calldataload(pC))
mstore(add(_pPairing, 608), calldataload(add(pC, 32)))
// delta2
mstore(add(_pPairing, 640), deltax1)
mstore(add(_pPairing, 672), deltax2)
mstore(add(_pPairing, 704), deltay1)
mstore(add(_pPairing, 736), deltay2)
let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20)
isOk := and(success, mload(_pPairing))
}
let pMem := mload(0x40)
mstore(0x40, add(pMem, pLastMem))
// Validate that all evaluations ∈ F
checkField(calldataload(add(_pubSignals, 0)))
checkField(calldataload(add(_pubSignals, 32)))
checkField(calldataload(add(_pubSignals, 64)))
// Validate all evaluations
let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem)
mstore(0, isValid)
return(0, 0x20)
}
}
}

395
test/RLN.t.sol Normal file
View File

@@ -0,0 +1,395 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
pragma solidity ^0.8.17;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "forge-std/Test.sol";
import "../src/rln/RLN.sol";
import { IVerifier } from "../src/rln/IVerifier.sol";
// A ERC20 token contract which allows arbitrary minting for testing
contract TestERC20 is ERC20 {
constructor() ERC20("TestERC20", "TST") { }
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}
// A mock verifier which makes us skip the proof verification.
contract MockVerifier is IVerifier {
bool public result;
constructor() {
result = true;
}
function verifyProof(
uint256[2] memory,
uint256[2][2] memory,
uint256[2] memory,
uint256[2] memory
)
external
view
returns (bool)
{
return result;
}
function changeResult(bool _result) external {
result = _result;
}
}
contract RLNTest is Test {
event MemberRegistered(uint256 identityCommitment, uint256 messageLimit, uint256 index);
event MemberWithdrawn(uint256 index);
event MemberSlashed(uint256 index, address slasher);
RLN rln;
TestERC20 token;
MockVerifier verifier;
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;
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 =
[uint256(0), uint256(1), uint256(2), uint256(3), uint256(4), uint256(5), uint256(6), uint256(7)];
function setUp() public {
token = new TestERC20();
verifier = new MockVerifier();
rln = new RLN(
minimalDeposit,
maximalRate,
depth,
feePercentage,
feeReceiver,
freezePeriod,
address(token),
address(verifier)
);
}
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));
assertEq(rln.identityCommitmentIndex(), 0);
}
/* register */
function test_register_succeeds() public {
// Test: register one user
register(user0, identityCommitment0, messageLimit0);
// Test: register second user
register(user1, identityCommitment1, messageLimit1);
}
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)
);
// Register the first user
_token.mint(user0, minimalDeposit);
vm.startPrank(user0);
_token.approve(address(smallRLN), minimalDeposit);
smallRLN.register(identityCommitment0, minimalDeposit);
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);
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);
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);
vm.stopPrank();
}
/* withdraw */
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
verifier.changeResult(false);
assertEq(verifier.result(), false);
vm.expectRevert("RLN, withdraw: invalid proof");
rln.withdraw(identityCommitment0, mockProof);
}
/* 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 */
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);
// 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);
}
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);
}
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);
}
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);
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);
}
function getUnfrozenBlockHeight() public view returns (uint256) {
return block.number + freezePeriod + 1;
}
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;
}
}