-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(contracts): replace DirectTransfer with MultiPayment (#774)
* Create multipayment * Add tests * Add errors * Test errors * Comments * Add abi * Deploy contract * style: resolve style guide violations --------- Co-authored-by: sebastijankuzner <[email protected]>
- Loading branch information
1 parent
e05a907
commit 1918e40
Showing
7 changed files
with
391 additions
and
114 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// SPDX-License-Identifier: GNU GENERAL PUBLIC LICENSE | ||
pragma solidity ^0.8.27; | ||
|
||
error RecipientsAndAmountsMismatch(); | ||
error InvalidValue(); | ||
error FailedToSendEther(); | ||
|
||
contract MultiPayment { | ||
function pay(address payable[] calldata recipients, uint256[] calldata amounts) public payable { | ||
if (recipients.length != amounts.length) { | ||
revert RecipientsAndAmountsMismatch(); | ||
} | ||
// Ensure value sent is equal to the total amount to send | ||
uint256 total = 0; | ||
for (uint256 i = 0; i < amounts.length; i++) { | ||
total += amounts[i]; | ||
} | ||
if (msg.value != total) { | ||
revert InvalidValue(); | ||
} | ||
|
||
for (uint256 i = 0; i < recipients.length; i++) { | ||
(bool sent,) = recipients[i].call{value: amounts[i]}(""); | ||
if (!sent) { | ||
revert FailedToSendEther(); | ||
} | ||
} | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,245 @@ | ||
// SPDX-License-Identifier: GNU GENERAL PUBLIC LICENSE | ||
pragma solidity ^0.8.13; | ||
|
||
import {Test, console} from "@forge-std/Test.sol"; | ||
import { | ||
MultiPayment, | ||
RecipientsAndAmountsMismatch, | ||
InvalidValue, | ||
FailedToSendEther | ||
} from "@contracts/multi-payment/MultiPayment.sol"; | ||
|
||
contract RejectPayments { | ||
receive() external payable { | ||
revert("Direct payments are not accepted"); | ||
} | ||
} | ||
|
||
contract MultiPaymentTest is Test { | ||
MultiPayment public multiPayment; | ||
|
||
function setUp() public { | ||
multiPayment = new MultiPayment(); | ||
} | ||
|
||
function test_pay_pass_with_zero_payment() public { | ||
address payable sender = payable(address(this)); | ||
vm.deal(sender, 100 ether); | ||
assertEq(sender.balance, 100 ether); | ||
|
||
address payable[] memory recipients = new address payable[](0); | ||
uint256[] memory amounts = new uint256[](0); | ||
|
||
// Act | ||
multiPayment.pay{value: 0}(recipients, amounts); | ||
|
||
// Assert | ||
assertEq(sender.balance, 100 ether); | ||
} | ||
|
||
function test_pay_pass_with_single_payment() public { | ||
address payable sender = payable(address(this)); | ||
vm.deal(sender, 100 ether); | ||
assertEq(sender.balance, 100 ether); | ||
|
||
address payable recipient = payable(address(1)); | ||
assertEq(recipient.balance, 0); | ||
|
||
address payable[] memory recipients = new address payable[](1); | ||
recipients[0] = recipient; | ||
|
||
uint256[] memory amounts = new uint256[](1); | ||
amounts[0] = 40 ether; | ||
|
||
// Act | ||
multiPayment.pay{value: 40 ether}(recipients, amounts); | ||
|
||
// Assert | ||
assertEq(recipient.balance, 40 ether); | ||
assertEq(sender.balance, 60 ether); | ||
} | ||
|
||
function test_pay_pass_with_sending_to_address_0() public { | ||
address payable sender = payable(address(this)); | ||
vm.deal(sender, 100 ether); | ||
assertEq(sender.balance, 100 ether); | ||
|
||
address payable recipient = payable(address(0)); | ||
assertEq(recipient.balance, 0); | ||
|
||
address payable[] memory recipients = new address payable[](1); | ||
recipients[0] = recipient; | ||
|
||
uint256[] memory amounts = new uint256[](1); | ||
amounts[0] = 40 ether; | ||
|
||
// Act | ||
multiPayment.pay{value: 40 ether}(recipients, amounts); | ||
|
||
// Assert | ||
assertEq(recipient.balance, 40 ether); | ||
assertEq(sender.balance, 60 ether); | ||
} | ||
|
||
function test_pay_pass_with_multiple_payments() public { | ||
address payable sender = payable(address(this)); | ||
vm.deal(sender, 100 ether); | ||
assertEq(sender.balance, 100 ether); | ||
|
||
address payable recipient1 = payable(address(1)); | ||
assertEq(recipient1.balance, 0); | ||
address payable recipient2 = payable(address(2)); | ||
assertEq(recipient2.balance, 0); | ||
address payable recipient3 = payable(address(3)); | ||
assertEq(recipient2.balance, 0); | ||
|
||
address payable[] memory recipients = new address payable[](3); | ||
recipients[0] = recipient1; | ||
recipients[1] = recipient2; | ||
recipients[2] = recipient3; | ||
|
||
uint256[] memory amounts = new uint256[](3); | ||
amounts[0] = 10 ether; | ||
amounts[1] = 20 ether; | ||
amounts[2] = 30 ether; | ||
|
||
// Act | ||
multiPayment.pay{value: 60 ether}(recipients, amounts); | ||
|
||
// Assert | ||
assertEq(recipient1.balance, 10 ether); | ||
assertEq(recipient2.balance, 20 ether); | ||
assertEq(recipient3.balance, 30 ether); | ||
assertEq(sender.balance, 40 ether); | ||
} | ||
|
||
function test_pay_pass_with_multiple_payments_same_address() public { | ||
address payable sender = payable(address(this)); | ||
vm.deal(sender, 100 ether); | ||
assertEq(sender.balance, 100 ether); | ||
|
||
address payable recipient1 = payable(address(1)); | ||
assertEq(recipient1.balance, 0); | ||
|
||
address payable[] memory recipients = new address payable[](3); | ||
recipients[0] = recipient1; | ||
recipients[1] = recipient1; | ||
recipients[2] = recipient1; | ||
|
||
uint256[] memory amounts = new uint256[](3); | ||
amounts[0] = 10 ether; | ||
amounts[1] = 20 ether; | ||
amounts[2] = 30 ether; | ||
|
||
// Act | ||
multiPayment.pay{value: 60 ether}(recipients, amounts); | ||
|
||
// Assert | ||
assertEq(recipient1.balance, 60 ether); | ||
assertEq(sender.balance, 40 ether); | ||
} | ||
|
||
function test_pay_pass_with_multiple_payments_large() public { | ||
uint256 payments = 10000; | ||
address payable[] memory recipients = new address payable[](payments); | ||
uint256[] memory amounts = new uint256[](payments); | ||
|
||
uint256 total = 0; | ||
for (uint256 i = 0; i < payments; i++) { | ||
recipients[i] = payable(address(uint160(i + 10))); // For some reason address(9) reverts // TODO: Check why | ||
amounts[i] = 1; | ||
total += 1; | ||
} | ||
|
||
address payable sender = payable(address(this)); | ||
vm.deal(sender, total); | ||
|
||
// Act | ||
multiPayment.pay{value: total}(recipients, amounts); | ||
|
||
// Assert | ||
assertEq(sender.balance, 0); | ||
for (uint256 i = 0; i < payments; i++) { | ||
assertEq(recipients[i].balance, 1); | ||
} | ||
} | ||
|
||
function test_pay_fail_with_recipients_and_amounts_mismatch() public { | ||
address payable sender = payable(address(this)); | ||
vm.deal(sender, 100 ether); | ||
assertEq(sender.balance, 100 ether); | ||
|
||
address payable recipient = payable(address(1)); | ||
assertEq(recipient.balance, 0); | ||
|
||
address payable[] memory recipients = new address payable[](1); | ||
recipients[0] = recipient; | ||
|
||
uint256[] memory amounts = new uint256[](2); | ||
amounts[0] = 40 ether; | ||
amounts[1] = 60 ether; | ||
|
||
// Act | ||
vm.expectRevert(RecipientsAndAmountsMismatch.selector); | ||
multiPayment.pay{value: 100 ether}(recipients, amounts); | ||
} | ||
|
||
function test_pay_fail_with_invalid_value() public { | ||
address payable sender = payable(address(this)); | ||
vm.deal(sender, 100 ether); | ||
assertEq(sender.balance, 100 ether); | ||
|
||
address payable recipient = payable(address(1)); | ||
assertEq(recipient.balance, 0); | ||
|
||
address payable[] memory recipients = new address payable[](1); | ||
recipients[0] = recipient; | ||
|
||
uint256[] memory amounts = new uint256[](1); | ||
amounts[0] = 40 ether; | ||
|
||
// Act | ||
vm.expectRevert(InvalidValue.selector); | ||
multiPayment.pay{value: 50 ether}(recipients, amounts); | ||
} | ||
|
||
function test_pay_fail_with_failed_to_send_ether() public { | ||
address payable sender = payable(address(this)); | ||
vm.deal(sender, 100 ether); | ||
assertEq(sender.balance, 100 ether); | ||
|
||
RejectPayments rejectPayments = new RejectPayments(); | ||
address payable recipient = payable(address(rejectPayments)); | ||
assertEq(recipient.balance, 0); | ||
|
||
address payable[] memory recipients = new address payable[](1); | ||
recipients[0] = recipient; | ||
|
||
uint256[] memory amounts = new uint256[](1); | ||
amounts[0] = 40 ether; | ||
|
||
// Act | ||
recipient = payable(address(0)); // Force recipient to be address(0) | ||
vm.expectRevert(FailedToSendEther.selector); | ||
multiPayment.pay{value: 40 ether}(recipients, amounts); | ||
} | ||
|
||
function test_pay_fail_if_no_enough_balance() public { | ||
address payable sender = payable(address(this)); | ||
vm.deal(sender, 100 ether); | ||
assertEq(sender.balance, 100 ether); | ||
|
||
address payable recipient = payable(address(1)); | ||
assertEq(recipient.balance, 0); | ||
|
||
address payable[] memory recipients = new address payable[](1); | ||
recipients[0] = recipient; | ||
|
||
uint256[] memory amounts = new uint256[](1); | ||
amounts[0] = 10 ether; | ||
|
||
// Act | ||
vm.expectRevert(); | ||
multiPayment.pay{value: 110 ether}(recipients, amounts); | ||
} | ||
} |
Oops, something went wrong.