2023-03-29 06:58:42 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2023-03-29 06:28:09 +00:00
|
|
|
|
2022-08-09 17:29:04 +00:00
|
|
|
pragma solidity 0.8.15;
|
2022-06-23 13:36:10 +00:00
|
|
|
|
2023-03-29 06:28:09 +00:00
|
|
|
import {IPoseidonHasher} from "./PoseidonHasher.sol";
|
2022-06-23 13:36:10 +00:00
|
|
|
|
2023-03-30 12:00:54 +00:00
|
|
|
/// Invalid deposit amount
|
|
|
|
/// @param required The required deposit amount
|
|
|
|
/// @param provided The provided deposit amount
|
|
|
|
error InsufficientDeposit(uint256 required, uint256 provided);
|
|
|
|
|
|
|
|
/// Provided Batch is empty
|
|
|
|
error EmptyBatch();
|
|
|
|
|
|
|
|
/// Batch is full, during batch operations
|
|
|
|
error FullBatch();
|
|
|
|
|
|
|
|
/// Member is already registered
|
|
|
|
error DuplicateIdCommitment();
|
|
|
|
|
|
|
|
/// Batch size mismatch, when the length of secrets and receivers are not equal
|
|
|
|
/// @param givenSecretsLen The length of the secrets array
|
|
|
|
/// @param givenReceiversLen The length of the receivers array
|
|
|
|
error MismatchedBatchSize(uint256 givenSecretsLen, uint256 givenReceiversLen);
|
|
|
|
|
|
|
|
/// Invalid withdrawal address, when the receiver is the contract itself or 0x0
|
|
|
|
error InvalidWithdrawalAddress(address to);
|
|
|
|
|
|
|
|
/// Member is not registered
|
|
|
|
error MemberNotRegistered(uint256 idCommitment);
|
|
|
|
|
|
|
|
/// Member has no stake
|
|
|
|
error MemberHasNoStake(uint256 idCommitment);
|
|
|
|
|
2022-06-23 13:36:10 +00:00
|
|
|
contract RLN {
|
2023-03-30 06:51:48 +00:00
|
|
|
/// @notice The deposit amount required to register as a member
|
2023-03-29 06:28:09 +00:00
|
|
|
uint256 public immutable MEMBERSHIP_DEPOSIT;
|
2023-03-30 06:51:48 +00:00
|
|
|
|
|
|
|
/// @notice The depth of the merkle tree
|
2023-03-29 06:28:09 +00:00
|
|
|
uint256 public immutable DEPTH;
|
2023-03-30 06:51:48 +00:00
|
|
|
|
|
|
|
/// @notice The size of the merkle tree, i.e 2^depth
|
2023-03-29 06:28:09 +00:00
|
|
|
uint256 public immutable SET_SIZE;
|
|
|
|
|
2023-03-30 06:51:48 +00:00
|
|
|
/// @notice The index of the next member to be registered
|
2023-03-29 06:28:09 +00:00
|
|
|
uint256 public idCommitmentIndex;
|
2023-03-30 06:51:48 +00:00
|
|
|
|
|
|
|
/// @notice The amount of eth staked by each member
|
2023-03-29 06:28:09 +00:00
|
|
|
mapping(uint256 => uint256) public stakedAmounts;
|
2023-03-30 06:51:48 +00:00
|
|
|
|
|
|
|
/// @notice The membership status of each member
|
2023-03-29 06:28:09 +00:00
|
|
|
mapping(uint256 => bool) public members;
|
|
|
|
|
2023-03-30 06:51:48 +00:00
|
|
|
/// @notice The Poseidon hasher contract
|
2023-03-29 06:28:09 +00:00
|
|
|
IPoseidonHasher public poseidonHasher;
|
|
|
|
|
2023-03-30 06:51:48 +00:00
|
|
|
/// 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
|
2023-03-29 06:28:09 +00:00
|
|
|
event MemberRegistered(uint256 idCommitment, uint256 index);
|
2023-03-30 06:51:48 +00:00
|
|
|
|
|
|
|
/// Emitted when a member is removed from the set
|
|
|
|
/// @param idCommitment The idCommitment of the member
|
2023-03-29 06:28:09 +00:00
|
|
|
event MemberWithdrawn(uint256 idCommitment);
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
uint256 membershipDeposit,
|
|
|
|
uint256 depth,
|
|
|
|
address _poseidonHasher
|
|
|
|
) {
|
|
|
|
MEMBERSHIP_DEPOSIT = membershipDeposit;
|
|
|
|
DEPTH = depth;
|
|
|
|
SET_SIZE = 1 << depth;
|
|
|
|
poseidonHasher = IPoseidonHasher(_poseidonHasher);
|
|
|
|
}
|
|
|
|
|
2023-03-30 06:51:48 +00:00
|
|
|
/// Allows a user to register as a member
|
|
|
|
/// @param idCommitment The idCommitment of the member
|
2023-03-29 06:28:09 +00:00
|
|
|
function register(uint256 idCommitment) external payable {
|
2023-03-30 12:00:54 +00:00
|
|
|
if (msg.value != MEMBERSHIP_DEPOSIT)
|
|
|
|
revert InsufficientDeposit(MEMBERSHIP_DEPOSIT, msg.value);
|
2023-03-29 06:28:09 +00:00
|
|
|
_register(idCommitment, msg.value);
|
|
|
|
}
|
|
|
|
|
2023-03-30 06:51:48 +00:00
|
|
|
/// Allows batch registration of members
|
|
|
|
/// @param idCommitments array of idCommitments
|
2023-03-29 06:28:09 +00:00
|
|
|
function registerBatch(uint256[] calldata idCommitments) external payable {
|
|
|
|
uint256 idCommitmentlen = idCommitments.length;
|
2023-03-30 12:00:54 +00:00
|
|
|
if (idCommitmentlen == 0) revert EmptyBatch();
|
|
|
|
if (idCommitmentIndex + idCommitmentlen >= SET_SIZE) revert FullBatch();
|
|
|
|
uint256 requiredDeposit = MEMBERSHIP_DEPOSIT * idCommitmentlen;
|
|
|
|
if (msg.value != requiredDeposit)
|
|
|
|
revert InsufficientDeposit(requiredDeposit, msg.value);
|
2023-03-29 06:28:09 +00:00
|
|
|
for (uint256 i = 0; i < idCommitmentlen; i++) {
|
|
|
|
_register(idCommitments[i], msg.value / idCommitmentlen);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-30 06:51:48 +00:00
|
|
|
/// Registers a member
|
|
|
|
/// @param idCommitment The idCommitment of the member
|
|
|
|
/// @param stake The amount of eth staked by the member
|
2023-03-29 06:28:09 +00:00
|
|
|
function _register(uint256 idCommitment, uint256 stake) internal {
|
2023-03-30 12:00:54 +00:00
|
|
|
if (members[idCommitment]) revert DuplicateIdCommitment();
|
|
|
|
if (idCommitmentIndex >= SET_SIZE) revert FullBatch();
|
2023-03-29 11:48:00 +00:00
|
|
|
|
|
|
|
members[idCommitment] = true;
|
|
|
|
stakedAmounts[idCommitment] = stake;
|
|
|
|
|
2023-03-29 06:28:09 +00:00
|
|
|
emit MemberRegistered(idCommitment, idCommitmentIndex);
|
|
|
|
idCommitmentIndex += 1;
|
|
|
|
}
|
|
|
|
|
2023-03-30 06:51:48 +00:00
|
|
|
/// Allows a user to slash a batch of members
|
|
|
|
/// @param secrets array of idSecretHashes
|
|
|
|
/// @param receivers array of addresses to receive the funds
|
2023-03-29 06:28:09 +00:00
|
|
|
function withdrawBatch(
|
|
|
|
uint256[] calldata secrets,
|
|
|
|
address payable[] calldata receivers
|
|
|
|
) external {
|
|
|
|
uint256 batchSize = secrets.length;
|
2023-03-30 12:00:54 +00:00
|
|
|
if (batchSize == 0) revert EmptyBatch();
|
|
|
|
if (batchSize != receivers.length)
|
|
|
|
revert MismatchedBatchSize(secrets.length, receivers.length);
|
2023-03-29 06:28:09 +00:00
|
|
|
for (uint256 i = 0; i < batchSize; i++) {
|
|
|
|
_withdraw(secrets[i], receivers[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-30 06:51:48 +00:00
|
|
|
/// Allows a user to slash a member
|
|
|
|
/// @param secret The idSecretHash of the member
|
2023-03-29 06:28:09 +00:00
|
|
|
function withdraw(uint256 secret, address payable receiver) external {
|
|
|
|
_withdraw(secret, receiver);
|
|
|
|
}
|
|
|
|
|
2023-03-30 06:51:48 +00:00
|
|
|
/// Slashes a member by removing them from the set, and transferring their
|
|
|
|
/// stake to the receiver
|
|
|
|
/// @param secret The idSecretHash of the member
|
|
|
|
/// @param receiver The address to receive the funds
|
2023-03-29 06:28:09 +00:00
|
|
|
function _withdraw(uint256 secret, address payable receiver) internal {
|
2023-03-30 12:00:54 +00:00
|
|
|
if (receiver == address(this) || receiver == address(0))
|
|
|
|
revert InvalidWithdrawalAddress(receiver);
|
2023-03-29 11:48:00 +00:00
|
|
|
|
2023-03-29 06:28:09 +00:00
|
|
|
// derive idCommitment
|
|
|
|
uint256 idCommitment = hash(secret);
|
|
|
|
// check if member is registered
|
2023-03-30 12:00:54 +00:00
|
|
|
if (!members[idCommitment]) revert MemberNotRegistered(idCommitment);
|
|
|
|
if (stakedAmounts[idCommitment] == 0)
|
|
|
|
revert MemberHasNoStake(idCommitment);
|
2023-03-29 06:28:09 +00:00
|
|
|
|
2023-03-29 11:48:00 +00:00
|
|
|
uint256 amountToTransfer = stakedAmounts[idCommitment];
|
2023-03-29 06:28:09 +00:00
|
|
|
|
|
|
|
// delete member
|
|
|
|
members[idCommitment] = false;
|
|
|
|
stakedAmounts[idCommitment] = 0;
|
|
|
|
|
|
|
|
// refund deposit
|
2023-03-29 11:48:00 +00:00
|
|
|
receiver.transfer(amountToTransfer);
|
2022-11-25 09:04:30 +00:00
|
|
|
|
2023-03-29 06:28:09 +00:00
|
|
|
emit MemberWithdrawn(idCommitment);
|
|
|
|
}
|
2022-06-23 13:36:10 +00:00
|
|
|
|
2023-03-30 06:51:48 +00:00
|
|
|
/// 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
|
2023-03-29 06:28:09 +00:00
|
|
|
function hash(uint256 input) internal view returns (uint256) {
|
|
|
|
return poseidonHasher.hash(input);
|
|
|
|
}
|
|
|
|
}
|