fix: remove .transfer from loops

This commit is contained in:
rymnc 2023-03-30 20:23:08 +05:30
parent 2c4ddb0312
commit 99c6625b40
No known key found for this signature in database
GPG Key ID: C740033EE3F41EBD
3 changed files with 135 additions and 45 deletions

View File

@ -23,8 +23,8 @@ error DuplicateIdCommitment();
/// @param givenReceiversLen The length of the receivers array /// @param givenReceiversLen The length of the receivers array
error MismatchedBatchSize(uint256 givenSecretsLen, uint256 givenReceiversLen); error MismatchedBatchSize(uint256 givenSecretsLen, uint256 givenReceiversLen);
/// Invalid withdrawal address, when the receiver is the contract itself or 0x0 /// Invalid receiver address, when the receiver is the contract itself or 0x0
error InvalidWithdrawalAddress(address to); error InvalidReceiverAddress(address to);
/// Member is not registered /// Member is not registered
error MemberNotRegistered(uint256 idCommitment); error MemberNotRegistered(uint256 idCommitment);
@ -32,6 +32,12 @@ error MemberNotRegistered(uint256 idCommitment);
/// Member has no stake /// Member has no stake
error MemberHasNoStake(uint256 idCommitment); error MemberHasNoStake(uint256 idCommitment);
/// User has insufficient balance to withdraw
error InsufficientWithdrawalBalance();
/// Contract has insufficient balance to return
error InsufficientContractBalance();
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;
@ -51,8 +57,11 @@ contract RLN {
/// @notice The membership status of each member /// @notice The membership status of each member
mapping(uint256 => bool) public members; mapping(uint256 => bool) public members;
/// @notice The balance of each user that can be withdrawn
mapping(address => uint256) public withdrawalBalance;
/// @notice The Poseidon hasher contract /// @notice The Poseidon hasher contract
IPoseidonHasher public poseidonHasher; IPoseidonHasher public immutable poseidonHasher;
/// Emitted when a new member is added to the set /// Emitted when a new member is added to the set
/// @param idCommitment The idCommitment of the member /// @param idCommitment The idCommitment of the member
@ -91,8 +100,9 @@ contract RLN {
uint256 requiredDeposit = MEMBERSHIP_DEPOSIT * idCommitmentlen; uint256 requiredDeposit = MEMBERSHIP_DEPOSIT * idCommitmentlen;
if (msg.value != requiredDeposit) if (msg.value != requiredDeposit)
revert InsufficientDeposit(requiredDeposit, msg.value); revert InsufficientDeposit(requiredDeposit, msg.value);
for (uint256 i = 0; i < idCommitmentlen; i++) { for (uint256 i = 0; i < idCommitmentlen; i++) {
_register(idCommitments[i], msg.value / idCommitmentlen); _register(idCommitments[i], MEMBERSHIP_DEPOSIT);
} }
} }
@ -113,7 +123,7 @@ contract RLN {
/// Allows a user to slash a batch of members /// Allows a user to slash a batch of members
/// @param secrets array of idSecretHashes /// @param secrets array of idSecretHashes
/// @param receivers array of addresses to receive the funds /// @param receivers array of addresses to receive the funds
function withdrawBatch( function slashBatch(
uint256[] calldata secrets, uint256[] calldata secrets,
address payable[] calldata receivers address payable[] calldata receivers
) external { ) external {
@ -122,23 +132,23 @@ contract RLN {
if (batchSize != receivers.length) if (batchSize != receivers.length)
revert MismatchedBatchSize(secrets.length, receivers.length); revert MismatchedBatchSize(secrets.length, receivers.length);
for (uint256 i = 0; i < batchSize; i++) { for (uint256 i = 0; i < batchSize; i++) {
_withdraw(secrets[i], receivers[i]); _slash(secrets[i], receivers[i]);
} }
} }
/// Allows a user to slash a member /// Allows a user to slash a member
/// @param secret The idSecretHash of the member /// @param secret The idSecretHash of the member
function withdraw(uint256 secret, address payable receiver) external { function slash(uint256 secret, address payable receiver) external {
_withdraw(secret, receiver); _slash(secret, receiver);
} }
/// Slashes a member by removing them from the set, and transferring their /// Slashes a member by removing them from the set, and transferring their
/// stake to the receiver /// stake to the receiver
/// @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 _slash(uint256 secret, address payable receiver) internal {
if (receiver == address(this) || receiver == address(0)) if (receiver == address(this) || receiver == address(0))
revert InvalidWithdrawalAddress(receiver); revert InvalidReceiverAddress(receiver);
// derive idCommitment // derive idCommitment
uint256 idCommitment = hash(secret); uint256 idCommitment = hash(secret);
@ -154,11 +164,24 @@ contract RLN {
stakedAmounts[idCommitment] = 0; stakedAmounts[idCommitment] = 0;
// refund deposit // refund deposit
receiver.transfer(amountToTransfer); withdrawalBalance[receiver] += amountToTransfer;
emit MemberWithdrawn(idCommitment); emit MemberWithdrawn(idCommitment);
} }
/// Allows a user to withdraw funds allocated to them upon slashing a member
function withdraw() external {
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 /// 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 /// 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 /// @param input The value to hash

View File

@ -890,13 +890,13 @@ Batch size mismatch, when the length of secrets and receivers are not equal
| givenSecretsLen | uint256 | The length of the secrets array | | givenSecretsLen | uint256 | The length of the secrets array |
| givenReceiversLen | uint256 | The length of the receivers array | | givenReceiversLen | uint256 | The length of the receivers array |
## InvalidWithdrawalAddress ## InvalidReceiverAddress
```solidity ```solidity
error InvalidWithdrawalAddress(address to) error InvalidReceiverAddress(address to)
``` ```
Invalid withdrawal address, when the receiver is the contract itself or 0x0 Invalid receiver address, when the receiver is the contract itself or 0x0
## MemberNotRegistered ## MemberNotRegistered
@ -914,6 +914,22 @@ error MemberHasNoStake(uint256 idCommitment)
Member has no stake Member has no stake
## InsufficientWithdrawalBalance
```solidity
error InsufficientWithdrawalBalance()
```
User has insufficient balance to withdraw
## InsufficientContractBalance
```solidity
error InsufficientContractBalance()
```
Contract has insufficient balance to return
## RLN ## RLN
### MEMBERSHIP_DEPOSIT ### MEMBERSHIP_DEPOSIT
@ -964,6 +980,14 @@ mapping(uint256 => bool) members
The membership status of each member The membership status of each member
### withdrawalBalance
```solidity
mapping(address => uint256) withdrawalBalance
```
The balance of each user that can be withdrawn
### poseidonHasher ### poseidonHasher
```solidity ```solidity
@ -1050,10 +1074,10 @@ Registers a member
| idCommitment | uint256 | The idCommitment of the member | | idCommitment | uint256 | The idCommitment of the member |
| stake | uint256 | The amount of eth staked by the member | | stake | uint256 | The amount of eth staked by the member |
### withdrawBatch ### slashBatch
```solidity ```solidity
function withdrawBatch(uint256[] secrets, address payable[] receivers) external function slashBatch(uint256[] secrets, address payable[] receivers) external
``` ```
Allows a user to slash a batch of members Allows a user to slash a batch of members
@ -1065,10 +1089,10 @@ Allows a user to slash a batch of members
| secrets | uint256[] | array of idSecretHashes | | secrets | uint256[] | array of idSecretHashes |
| receivers | address payable[] | array of addresses to receive the funds | | receivers | address payable[] | array of addresses to receive the funds |
### withdraw ### slash
```solidity ```solidity
function withdraw(uint256 secret, address payable receiver) external function slash(uint256 secret, address payable receiver) external
``` ```
Allows a user to slash a member Allows a user to slash a member
@ -1080,10 +1104,10 @@ Allows a user to slash a member
| secret | uint256 | The idSecretHash of the member | | secret | uint256 | The idSecretHash of the member |
| receiver | address payable | | | receiver | address payable | |
### \_withdraw ### \_slash
```solidity ```solidity
function _withdraw(uint256 secret, address payable receiver) internal function _slash(uint256 secret, address payable receiver) internal
``` ```
Slashes a member by removing them from the set, and transferring their Slashes a member by removing them from the set, and transferring their
@ -1096,6 +1120,14 @@ stake to the receiver
| secret | uint256 | The idSecretHash of the member | | secret | uint256 | The idSecretHash of the member |
| receiver | address payable | The address to receive the funds | | receiver | address payable | The address to receive the funds |
### withdraw
```solidity
function withdraw() external
```
Allows a user to withdraw funds allocated to them upon slashing a member
### hash ### hash
```solidity ```solidity

View File

@ -167,10 +167,7 @@ contract RLNTest is Test {
rln.registerBatch{value: badDepositAmount}(idCommitments); rln.registerBatch{value: badDepositAmount}(idCommitments);
} }
function test__ValidWithdraw( function test__ValidSlash(uint256 idSecretHash, address payable to) public {
uint256 idSecretHash,
address payable to
) public {
// avoid precompiles, etc // avoid precompiles, etc
// TODO: wrap both of these in a single function // TODO: wrap both of these in a single function
assumePayable(to); assumePayable(to);
@ -182,52 +179,51 @@ contract RLNTest is Test {
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT); assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
uint256 balanceBefore = to.balance; uint256 balanceBefore = to.balance;
rln.withdraw(idSecretHash, to); rln.slash(idSecretHash, to);
vm.prank(to);
rln.withdraw();
assertEq(rln.stakedAmounts(idCommitment), 0); assertEq(rln.stakedAmounts(idCommitment), 0);
assertEq(rln.members(idCommitment), false); assertEq(rln.members(idCommitment), false);
assertEq(to.balance, balanceBefore + MEMBERSHIP_DEPOSIT); assertEq(to.balance, balanceBefore + MEMBERSHIP_DEPOSIT);
} }
function test__InvalidWithdraw__ToZeroAddress() public { function test__InvalidSlash__ToZeroAddress() public {
uint256 idSecretHash = 19014214495641488759237505126948346942972912379615652741039992445865937985820; uint256 idSecretHash = 19014214495641488759237505126948346942972912379615652741039992445865937985820;
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( vm.expectRevert(
abi.encodeWithSelector( abi.encodeWithSelector(InvalidReceiverAddress.selector, address(0))
InvalidWithdrawalAddress.selector,
address(0)
)
); );
rln.withdraw(idSecretHash, payable(address(0))); rln.slash(idSecretHash, payable(address(0)));
} }
function test__InvalidWithdraw__ToRlnAddress() public { function test__InvalidSlash__ToRlnAddress() public {
uint256 idSecretHash = 19014214495641488759237505126948346942972912379615652741039992445865937985820; uint256 idSecretHash = 19014214495641488759237505126948346942972912379615652741039992445865937985820;
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( vm.expectRevert(
abi.encodeWithSelector( abi.encodeWithSelector(
InvalidWithdrawalAddress.selector, InvalidReceiverAddress.selector,
address(rln) address(rln)
) )
); );
rln.withdraw(idSecretHash, payable(address(rln))); rln.slash(idSecretHash, payable(address(rln)));
} }
function test__InvalidWithdraw__InvalidIdCommitment( function test__InvalidSlash__InvalidIdCommitment(
uint256 idSecretHash uint256 idSecretHash
) public { ) public {
uint256 idCommitment = poseidon.hash(idSecretHash); uint256 idCommitment = poseidon.hash(idSecretHash);
vm.expectRevert( vm.expectRevert(
abi.encodeWithSelector(MemberNotRegistered.selector, idCommitment) abi.encodeWithSelector(MemberNotRegistered.selector, idCommitment)
); );
rln.withdraw(idSecretHash, payable(address(this))); rln.slash(idSecretHash, payable(address(this)));
} }
// this shouldn't be possible, but just in case // this shouldn't be possible, but just in case
function test__InvalidWithdraw__NoStake( function test__InvalidSlash__NoStake(
uint256 idSecretHash, uint256 idSecretHash,
address payable to address payable to
) public { ) public {
@ -240,7 +236,7 @@ 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);
rln.withdraw(idSecretHash, to); rln.slash(idSecretHash, to);
assertEq(rln.stakedAmounts(idCommitment), 0); assertEq(rln.stakedAmounts(idCommitment), 0);
assertEq(rln.members(idCommitment), false); assertEq(rln.members(idCommitment), false);
@ -255,10 +251,10 @@ contract RLNTest is Test {
vm.expectRevert( vm.expectRevert(
abi.encodeWithSelector(MemberHasNoStake.selector, idCommitment) abi.encodeWithSelector(MemberHasNoStake.selector, idCommitment)
); );
rln.withdraw(idSecretHash, to); rln.slash(idSecretHash, to);
} }
function test__ValidBatchWithdraw( function test__ValidBatchSlash(
uint256[] calldata idSecretHashes, uint256[] calldata idSecretHashes,
address payable to address payable to
) public { ) public {
@ -281,7 +277,7 @@ contract RLNTest is Test {
} }
uint256 balanceBefore = to.balance; uint256 balanceBefore = to.balance;
rln.withdrawBatch( rln.slashBatch(
idSecretHashes, idSecretHashes,
repeatElementIntoArray(idSecretHashes.length, to) repeatElementIntoArray(idSecretHashes.length, to)
); );
@ -289,20 +285,22 @@ contract RLNTest is Test {
assertEq(rln.stakedAmounts(idCommitments[i]), 0); assertEq(rln.stakedAmounts(idCommitments[i]), 0);
assertEq(rln.members(idCommitments[i]), false); assertEq(rln.members(idCommitments[i]), false);
} }
vm.prank(to);
rln.withdraw();
assertEq( assertEq(
to.balance, to.balance,
balanceBefore + MEMBERSHIP_DEPOSIT * idCommitmentlen balanceBefore + MEMBERSHIP_DEPOSIT * idCommitmentlen
); );
} }
function test__InvalidBatchWithdraw__EmptyBatch() public { function test__InvalidBatchSlash__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(EmptyBatch.selector); vm.expectRevert(EmptyBatch.selector);
rln.withdrawBatch(idSecretHashes, to); rln.slashBatch(idSecretHashes, to);
} }
function test__InvalidBatchWithdraw__MismatchInputSize( function test__InvalidBatchSlash__MismatchInputSize(
uint256[] calldata idSecretHashes, uint256[] calldata idSecretHashes,
address payable to address payable to
) public { ) public {
@ -319,9 +317,46 @@ contract RLNTest is Test {
numberOfReceivers numberOfReceivers
) )
); );
rln.withdrawBatch( rln.slashBatch(
idSecretHashes, idSecretHashes,
repeatElementIntoArray(numberOfReceivers, to) repeatElementIntoArray(numberOfReceivers, to)
); );
} }
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);
}
} }