-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e1a21d6
commit d78ad75
Showing
2 changed files
with
243 additions
and
21 deletions.
There are no files selected for viewing
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 |
---|---|---|
@@ -1,26 +1,15 @@ | ||
No files changed, compilation skipped | ||
|
||
Running 2 tests for test/benchmarks/BenchmarkPaymentsGatewaySplit.t.sol:BenchmarkPaymentsGatewaySplitTest | ||
[32m[PASS][0m test_startTransfer_erc20() (gas: 131301) | ||
[32m[PASS][0m test_startTransfer_nativeToken() (gas: 141479) | ||
Test result: [32mok[0m. [32m2[0m passed; [31m0[0m failed; [33m0[0m skipped; finished in 1.69ms | ||
Ran 2 tests for test/benchmarks/BenchmarkModularPaymentsGateway.t.sol:BenchmarkModularPaymentsGatewayTest | ||
[PASS] test_initiateTokenPurchase_erc20() (gas: 181350) | ||
[PASS] test_initiateTokenPurchase_nativeToken() (gas: 246176) | ||
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.83ms (777.63µs CPU time) | ||
|
||
Running 2 tests for test/benchmarks/BenchmarkPaymentsGateway.t.sol:BenchmarkPaymentsGatewayTest | ||
[32m[PASS][0m test_startTransfer_erc20() (gas: 158244) | ||
[32m[PASS][0m test_startTransfer_nativeToken() (gas: 183194) | ||
Test result: [32mok[0m. [32m2[0m passed; [31m0[0m failed; [33m0[0m skipped; finished in 1.94ms | ||
Ran 2 tests for test/benchmarks/BenchmarkPaymentsGateway.t.sol:BenchmarkPaymentsGatewayTest | ||
[PASS] test_initiateTokenPurchase_erc20() (gas: 150635) | ||
[PASS] test_initiateTokenPurchase_nativeToken() (gas: 209936) | ||
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.83ms (1.01ms CPU time) | ||
|
||
Running 2 tests for test/benchmarks/BenchmarkPaymentsGatewayPull.t.sol:BenchmarkPaymentsGatewayPullTest | ||
[32m[PASS][0m test_startTransfer_erc20() (gas: 184188) | ||
[32m[PASS][0m test_startTransfer_nativeToken() (gas: 181962) | ||
Test result: [32mok[0m. [32m2[0m passed; [31m0[0m failed; [33m0[0m skipped; finished in 1.81ms | ||
|
||
|
||
Ran 3 test suites: [32m6[0m tests passed, [31m0[0m failed, [33m0[0m skipped (6 total tests) | ||
test_startTransfer_erc20() (gas: 0 (0.000%)) | ||
test_startTransfer_nativeToken() (gas: 0 (0.000%)) | ||
test_startTransfer_erc20() (gas: 0 (0.000%)) | ||
test_startTransfer_nativeToken() (gas: 0 (0.000%)) | ||
test_startTransfer_erc20() (gas: 0 (0.000%)) | ||
test_startTransfer_nativeToken() (gas: 0 (0.000%)) | ||
Overall gas change: 0 (0.000%) | ||
Ran 2 test suites in 2.70ms (3.65ms CPU time): 4 tests passed, 0 failed, 0 skipped (4 total tests) | ||
Overall gas change: 0 (NaN%) |
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,233 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.13; | ||
|
||
import { Test, console } from "forge-std/Test.sol"; | ||
import { ModularPaymentsGateway } from "src/ModularPaymentsGateway.sol"; | ||
import { PaymentsGatewayExtension } from "src/PaymentsGatewayExtension.sol"; | ||
import { LibClone } from "lib/solady/src/utils/LibClone.sol"; | ||
import { MockERC20 } from "../utils/MockERC20.sol"; | ||
import { MockTarget } from "../utils/MockTarget.sol"; | ||
|
||
contract BenchmarkModularPaymentsGatewayTest is Test { | ||
PaymentsGatewayExtension internal gateway; | ||
MockERC20 internal mockERC20; | ||
MockTarget internal mockTarget; | ||
|
||
address payable internal owner; | ||
address payable internal operator; | ||
address payable internal sender; | ||
address payable internal receiver; | ||
address payable internal client; | ||
|
||
bytes32 internal ownerClientId; | ||
bytes32 internal clientId; | ||
|
||
uint256 internal ownerFeeAmount; | ||
uint256 internal clientFeeAmount; | ||
uint256 internal totalFeeAmount; | ||
|
||
PaymentsGatewayExtension.PayoutInfo[] internal payouts; | ||
|
||
bytes32 internal typehashPayRequest; | ||
bytes32 internal typehashPayoutInfo; | ||
bytes32 internal nameHash; | ||
bytes32 internal versionHash; | ||
bytes32 internal typehashEip712; | ||
bytes32 internal domainSeparator; | ||
|
||
function setUp() public { | ||
owner = payable(vm.addr(1)); | ||
operator = payable(vm.addr(2)); | ||
sender = payable(vm.addr(3)); | ||
receiver = payable(vm.addr(4)); | ||
client = payable(vm.addr(5)); | ||
|
||
ownerClientId = keccak256("owner"); | ||
clientId = keccak256("client"); | ||
|
||
ownerFeeAmount = 20; | ||
clientFeeAmount = 10; | ||
|
||
// deploy and install extension | ||
address impl = address(new ModularPaymentsGateway()); | ||
address extension = address(new PaymentsGatewayExtension()); | ||
|
||
address[] memory extensions = new address[](1); | ||
bytes[] memory extensionData = new bytes[](1); | ||
extensions[0] = address(extension); | ||
extensionData[0] = ""; | ||
|
||
gateway = PaymentsGatewayExtension(LibClone.clone(impl)); | ||
ModularPaymentsGateway(payable(address(gateway))).initialize(operator, extensions, extensionData); | ||
|
||
mockERC20 = new MockERC20("Token", "TKN"); | ||
mockTarget = new MockTarget(); | ||
|
||
// fund the sender | ||
mockERC20.mint(sender, 10 ether); | ||
vm.deal(sender, 10 ether); | ||
|
||
// build payout info | ||
payouts.push( | ||
PaymentsGatewayExtension.PayoutInfo({ | ||
clientId: ownerClientId, | ||
payoutAddress: owner, | ||
feeAmount: ownerFeeAmount | ||
}) | ||
); | ||
payouts.push( | ||
PaymentsGatewayExtension.PayoutInfo({ | ||
clientId: clientId, | ||
payoutAddress: client, | ||
feeAmount: clientFeeAmount | ||
}) | ||
); | ||
|
||
for (uint256 i = 0; i < payouts.length; i++) { | ||
totalFeeAmount += payouts[i].feeAmount; | ||
} | ||
|
||
// EIP712 | ||
typehashPayoutInfo = keccak256("PayoutInfo(bytes32 clientId,address payoutAddress,uint256 feeAmount)"); | ||
typehashPayRequest = keccak256( | ||
"PayRequest(bytes32 clientId,bytes32 transactionId,address tokenAddress,uint256 tokenAmount,uint256 expirationTimestamp,PayoutInfo[] payouts,address forwardAddress,bytes data)PayoutInfo(bytes32 clientId,address payoutAddress,uint256 feeAmount)" | ||
); | ||
nameHash = keccak256(bytes("PaymentsGateway")); | ||
versionHash = keccak256(bytes("1")); | ||
typehashEip712 = keccak256( | ||
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" | ||
); | ||
domainSeparator = keccak256(abi.encode(typehashEip712, nameHash, versionHash, block.chainid, address(gateway))); | ||
} | ||
|
||
/*/////////////////////////////////////////////////////////////// | ||
internal util functions | ||
//////////////////////////////////////////////////////////////*/ | ||
|
||
function _buildMockTargetCalldata( | ||
address _sender, | ||
address _receiver, | ||
address _token, | ||
uint256 _sendValue, | ||
string memory _message | ||
) internal pure returns (bytes memory data) { | ||
data = abi.encode(_sender, _receiver, _token, _sendValue, _message); | ||
} | ||
|
||
function _hashPayoutInfo(PaymentsGatewayExtension.PayoutInfo[] memory _payouts) private view returns (bytes32) { | ||
bytes32 payoutHash = typehashPayoutInfo; | ||
|
||
bytes32[] memory payoutsHashes = new bytes32[](_payouts.length); | ||
for (uint i = 0; i < payouts.length; i++) { | ||
payoutsHashes[i] = keccak256( | ||
abi.encode(payoutHash, _payouts[i].clientId, _payouts[i].payoutAddress, _payouts[i].feeAmount) | ||
); | ||
} | ||
return keccak256(abi.encodePacked(payoutsHashes)); | ||
} | ||
|
||
function _prepareAndSignData( | ||
uint256 _operatorPrivateKey, | ||
PaymentsGatewayExtension.PayRequest memory req | ||
) internal view returns (bytes memory signature) { | ||
bytes memory dataToHash; | ||
{ | ||
bytes32 _payoutsHash = _hashPayoutInfo(req.payouts); | ||
dataToHash = abi.encode( | ||
typehashPayRequest, | ||
req.clientId, | ||
req.transactionId, | ||
req.tokenAddress, | ||
req.tokenAmount, | ||
req.expirationTimestamp, | ||
_payoutsHash, | ||
req.forwardAddress, | ||
keccak256(req.data) | ||
); | ||
} | ||
|
||
{ | ||
bytes32 _structHash = keccak256(dataToHash); | ||
bytes32 typedDataHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator, _structHash)); | ||
(uint8 v, bytes32 r, bytes32 s) = vm.sign(_operatorPrivateKey, typedDataHash); | ||
|
||
signature = abi.encodePacked(r, s, v); | ||
} | ||
} | ||
|
||
/*/////////////////////////////////////////////////////////////// | ||
Test `initiateTokenPurchase` | ||
//////////////////////////////////////////////////////////////*/ | ||
|
||
function test_initiateTokenPurchase_erc20() public { | ||
vm.pauseGasMetering(); | ||
uint256 sendValue = 1 ether; | ||
uint256 sendValueWithFees = sendValue + totalFeeAmount; | ||
bytes memory targetCalldata = _buildMockTargetCalldata(sender, receiver, address(mockERC20), sendValue, ""); | ||
|
||
// approve amount to gateway contract | ||
vm.prank(sender); | ||
mockERC20.approve(address(gateway), sendValueWithFees); | ||
|
||
// create pay request | ||
PaymentsGatewayExtension.PayRequest memory req; | ||
bytes32 _transactionId = keccak256("transaction ID"); | ||
|
||
req.clientId = clientId; | ||
req.transactionId = _transactionId; | ||
req.tokenAddress = address(mockERC20); | ||
req.tokenAmount = sendValue; | ||
req.forwardAddress = payable(address(mockTarget)); | ||
req.expirationTimestamp = 1000; | ||
req.data = targetCalldata; | ||
req.payouts = payouts; | ||
|
||
// generate signature | ||
bytes memory _signature = _prepareAndSignData( | ||
2, // sign with operator private key, i.e. 2 | ||
req | ||
); | ||
|
||
// send transaction | ||
vm.prank(sender); | ||
vm.resumeGasMetering(); | ||
gateway.initiateTokenPurchase(req, _signature); | ||
} | ||
|
||
function test_initiateTokenPurchase_nativeToken() public { | ||
vm.pauseGasMetering(); | ||
uint256 sendValue = 1 ether; | ||
uint256 sendValueWithFees = sendValue + totalFeeAmount; | ||
bytes memory targetCalldata = _buildMockTargetCalldata( | ||
sender, | ||
receiver, | ||
address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE), | ||
sendValue, | ||
"" | ||
); | ||
|
||
// create pay request | ||
PaymentsGatewayExtension.PayRequest memory req; | ||
bytes32 _transactionId = keccak256("transaction ID"); | ||
|
||
req.clientId = clientId; | ||
req.transactionId = _transactionId; | ||
req.tokenAddress = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); | ||
req.tokenAmount = sendValue; | ||
req.forwardAddress = payable(address(mockTarget)); | ||
req.expirationTimestamp = 1000; | ||
req.data = targetCalldata; | ||
req.payouts = payouts; | ||
|
||
// generate signature | ||
bytes memory _signature = _prepareAndSignData( | ||
2, // sign with operator private key, i.e. 2 | ||
req | ||
); | ||
|
||
// send transaction | ||
vm.prank(sender); | ||
vm.resumeGasMetering(); | ||
gateway.initiateTokenPurchase{ value: sendValueWithFees }(req, _signature); | ||
} | ||
} |