-
Notifications
You must be signed in to change notification settings - Fork 6
/
RlnBase.sol
214 lines (166 loc) · 7.29 KB
/
RlnBase.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import {PoseidonHasher} from "./PoseidonHasher.sol";
import {IVerifier} from "./IVerifier.sol";
/// The tree is full
error FullTree();
/// Invalid deposit amount
/// @param required The required deposit amount
/// @param provided The provided deposit amount
error InsufficientDeposit(uint256 required, uint256 provided);
/// Member is already registered
error DuplicateIdCommitment();
/// Failed validation on registration/slashing
error FailedValidation();
/// Invalid idCommitment
error InvalidIdCommitment(uint256 idCommitment);
/// Invalid receiver address, when the receiver is the contract itself or 0x0
error InvalidReceiverAddress(address to);
/// Member is not registered
error MemberNotRegistered(uint256 idCommitment);
/// Member has no stake
error MemberHasNoStake(uint256 idCommitment);
/// User has insufficient balance to withdraw
error InsufficientWithdrawalBalance();
/// Contract has insufficient balance to return
error InsufficientContractBalance();
/// Invalid proof
error InvalidProof();
abstract contract RlnBase {
/// @notice The deposit amount required to register as a member
uint256 public immutable MEMBERSHIP_DEPOSIT;
/// @notice The depth of the merkle tree
uint256 public immutable DEPTH;
/// @notice The size of the merkle tree, i.e 2^depth
uint256 public immutable SET_SIZE;
/// @notice The index of the next member to be registered
uint256 public idCommitmentIndex = 1;
/// @notice The amount of eth staked by each member
/// maps from idCommitment to the amount staked
mapping(uint256 => uint256) public stakedAmounts;
/// @notice The membership status of each member
/// maps from idCommitment to their index in the set
mapping(uint256 => uint256) public members;
/// @notice The balance of each user that can be withdrawn
mapping(address => uint256) public withdrawalBalance;
/// @notice The Poseidon hasher contract
PoseidonHasher public immutable poseidonHasher;
/// @notice The groth16 verifier contract
IVerifier public immutable verifier;
/// @notice the deployed block number
uint32 public immutable deployedBlockNumber;
/// Emitted when a new member is added to the set
/// @param idCommitment The idCommitment of the member
/// @param index The index of the member in the set
event MemberRegistered(uint256 idCommitment, uint256 index);
/// Emitted when a member is removed from the set
/// @param idCommitment The idCommitment of the member
/// @param index The index of the member in the set
event MemberWithdrawn(uint256 idCommitment, uint256 index);
modifier onlyValidIdCommitment(uint256 idCommitment) {
if (!isValidCommitment(idCommitment)) revert InvalidIdCommitment(idCommitment);
_;
}
constructor(uint256 membershipDeposit, uint256 depth, address _poseidonHasher, address _verifier) {
MEMBERSHIP_DEPOSIT = membershipDeposit;
DEPTH = depth;
SET_SIZE = 1 << depth;
poseidonHasher = PoseidonHasher(_poseidonHasher);
verifier = IVerifier(_verifier);
deployedBlockNumber = uint32(block.number);
}
/// Allows a user to register as a member
/// @param idCommitment The idCommitment of the member
function register(uint256 idCommitment) external payable virtual onlyValidIdCommitment(idCommitment) {
if (msg.value != MEMBERSHIP_DEPOSIT) {
revert InsufficientDeposit(MEMBERSHIP_DEPOSIT, msg.value);
}
_validateRegistration(idCommitment);
_register(idCommitment, msg.value);
}
/// Registers a member
/// @param idCommitment The idCommitment of the member
/// @param stake The amount of eth staked by the member
function _register(uint256 idCommitment, uint256 stake) internal virtual {
if (members[idCommitment] != 0) revert DuplicateIdCommitment();
if (idCommitmentIndex >= SET_SIZE) revert FullTree();
members[idCommitment] = idCommitmentIndex;
stakedAmounts[idCommitment] = stake;
emit MemberRegistered(idCommitment, idCommitmentIndex);
idCommitmentIndex += 1;
}
/// @dev Inheriting contracts MUST override this function
function _validateRegistration(uint256 idCommitment) internal view virtual;
/// @dev Allows a user to slash a member
/// @param idCommitment The idCommitment of the member
function slash(uint256 idCommitment, address payable receiver, uint256[8] calldata proof)
external
virtual
onlyValidIdCommitment(idCommitment)
{
_validateSlash(idCommitment, receiver, proof);
_slash(idCommitment, receiver, proof);
}
/// @dev Slashes a member by removing them from the set, and adding their
/// stake to the receiver's available withdrawal balance
/// @param idCommitment The idCommitment of the member
/// @param receiver The address to receive the funds
function _slash(uint256 idCommitment, address payable receiver, uint256[8] calldata proof) internal virtual {
if (receiver == address(this) || receiver == address(0)) {
revert InvalidReceiverAddress(receiver);
}
if (members[idCommitment] == 0) revert MemberNotRegistered(idCommitment);
// check if member is registered
if (stakedAmounts[idCommitment] == 0) {
revert MemberHasNoStake(idCommitment);
}
if (!_verifyProof(idCommitment, receiver, proof)) {
revert InvalidProof();
}
uint256 amountToTransfer = stakedAmounts[idCommitment];
// delete member
uint256 index = members[idCommitment];
members[idCommitment] = 0;
stakedAmounts[idCommitment] = 0;
// refund deposit
withdrawalBalance[receiver] += amountToTransfer;
emit MemberWithdrawn(idCommitment, index);
}
function _validateSlash(uint256 idCommitment, address payable receiver, uint256[8] calldata proof)
internal
view
virtual;
/// Allows a user to withdraw funds allocated to them upon slashing a member
function withdraw() external virtual {
uint256 amount = withdrawalBalance[msg.sender];
if (amount == 0) revert InsufficientWithdrawalBalance();
if (amount > address(this).balance) {
revert InsufficientContractBalance();
}
withdrawalBalance[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}
/// Hashes a value using the Poseidon hasher
/// NOTE: The variant of Poseidon we use accepts only 1 input, assume n=2, and the second input is 0
/// @param input The value to hash
function hash(uint256 input) internal view returns (uint256) {
return poseidonHasher.hash(input);
}
function isValidCommitment(uint256 idCommitment) public view returns (bool) {
return idCommitment != 0 && idCommitment < poseidonHasher.Q();
}
/// @dev Groth16 proof verification
function _verifyProof(uint256 idCommitment, address receiver, uint256[8] calldata proof)
internal
view
virtual
returns (bool)
{
return verifier.verifyProof(
[proof[0], proof[1]],
[[proof[2], proof[3]], [proof[4], proof[5]]],
[proof[6], proof[7]],
[idCommitment, uint256(uint160(receiver))]
);
}
}