Merge pull request #19 from vacp2p/custom-errors

feat: custom errors
This commit is contained in:
Aaryamann Challani 2023-03-30 18:24:42 +05:30 committed by GitHub
commit 75ba356d01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 173 additions and 62 deletions

View File

@ -4,6 +4,34 @@ pragma solidity 0.8.15;
import {IPoseidonHasher} from "./PoseidonHasher.sol"; import {IPoseidonHasher} from "./PoseidonHasher.sol";
/// 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);
contract RLN { contract RLN {
/// @notice The deposit amount required to register as a member /// @notice The deposit amount required to register as a member
uint256 public immutable MEMBERSHIP_DEPOSIT; uint256 public immutable MEMBERSHIP_DEPOSIT;
@ -49,10 +77,8 @@ contract RLN {
/// Allows a user to register as a member /// Allows a user to register as a member
/// @param idCommitment The idCommitment of the member /// @param idCommitment The idCommitment of the member
function register(uint256 idCommitment) external payable { function register(uint256 idCommitment) external payable {
require( if (msg.value != MEMBERSHIP_DEPOSIT)
msg.value == MEMBERSHIP_DEPOSIT, revert InsufficientDeposit(MEMBERSHIP_DEPOSIT, msg.value);
"RLN, register: membership deposit is not satisfied"
);
_register(idCommitment, msg.value); _register(idCommitment, msg.value);
} }
@ -60,15 +86,11 @@ contract RLN {
/// @param idCommitments array of idCommitments /// @param idCommitments array of idCommitments
function registerBatch(uint256[] calldata idCommitments) external payable { function registerBatch(uint256[] calldata idCommitments) external payable {
uint256 idCommitmentlen = idCommitments.length; uint256 idCommitmentlen = idCommitments.length;
require(idCommitmentlen > 0, "RLN, registerBatch: batch size zero"); if (idCommitmentlen == 0) revert EmptyBatch();
require( if (idCommitmentIndex + idCommitmentlen >= SET_SIZE) revert FullBatch();
idCommitmentIndex + idCommitmentlen <= SET_SIZE, uint256 requiredDeposit = MEMBERSHIP_DEPOSIT * idCommitmentlen;
"RLN, registerBatch: set is full" if (msg.value != requiredDeposit)
); revert InsufficientDeposit(requiredDeposit, msg.value);
require(
msg.value == MEMBERSHIP_DEPOSIT * idCommitmentlen,
"RLN, registerBatch: membership deposit is not satisfied"
);
for (uint256 i = 0; i < idCommitmentlen; i++) { for (uint256 i = 0; i < idCommitmentlen; i++) {
_register(idCommitments[i], msg.value / idCommitmentlen); _register(idCommitments[i], msg.value / idCommitmentlen);
} }
@ -78,11 +100,8 @@ contract RLN {
/// @param idCommitment The idCommitment of the member /// @param idCommitment The idCommitment of the member
/// @param stake The amount of eth staked by the member /// @param stake The amount of eth staked by the member
function _register(uint256 idCommitment, uint256 stake) internal { function _register(uint256 idCommitment, uint256 stake) internal {
require( if (members[idCommitment]) revert DuplicateIdCommitment();
!members[idCommitment], if (idCommitmentIndex >= SET_SIZE) revert FullBatch();
"RLN, _register: member already registered"
);
require(idCommitmentIndex < SET_SIZE, "RLN, register: set is full");
members[idCommitment] = true; members[idCommitment] = true;
stakedAmounts[idCommitment] = stake; stakedAmounts[idCommitment] = stake;
@ -99,11 +118,9 @@ contract RLN {
address payable[] calldata receivers address payable[] calldata receivers
) external { ) external {
uint256 batchSize = secrets.length; uint256 batchSize = secrets.length;
require(batchSize != 0, "RLN, withdrawBatch: batch size zero"); if (batchSize == 0) revert EmptyBatch();
require( if (batchSize != receivers.length)
batchSize == receivers.length, revert MismatchedBatchSize(secrets.length, receivers.length);
"RLN, withdrawBatch: batch size mismatch receivers"
);
for (uint256 i = 0; i < batchSize; i++) { for (uint256 i = 0; i < batchSize; i++) {
_withdraw(secrets[i], receivers[i]); _withdraw(secrets[i], receivers[i]);
} }
@ -120,26 +137,15 @@ contract RLN {
/// @param secret The idSecretHash of the member /// @param secret The idSecretHash of the member
/// @param receiver The address to receive the funds /// @param receiver The address to receive the funds
function _withdraw(uint256 secret, address payable receiver) internal { function _withdraw(uint256 secret, address payable receiver) internal {
require( if (receiver == address(this) || receiver == address(0))
receiver != address(0), revert InvalidWithdrawalAddress(receiver);
"RLN, _withdraw: empty receiver address"
);
require(
receiver != address(this),
"RLN, _withdraw: cannot withdraw to RLN"
);
// derive idCommitment // derive idCommitment
uint256 idCommitment = hash(secret); uint256 idCommitment = hash(secret);
// check if member is registered // check if member is registered
require(members[idCommitment], "RLN, _withdraw: member not registered"); if (!members[idCommitment]) revert MemberNotRegistered(idCommitment);
if (stakedAmounts[idCommitment] == 0)
// check if member has stake revert MemberHasNoStake(idCommitment);
require(
stakedAmounts[idCommitment] != 0,
"RLN, _withdraw: member has no stake"
);
uint256 amountToTransfer = stakedAmounts[idCommitment]; uint256 amountToTransfer = stakedAmounts[idCommitment];

View File

@ -836,6 +836,84 @@ Hashes the input using the Poseidon hash function, n = 2, second input is the co
function _hash(uint256 input) internal pure returns (uint256 result) function _hash(uint256 input) internal pure returns (uint256 result)
``` ```
## InsufficientDeposit
```solidity
error InsufficientDeposit(uint256 required, uint256 provided)
```
Invalid deposit amount
### Parameters
| Name | Type | Description |
| -------- | ------- | --------------------------- |
| required | uint256 | The required deposit amount |
| provided | uint256 | The provided deposit amount |
## EmptyBatch
```solidity
error EmptyBatch()
```
Provided Batch is empty
## FullBatch
```solidity
error FullBatch()
```
Batch is full, during batch operations
## DuplicateIdCommitment
```solidity
error DuplicateIdCommitment()
```
Member is already registered
## MismatchedBatchSize
```solidity
error MismatchedBatchSize(uint256 givenSecretsLen, uint256 givenReceiversLen)
```
Batch size mismatch, when the length of secrets and receivers are not equal
### Parameters
| Name | Type | Description |
| ----------------- | ------- | --------------------------------- |
| givenSecretsLen | uint256 | The length of the secrets array |
| givenReceiversLen | uint256 | The length of the receivers array |
## InvalidWithdrawalAddress
```solidity
error InvalidWithdrawalAddress(address to)
```
Invalid withdrawal address, when the receiver is the contract itself or 0x0
## MemberNotRegistered
```solidity
error MemberNotRegistered(uint256 idCommitment)
```
Member is not registered
## MemberHasNoStake
```solidity
error MemberHasNoStake(uint256 idCommitment)
```
Member has no stake
## RLN ## RLN
### MEMBERSHIP_DEPOSIT ### MEMBERSHIP_DEPOSIT

View File

@ -75,18 +75,22 @@ contract RLNTest is Test {
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment); rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT); assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
assertEq(rln.members(idCommitment), true); assertEq(rln.members(idCommitment), true);
// TODO: use custom errors instead of revert strings vm.expectRevert(DuplicateIdCommitment.selector);
vm.expectRevert(bytes("RLN, _register: member already registered"));
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment); rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
} }
function test__InvalidRegistration__InsufficientDeposit( function test__InvalidRegistration__InsufficientDeposit(
uint256 idCommitment uint256 idCommitment
) public { ) public {
uint256 badDepositAmount = MEMBERSHIP_DEPOSIT - 1;
vm.expectRevert( vm.expectRevert(
bytes("RLN, register: membership deposit is not satisfied") abi.encodeWithSelector(
InsufficientDeposit.selector,
MEMBERSHIP_DEPOSIT,
badDepositAmount
)
); );
rln.register{value: MEMBERSHIP_DEPOSIT - 1}(idCommitment); rln.register{value: badDepositAmount}(idCommitment);
} }
function test__InvalidRegistration__FullSet( function test__InvalidRegistration__FullSet(
@ -103,7 +107,7 @@ contract RLNTest is Test {
tempRln.register{value: MEMBERSHIP_DEPOSIT}(idCommitmentSeed + i); tempRln.register{value: MEMBERSHIP_DEPOSIT}(idCommitmentSeed + i);
} }
assertEq(tempRln.idCommitmentIndex(), 4); assertEq(tempRln.idCommitmentIndex(), 4);
vm.expectRevert(bytes("RLN, register: set is full")); vm.expectRevert(FullBatch.selector);
tempRln.register{value: MEMBERSHIP_DEPOSIT}(idCommitmentSeed + setSize); tempRln.register{value: MEMBERSHIP_DEPOSIT}(idCommitmentSeed + setSize);
} }
@ -135,13 +139,13 @@ contract RLNTest is Test {
assertEq(tempRln.idCommitmentIndex(), 4); assertEq(tempRln.idCommitmentIndex(), 4);
uint256[] memory idCommitments = new uint256[](1); uint256[] memory idCommitments = new uint256[](1);
idCommitments[0] = idCommitmentSeed + setSize; idCommitments[0] = idCommitmentSeed + setSize;
vm.expectRevert(bytes("RLN, registerBatch: set is full")); vm.expectRevert(FullBatch.selector);
tempRln.registerBatch{value: MEMBERSHIP_DEPOSIT}(idCommitments); tempRln.registerBatch{value: MEMBERSHIP_DEPOSIT}(idCommitments);
} }
function test__InvalidBatchRegistration__EmptyBatch() public { function test__InvalidBatchRegistration__EmptyBatch() public {
uint256[] memory idCommitments = new uint256[](0); uint256[] memory idCommitments = new uint256[](0);
vm.expectRevert(bytes("RLN, registerBatch: batch size zero")); vm.expectRevert(EmptyBatch.selector);
rln.registerBatch{value: MEMBERSHIP_DEPOSIT}(idCommitments); rln.registerBatch{value: MEMBERSHIP_DEPOSIT}(idCommitments);
} }
@ -150,12 +154,17 @@ contract RLNTest is Test {
) public { ) public {
vm.assume(isUniqueArray(idCommitments) && idCommitments.length > 0); vm.assume(isUniqueArray(idCommitments) && idCommitments.length > 0);
uint256 idCommitmentlen = idCommitments.length; uint256 idCommitmentlen = idCommitments.length;
uint256 goodDepositAmount = MEMBERSHIP_DEPOSIT * idCommitmentlen;
uint256 badDepositAmount = goodDepositAmount - 1;
vm.expectRevert( vm.expectRevert(
bytes("RLN, registerBatch: membership deposit is not satisfied") abi.encodeWithSelector(
); InsufficientDeposit.selector,
rln.registerBatch{value: MEMBERSHIP_DEPOSIT * idCommitmentlen - 1}( goodDepositAmount,
idCommitments badDepositAmount
)
); );
rln.registerBatch{value: badDepositAmount}(idCommitments);
} }
function test__ValidWithdraw( function test__ValidWithdraw(
@ -184,7 +193,12 @@ contract RLNTest is Test {
uint256 idCommitment = poseidon.hash(idSecretHash); uint256 idCommitment = poseidon.hash(idSecretHash);
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment); rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT); assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
vm.expectRevert(bytes("RLN, _withdraw: empty receiver address")); vm.expectRevert(
abi.encodeWithSelector(
InvalidWithdrawalAddress.selector,
address(0)
)
);
rln.withdraw(idSecretHash, payable(address(0))); rln.withdraw(idSecretHash, payable(address(0)));
} }
@ -193,15 +207,23 @@ contract RLNTest is Test {
uint256 idCommitment = poseidon.hash(idSecretHash); uint256 idCommitment = poseidon.hash(idSecretHash);
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment); rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT); assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
vm.expectRevert(bytes("RLN, _withdraw: cannot withdraw to RLN")); vm.expectRevert(
abi.encodeWithSelector(
InvalidWithdrawalAddress.selector,
address(rln)
)
);
rln.withdraw(idSecretHash, payable(address(rln))); rln.withdraw(idSecretHash, payable(address(rln)));
} }
function test__InvalidWithdraw__InvalidIdCommitment( function test__InvalidWithdraw__InvalidIdCommitment(
uint256 idCommitment uint256 idSecretHash
) public { ) public {
vm.expectRevert(bytes("RLN, _withdraw: member not registered")); uint256 idCommitment = poseidon.hash(idSecretHash);
rln.withdraw(idCommitment, payable(address(this))); vm.expectRevert(
abi.encodeWithSelector(MemberNotRegistered.selector, idCommitment)
);
rln.withdraw(idSecretHash, payable(address(this)));
} }
// this shouldn't be possible, but just in case // this shouldn't be possible, but just in case
@ -230,7 +252,9 @@ contract RLNTest is Test {
.depth(0) .depth(0)
.checked_write(true); .checked_write(true);
vm.expectRevert(bytes("RLN, _withdraw: member has no stake")); vm.expectRevert(
abi.encodeWithSelector(MemberHasNoStake.selector, idCommitment)
);
rln.withdraw(idSecretHash, to); rln.withdraw(idSecretHash, to);
} }
@ -274,7 +298,7 @@ contract RLNTest is Test {
function test__InvalidBatchWithdraw__EmptyBatch() public { function test__InvalidBatchWithdraw__EmptyBatch() public {
uint256[] memory idSecretHashes = new uint256[](0); uint256[] memory idSecretHashes = new uint256[](0);
address payable[] memory to = new address payable[](0); address payable[] memory to = new address payable[](0);
vm.expectRevert(bytes("RLN, withdrawBatch: batch size zero")); vm.expectRevert(EmptyBatch.selector);
rln.withdrawBatch(idSecretHashes, to); rln.withdrawBatch(idSecretHashes, to);
} }
@ -287,12 +311,17 @@ contract RLNTest is Test {
vm.assume(isUniqueArray(idSecretHashes) && idSecretHashes.length > 0); vm.assume(isUniqueArray(idSecretHashes) && idSecretHashes.length > 0);
vm.assume(to != address(0)); vm.assume(to != address(0));
uint256 numberOfReceivers = idSecretHashes.length + 1;
vm.expectRevert( vm.expectRevert(
bytes("RLN, withdrawBatch: batch size mismatch receivers") abi.encodeWithSelector(
MismatchedBatchSize.selector,
idSecretHashes.length,
numberOfReceivers
)
); );
rln.withdrawBatch( rln.withdrawBatch(
idSecretHashes, idSecretHashes,
repeatElementIntoArray(idSecretHashes.length + 1, to) repeatElementIntoArray(numberOfReceivers, to)
); );
} }
} }

View File

@ -82,8 +82,6 @@ describe("RLN", () => {
value: price, value: price,
}); });
await expect(registerTx2).to.be.revertedWith( await expect(registerTx2).to.be.revertedWith("DuplicateIdCommitment()");
"RLN, _register: member already registered"
);
}); });
}); });