2023-03-29 09:49:32 +00:00
|
|
|
// SPDX-License-Identifier: Unlicense
|
|
|
|
pragma solidity ^0.8.15;
|
|
|
|
|
|
|
|
import "../contracts/PoseidonHasher.sol";
|
|
|
|
import "../contracts/Rln.sol";
|
|
|
|
import "forge-std/Test.sol";
|
2023-03-29 11:48:00 +00:00
|
|
|
import "forge-std/StdCheats.sol";
|
2023-03-29 09:49:32 +00:00
|
|
|
import "forge-std/console.sol";
|
|
|
|
|
2023-03-29 11:48:00 +00:00
|
|
|
contract ArrayUnique {
|
|
|
|
mapping(uint256 => bool) seen;
|
|
|
|
|
|
|
|
constructor(uint256[] memory arr) {
|
|
|
|
for (uint256 i = 0; i < arr.length; i++) {
|
|
|
|
require(!seen[arr[i]], "ArrayUnique: duplicate value");
|
|
|
|
seen[arr[i]] = true;
|
|
|
|
}
|
|
|
|
}
|
2023-03-29 12:21:48 +00:00
|
|
|
|
|
|
|
// contract in construction goes around the assumePayable() check
|
|
|
|
receive() external payable {}
|
2023-03-29 11:48:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function repeatElementIntoArray(
|
|
|
|
uint256 length,
|
|
|
|
address payable element
|
|
|
|
) pure returns (address payable[] memory) {
|
|
|
|
address payable[] memory arr = new address payable[](length);
|
|
|
|
for (uint256 i = 0; i < length; i++) {
|
|
|
|
arr[i] = element;
|
|
|
|
}
|
|
|
|
return arr;
|
|
|
|
}
|
|
|
|
|
2023-03-29 09:49:32 +00:00
|
|
|
contract RLNTest is Test {
|
2023-03-29 11:48:00 +00:00
|
|
|
using stdStorage for StdStorage;
|
|
|
|
|
2023-03-29 09:49:32 +00:00
|
|
|
RLN public rln;
|
2023-03-29 11:48:00 +00:00
|
|
|
PoseidonHasher public poseidon;
|
2023-03-29 09:49:32 +00:00
|
|
|
|
|
|
|
uint256 public constant MEMBERSHIP_DEPOSIT = 1000000000000000;
|
|
|
|
uint256 public constant DEPTH = 20;
|
|
|
|
uint256 public constant SET_SIZE = 1048576;
|
|
|
|
|
|
|
|
/// @dev Setup the testing environment.
|
|
|
|
function setUp() public {
|
2023-03-29 11:48:00 +00:00
|
|
|
poseidon = new PoseidonHasher();
|
2023-03-29 09:49:32 +00:00
|
|
|
rln = new RLN(MEMBERSHIP_DEPOSIT, DEPTH, address(poseidon));
|
|
|
|
}
|
|
|
|
|
2023-03-29 11:48:00 +00:00
|
|
|
function isUniqueArray(uint256[] memory arr) internal returns (bool) {
|
|
|
|
try new ArrayUnique(arr) {
|
|
|
|
return true;
|
|
|
|
} catch {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-29 09:49:32 +00:00
|
|
|
/// @dev Ensure that you can hash a value.
|
|
|
|
function test__Constants() public {
|
|
|
|
assertEq(rln.MEMBERSHIP_DEPOSIT(), MEMBERSHIP_DEPOSIT);
|
|
|
|
assertEq(rln.DEPTH(), DEPTH);
|
|
|
|
assertEq(rln.SET_SIZE(), SET_SIZE);
|
|
|
|
}
|
|
|
|
|
|
|
|
function test__ValidRegistration(uint256 idCommitment) public {
|
|
|
|
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
|
|
|
|
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
|
|
|
|
assertEq(rln.members(idCommitment), true);
|
|
|
|
}
|
|
|
|
|
|
|
|
function test__InvalidRegistration__DuplicateCommitment(
|
|
|
|
uint256 idCommitment
|
|
|
|
) public {
|
|
|
|
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
|
|
|
|
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
|
|
|
|
assertEq(rln.members(idCommitment), true);
|
2023-03-30 12:00:54 +00:00
|
|
|
vm.expectRevert(DuplicateIdCommitment.selector);
|
2023-03-29 09:49:32 +00:00
|
|
|
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
|
|
|
|
}
|
|
|
|
|
|
|
|
function test__InvalidRegistration__InsufficientDeposit(
|
|
|
|
uint256 idCommitment
|
|
|
|
) public {
|
2023-03-30 12:00:54 +00:00
|
|
|
uint256 badDepositAmount = MEMBERSHIP_DEPOSIT - 1;
|
2023-03-29 09:49:32 +00:00
|
|
|
vm.expectRevert(
|
2023-03-30 12:00:54 +00:00
|
|
|
abi.encodeWithSelector(
|
|
|
|
InsufficientDeposit.selector,
|
|
|
|
MEMBERSHIP_DEPOSIT,
|
|
|
|
badDepositAmount
|
|
|
|
)
|
2023-03-29 09:49:32 +00:00
|
|
|
);
|
2023-03-30 12:00:54 +00:00
|
|
|
rln.register{value: badDepositAmount}(idCommitment);
|
2023-03-29 09:49:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function test__InvalidRegistration__FullSet(
|
|
|
|
uint256 idCommitmentSeed
|
|
|
|
) public {
|
|
|
|
vm.assume(idCommitmentSeed < 2 ** 255 - SET_SIZE);
|
|
|
|
RLN tempRln = new RLN(
|
|
|
|
MEMBERSHIP_DEPOSIT,
|
|
|
|
2,
|
|
|
|
address(rln.poseidonHasher())
|
|
|
|
);
|
|
|
|
uint256 setSize = tempRln.SET_SIZE();
|
|
|
|
for (uint256 i = 0; i < setSize; i++) {
|
|
|
|
tempRln.register{value: MEMBERSHIP_DEPOSIT}(idCommitmentSeed + i);
|
|
|
|
}
|
|
|
|
assertEq(tempRln.idCommitmentIndex(), 4);
|
2023-03-30 12:00:54 +00:00
|
|
|
vm.expectRevert(FullBatch.selector);
|
2023-03-29 09:49:32 +00:00
|
|
|
tempRln.register{value: MEMBERSHIP_DEPOSIT}(idCommitmentSeed + setSize);
|
|
|
|
}
|
2023-03-29 11:48:00 +00:00
|
|
|
|
|
|
|
function test__ValidBatchRegistration(
|
|
|
|
uint256[] calldata idCommitments
|
|
|
|
) public {
|
|
|
|
// assume that the array is unique, otherwise it triggers
|
|
|
|
// a revert that has already been tested
|
|
|
|
vm.assume(isUniqueArray(idCommitments) && idCommitments.length > 0);
|
|
|
|
uint256 idCommitmentlen = idCommitments.length;
|
|
|
|
rln.registerBatch{value: MEMBERSHIP_DEPOSIT * idCommitmentlen}(
|
|
|
|
idCommitments
|
|
|
|
);
|
|
|
|
for (uint256 i = 0; i < idCommitmentlen; i++) {
|
|
|
|
assertEq(rln.stakedAmounts(idCommitments[i]), MEMBERSHIP_DEPOSIT);
|
|
|
|
assertEq(rln.members(idCommitments[i]), true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function test__InvalidBatchRegistration__FullSet(
|
|
|
|
uint256 idCommitmentSeed
|
|
|
|
) public {
|
|
|
|
vm.assume(idCommitmentSeed < 2 ** 255 - SET_SIZE);
|
|
|
|
RLN tempRln = new RLN(MEMBERSHIP_DEPOSIT, 2, address(poseidon));
|
|
|
|
uint256 setSize = tempRln.SET_SIZE();
|
|
|
|
for (uint256 i = 0; i < setSize; i++) {
|
|
|
|
tempRln.register{value: MEMBERSHIP_DEPOSIT}(idCommitmentSeed + i);
|
|
|
|
}
|
|
|
|
assertEq(tempRln.idCommitmentIndex(), 4);
|
|
|
|
uint256[] memory idCommitments = new uint256[](1);
|
|
|
|
idCommitments[0] = idCommitmentSeed + setSize;
|
2023-03-30 12:00:54 +00:00
|
|
|
vm.expectRevert(FullBatch.selector);
|
2023-03-29 11:48:00 +00:00
|
|
|
tempRln.registerBatch{value: MEMBERSHIP_DEPOSIT}(idCommitments);
|
|
|
|
}
|
|
|
|
|
|
|
|
function test__InvalidBatchRegistration__EmptyBatch() public {
|
|
|
|
uint256[] memory idCommitments = new uint256[](0);
|
2023-03-30 12:00:54 +00:00
|
|
|
vm.expectRevert(EmptyBatch.selector);
|
2023-03-29 11:48:00 +00:00
|
|
|
rln.registerBatch{value: MEMBERSHIP_DEPOSIT}(idCommitments);
|
|
|
|
}
|
|
|
|
|
|
|
|
function test__InvalidBatchRegistration__InsufficientDeposit(
|
|
|
|
uint256[] calldata idCommitments
|
|
|
|
) public {
|
|
|
|
vm.assume(isUniqueArray(idCommitments) && idCommitments.length > 0);
|
|
|
|
uint256 idCommitmentlen = idCommitments.length;
|
2023-03-30 12:00:54 +00:00
|
|
|
uint256 goodDepositAmount = MEMBERSHIP_DEPOSIT * idCommitmentlen;
|
|
|
|
uint256 badDepositAmount = goodDepositAmount - 1;
|
|
|
|
|
2023-03-29 11:48:00 +00:00
|
|
|
vm.expectRevert(
|
2023-03-30 12:00:54 +00:00
|
|
|
abi.encodeWithSelector(
|
|
|
|
InsufficientDeposit.selector,
|
|
|
|
goodDepositAmount,
|
|
|
|
badDepositAmount
|
|
|
|
)
|
2023-03-29 11:48:00 +00:00
|
|
|
);
|
2023-03-30 12:00:54 +00:00
|
|
|
rln.registerBatch{value: badDepositAmount}(idCommitments);
|
2023-03-29 11:48:00 +00:00
|
|
|
}
|
|
|
|
|
2023-03-30 14:53:08 +00:00
|
|
|
function test__ValidSlash(uint256 idSecretHash, address payable to) public {
|
2023-03-29 11:48:00 +00:00
|
|
|
// avoid precompiles, etc
|
2023-03-29 12:21:48 +00:00
|
|
|
// TODO: wrap both of these in a single function
|
2023-03-29 11:48:00 +00:00
|
|
|
assumePayable(to);
|
2023-03-29 12:21:48 +00:00
|
|
|
assumeNoPrecompiles(to);
|
2023-03-29 11:59:33 +00:00
|
|
|
vm.assume(to != address(0));
|
2023-03-29 11:48:00 +00:00
|
|
|
uint256 idCommitment = poseidon.hash(idSecretHash);
|
|
|
|
|
|
|
|
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
|
|
|
|
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
|
|
|
|
|
|
|
|
uint256 balanceBefore = to.balance;
|
2023-03-30 14:53:08 +00:00
|
|
|
rln.slash(idSecretHash, to);
|
|
|
|
vm.prank(to);
|
|
|
|
rln.withdraw();
|
2023-03-29 11:48:00 +00:00
|
|
|
assertEq(rln.stakedAmounts(idCommitment), 0);
|
|
|
|
assertEq(rln.members(idCommitment), false);
|
|
|
|
assertEq(to.balance, balanceBefore + MEMBERSHIP_DEPOSIT);
|
|
|
|
}
|
|
|
|
|
2023-03-30 14:53:08 +00:00
|
|
|
function test__InvalidSlash__ToZeroAddress() public {
|
2023-03-29 11:48:00 +00:00
|
|
|
uint256 idSecretHash = 19014214495641488759237505126948346942972912379615652741039992445865937985820;
|
|
|
|
uint256 idCommitment = poseidon.hash(idSecretHash);
|
|
|
|
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
|
|
|
|
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
|
2023-03-30 12:00:54 +00:00
|
|
|
vm.expectRevert(
|
2023-03-30 14:53:08 +00:00
|
|
|
abi.encodeWithSelector(InvalidReceiverAddress.selector, address(0))
|
2023-03-30 12:00:54 +00:00
|
|
|
);
|
2023-03-30 14:53:08 +00:00
|
|
|
rln.slash(idSecretHash, payable(address(0)));
|
2023-03-29 11:48:00 +00:00
|
|
|
}
|
|
|
|
|
2023-03-30 14:53:08 +00:00
|
|
|
function test__InvalidSlash__ToRlnAddress() public {
|
2023-03-29 11:48:00 +00:00
|
|
|
uint256 idSecretHash = 19014214495641488759237505126948346942972912379615652741039992445865937985820;
|
|
|
|
uint256 idCommitment = poseidon.hash(idSecretHash);
|
|
|
|
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
|
|
|
|
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
|
2023-03-30 12:00:54 +00:00
|
|
|
vm.expectRevert(
|
|
|
|
abi.encodeWithSelector(
|
2023-03-30 14:53:08 +00:00
|
|
|
InvalidReceiverAddress.selector,
|
2023-03-30 12:00:54 +00:00
|
|
|
address(rln)
|
|
|
|
)
|
|
|
|
);
|
2023-03-30 14:53:08 +00:00
|
|
|
rln.slash(idSecretHash, payable(address(rln)));
|
2023-03-29 11:48:00 +00:00
|
|
|
}
|
|
|
|
|
2023-03-30 14:53:08 +00:00
|
|
|
function test__InvalidSlash__InvalidIdCommitment(
|
2023-03-30 12:00:54 +00:00
|
|
|
uint256 idSecretHash
|
2023-03-29 11:48:00 +00:00
|
|
|
) public {
|
2023-03-30 12:00:54 +00:00
|
|
|
uint256 idCommitment = poseidon.hash(idSecretHash);
|
|
|
|
vm.expectRevert(
|
|
|
|
abi.encodeWithSelector(MemberNotRegistered.selector, idCommitment)
|
|
|
|
);
|
2023-03-30 14:53:08 +00:00
|
|
|
rln.slash(idSecretHash, payable(address(this)));
|
2023-03-29 11:48:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// this shouldn't be possible, but just in case
|
2023-03-30 14:53:08 +00:00
|
|
|
function test__InvalidSlash__NoStake(
|
2023-03-29 11:48:00 +00:00
|
|
|
uint256 idSecretHash,
|
|
|
|
address payable to
|
|
|
|
) public {
|
|
|
|
// avoid precompiles, etc
|
|
|
|
assumePayable(to);
|
2023-03-29 12:21:48 +00:00
|
|
|
assumeNoPrecompiles(to);
|
2023-03-29 11:59:33 +00:00
|
|
|
vm.assume(to != address(0));
|
2023-03-29 11:48:00 +00:00
|
|
|
uint256 idCommitment = poseidon.hash(idSecretHash);
|
|
|
|
|
|
|
|
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
|
|
|
|
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
|
|
|
|
|
2023-03-30 14:53:08 +00:00
|
|
|
rln.slash(idSecretHash, to);
|
2023-03-29 11:48:00 +00:00
|
|
|
assertEq(rln.stakedAmounts(idCommitment), 0);
|
|
|
|
assertEq(rln.members(idCommitment), false);
|
|
|
|
|
|
|
|
// manually set members[idCommitment] to true using vm
|
|
|
|
stdstore
|
|
|
|
.target(address(rln))
|
|
|
|
.sig("members(uint256)")
|
|
|
|
.with_key(idCommitment)
|
|
|
|
.depth(0)
|
|
|
|
.checked_write(true);
|
|
|
|
|
2023-03-30 12:00:54 +00:00
|
|
|
vm.expectRevert(
|
|
|
|
abi.encodeWithSelector(MemberHasNoStake.selector, idCommitment)
|
|
|
|
);
|
2023-03-30 14:53:08 +00:00
|
|
|
rln.slash(idSecretHash, to);
|
2023-03-29 11:48:00 +00:00
|
|
|
}
|
|
|
|
|
2023-03-30 14:53:08 +00:00
|
|
|
function test__ValidBatchSlash(
|
2023-03-29 11:48:00 +00:00
|
|
|
uint256[] calldata idSecretHashes,
|
|
|
|
address payable to
|
|
|
|
) public {
|
|
|
|
// avoid precompiles, etc
|
|
|
|
assumePayable(to);
|
2023-03-29 12:21:48 +00:00
|
|
|
assumeNoPrecompiles(to);
|
2023-03-29 11:59:33 +00:00
|
|
|
vm.assume(isUniqueArray(idSecretHashes) && idSecretHashes.length > 0);
|
|
|
|
vm.assume(to != address(0));
|
2023-03-29 11:48:00 +00:00
|
|
|
uint256 idCommitmentlen = idSecretHashes.length;
|
|
|
|
uint256[] memory idCommitments = new uint256[](idCommitmentlen);
|
|
|
|
for (uint256 i = 0; i < idCommitmentlen; i++) {
|
|
|
|
idCommitments[i] = poseidon.hash(idSecretHashes[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
rln.registerBatch{value: MEMBERSHIP_DEPOSIT * idCommitmentlen}(
|
|
|
|
idCommitments
|
|
|
|
);
|
|
|
|
for (uint256 i = 0; i < idCommitmentlen; i++) {
|
|
|
|
assertEq(rln.stakedAmounts(idCommitments[i]), MEMBERSHIP_DEPOSIT);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint256 balanceBefore = to.balance;
|
2023-03-30 14:53:08 +00:00
|
|
|
rln.slashBatch(
|
2023-03-29 11:48:00 +00:00
|
|
|
idSecretHashes,
|
|
|
|
repeatElementIntoArray(idSecretHashes.length, to)
|
|
|
|
);
|
|
|
|
for (uint256 i = 0; i < idCommitmentlen; i++) {
|
|
|
|
assertEq(rln.stakedAmounts(idCommitments[i]), 0);
|
|
|
|
assertEq(rln.members(idCommitments[i]), false);
|
|
|
|
}
|
2023-03-30 14:53:08 +00:00
|
|
|
vm.prank(to);
|
|
|
|
rln.withdraw();
|
2023-03-29 11:48:00 +00:00
|
|
|
assertEq(
|
|
|
|
to.balance,
|
|
|
|
balanceBefore + MEMBERSHIP_DEPOSIT * idCommitmentlen
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-03-30 14:53:08 +00:00
|
|
|
function test__InvalidBatchSlash__EmptyBatch() public {
|
2023-03-29 11:48:00 +00:00
|
|
|
uint256[] memory idSecretHashes = new uint256[](0);
|
|
|
|
address payable[] memory to = new address payable[](0);
|
2023-03-30 12:00:54 +00:00
|
|
|
vm.expectRevert(EmptyBatch.selector);
|
2023-03-30 14:53:08 +00:00
|
|
|
rln.slashBatch(idSecretHashes, to);
|
2023-03-29 11:48:00 +00:00
|
|
|
}
|
|
|
|
|
2023-03-30 14:53:08 +00:00
|
|
|
function test__InvalidBatchSlash__MismatchInputSize(
|
2023-03-29 11:48:00 +00:00
|
|
|
uint256[] calldata idSecretHashes,
|
|
|
|
address payable to
|
|
|
|
) public {
|
2023-03-29 11:59:33 +00:00
|
|
|
assumePayable(to);
|
2023-03-29 12:21:48 +00:00
|
|
|
assumeNoPrecompiles(to);
|
2023-03-29 11:48:00 +00:00
|
|
|
vm.assume(isUniqueArray(idSecretHashes) && idSecretHashes.length > 0);
|
|
|
|
vm.assume(to != address(0));
|
|
|
|
|
2023-03-30 12:00:54 +00:00
|
|
|
uint256 numberOfReceivers = idSecretHashes.length + 1;
|
2023-03-29 11:48:00 +00:00
|
|
|
vm.expectRevert(
|
2023-03-30 12:00:54 +00:00
|
|
|
abi.encodeWithSelector(
|
|
|
|
MismatchedBatchSize.selector,
|
|
|
|
idSecretHashes.length,
|
|
|
|
numberOfReceivers
|
|
|
|
)
|
2023-03-29 11:48:00 +00:00
|
|
|
);
|
2023-03-30 14:53:08 +00:00
|
|
|
rln.slashBatch(
|
2023-03-29 11:48:00 +00:00
|
|
|
idSecretHashes,
|
2023-03-30 12:00:54 +00:00
|
|
|
repeatElementIntoArray(numberOfReceivers, to)
|
2023-03-29 11:48:00 +00:00
|
|
|
);
|
|
|
|
}
|
2023-03-30 14:53:08 +00:00
|
|
|
|
|
|
|
function test__InvalidWithdraw__InsufficientWithdrawalBalance() public {
|
|
|
|
vm.expectRevert(InsufficientWithdrawalBalance.selector);
|
|
|
|
rln.withdraw();
|
|
|
|
}
|
|
|
|
|
|
|
|
function test__InvalidWithdraw__InsufficientContractBalance() public {
|
|
|
|
uint256 idSecretHash = 19014214495641488759237505126948346942972912379615652741039992445865937985820;
|
|
|
|
uint256 idCommitment = poseidon.hash(idSecretHash);
|
|
|
|
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
|
|
|
|
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
|
|
|
|
rln.slash(idSecretHash, payable(address(this)));
|
|
|
|
assertEq(rln.stakedAmounts(idCommitment), 0);
|
|
|
|
assertEq(rln.members(idCommitment), false);
|
|
|
|
|
|
|
|
vm.deal(address(rln), 0);
|
|
|
|
vm.expectRevert(InsufficientContractBalance.selector);
|
|
|
|
rln.withdraw();
|
|
|
|
}
|
|
|
|
|
|
|
|
function test__ValidWithdraw(address payable to) public {
|
|
|
|
assumePayable(to);
|
|
|
|
assumeNoPrecompiles(to);
|
|
|
|
|
|
|
|
uint256 idSecretHash = 19014214495641488759237505126948346942972912379615652741039992445865937985820;
|
|
|
|
uint256 idCommitment = poseidon.hash(idSecretHash);
|
|
|
|
|
|
|
|
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
|
|
|
|
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
|
|
|
|
rln.slash(idSecretHash, to);
|
|
|
|
assertEq(rln.stakedAmounts(idCommitment), 0);
|
|
|
|
assertEq(rln.members(idCommitment), false);
|
|
|
|
|
|
|
|
vm.prank(to);
|
|
|
|
rln.withdraw();
|
|
|
|
assertEq(rln.withdrawalBalance(to), 0);
|
|
|
|
}
|
2023-03-29 09:49:32 +00:00
|
|
|
}
|