From d78ad75c25c92a149d137beb761f3e9cbd8b73b1 Mon Sep 17 00:00:00 2001 From: Yash Date: Thu, 1 Aug 2024 04:17:54 +0530 Subject: [PATCH] gas benchmarks --- gasreport.txt | 31 +-- .../BenchmarkModularPaymentsGateway.t.sol | 233 ++++++++++++++++++ 2 files changed, 243 insertions(+), 21 deletions(-) create mode 100644 test/benchmarks/BenchmarkModularPaymentsGateway.t.sol diff --git a/gasreport.txt b/gasreport.txt index 35000cb..7e3735c 100644 --- a/gasreport.txt +++ b/gasreport.txt @@ -1,26 +1,15 @@ No files changed, compilation skipped -Running 2 tests for test/benchmarks/BenchmarkPaymentsGatewaySplit.t.sol:BenchmarkPaymentsGatewaySplitTest -[PASS] test_startTransfer_erc20() (gas: 131301) -[PASS] test_startTransfer_nativeToken() (gas: 141479) -Test result: ok. 2 passed; 0 failed; 0 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 -[PASS] test_startTransfer_erc20() (gas: 158244) -[PASS] test_startTransfer_nativeToken() (gas: 183194) -Test result: ok. 2 passed; 0 failed; 0 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 -[PASS] test_startTransfer_erc20() (gas: 184188) -[PASS] test_startTransfer_nativeToken() (gas: 181962) -Test result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.81ms - -Ran 3 test suites: 6 tests passed, 0 failed, 0 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%) diff --git a/test/benchmarks/BenchmarkModularPaymentsGateway.t.sol b/test/benchmarks/BenchmarkModularPaymentsGateway.t.sol new file mode 100644 index 0000000..bb398de --- /dev/null +++ b/test/benchmarks/BenchmarkModularPaymentsGateway.t.sol @@ -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); + } +}