mirror of
https://github.com/vacp2p/staking-reward-streamer.git
synced 2026-01-08 04:33:52 -05:00
committed by
r4bbit
parent
382b1c1643
commit
edd63e75f9
80
.gas-report
80
.gas-report
@@ -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 | | | | |
|
||||
|---------------------------------------------+-----------------+-------+--------+-------+---------|
|
||||
| | | | | | |
|
||||
|---------------------------------------------+-----------------+-------+--------+-------+---------|
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
213
src/rln/RLN.sol
213
src/rln/RLN.sol
@@ -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)]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
543
test/RLN.t.sol
543
test/RLN.t.sol
@@ -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 (8‐element array)
|
||||
uint256[8] private mockProof =
|
||||
[uint256(0), uint256(1), uint256(2), uint256(3), uint256(4), uint256(5), uint256(6), uint256(7)];
|
||||
|
||||
// Role‐holders
|
||||
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);
|
||||
|
||||
// Sanity‐check 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 non‐existent 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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user