Skip to content

Commit

Permalink
Multi transaction setup
Browse files Browse the repository at this point in the history
  • Loading branch information
kumaryash90 committed Aug 7, 2024
1 parent d0e22b9 commit 6be1017
Show file tree
Hide file tree
Showing 6 changed files with 1,064 additions and 735 deletions.
152 changes: 105 additions & 47 deletions src/PaymentsGatewayExtension.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pragma solidity ^0.8.22;

import { EIP712 } from "lib/solady/src/utils/EIP712.sol";
import { ERC20 } from "lib/solady/src/tokens/ERC20.sol";
import { WETH } from "lib/solady/src/tokens/WETH.sol";
import { SafeTransferLib } from "lib/solady/src/utils/SafeTransferLib.sol";
import { ReentrancyGuard } from "lib/solady/src/utils/ReentrancyGuard.sol";
import { ECDSA } from "lib/solady/src/utils/ECDSA.sol";
Expand Down Expand Up @@ -40,9 +41,13 @@ contract PaymentsGatewayExtension is EIP712, ModularExtension, ReentrancyGuard {

bytes32 private constant PAYOUTINFO_TYPEHASH =
keccak256("PayoutInfo(bytes32 clientId,address payoutAddress,uint256 feeAmount)");
bytes32 private constant TARGETINFO_TYPEHASH =
keccak256(
"TargetInfo(address forwardAddress,address tokenIn,uint256 amountInOffset,uint8 callType,bytes data)"
);
bytes32 private constant REQUEST_TYPEHASH =
keccak256(
"PayRequest(bytes32 clientId,bytes32 transactionId,address tokenAddress,uint256 tokenAmount,address tokenOut, uint256 minAmountOut,uint256 expirationTimestamp,PayoutInfo[] payouts,address[] forwardAddresses,uint256[] values,bytes[] data)PayoutInfo(bytes32 clientId,address payoutAddress,uint256 feeAmount)"
"PayRequest(bytes32 clientId,bytes32 transactionId,address tokenAddress,uint256 tokenAmount,address tokenOut, uint256 minAmountOut,uint256 expirationTimestamp,PayoutInfo[] payouts,TargetInfo[] targets)PayoutInfo(bytes32 clientId,address payoutAddress,uint256 feeAmount)TargetInfo(address forwardAddress,address tokenIn,uint256 amountInOffset,uint8 callType,bytes data)"
);
address private constant NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

Expand All @@ -59,6 +64,22 @@ contract PaymentsGatewayExtension is EIP712, ModularExtension, ReentrancyGuard {
uint256 feeAmount;
}

enum CallType {
Default,
Wrap,
Unwrap,
Swap
}

struct TargetInfo {
address payable forwardAddress;
address tokenIn;
// address tokenOut;
uint256 amountInOffset;
CallType callType;
bytes data;
}

/**
* @notice The body of a request to purchase tokens.
*
Expand All @@ -80,9 +101,7 @@ contract PaymentsGatewayExtension is EIP712, ModularExtension, ReentrancyGuard {
uint256 minAmountOut;
uint256 expirationTimestamp;
PayoutInfo[] payouts;
address payable[] forwardAddresses;
uint256[] values;
bytes[] data;
TargetInfo[] targets;
}

/*///////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -123,6 +142,7 @@ contract PaymentsGatewayExtension is EIP712, ModularExtension, ReentrancyGuard {
error PaymentsGatewayFailedToForward();
error PaymentsGatewayRequestExpired(uint256 expirationTimestamp);
error PaymentsGatewayReceivedLessThanMinAmount();
error PaymentsGatewayInvalidCallType();

/*//////////////////////////////////////////////////////////////
EXTENSION CONFIG
Expand Down Expand Up @@ -214,35 +234,64 @@ contract PaymentsGatewayExtension is EIP712, ModularExtension, ReentrancyGuard {
if (isTokenNative) {
sendValue = msg.value - totalFeeAmount;

uint256 len = req.values.length;
uint256 totalValue;
for (uint256 i = 0; i < len; i++) {
totalValue += req.values[i];
}

if (sendValue < req.tokenAmount || sendValue != totalValue) {
if (sendValue < req.tokenAmount) {
revert PaymentsGatewayMismatchedValue(sendValue, req.tokenAmount);
}
} else {
// pull user funds
SafeTransferLib.safeTransferFrom(req.tokenAddress, msg.sender, address(this), req.tokenAmount);

// NOTE: approval should be composed offchain, because multiple forwarders now (?)
//
// SafeTransferLib.safeApprove(req.tokenAddress, req.forwardAddress, req.tokenAmount);
}

uint256 balanceBefore;
if (req.minAmountOut != 0) {
balanceBefore = isTokenNative ? msg.sender.balance : ERC20(req.tokenAddress).balanceOf(msg.sender);
}

{
uint256 len = req.forwardAddresses.length;
// process target calls
uint256 len = req.targets.length;
for (uint256 i = 0; i < len; i++) {
(bool success, bytes memory response) = req.forwardAddresses[i].call{ value: req.values[i] }(
req.data[i]
);
TargetInfo memory target = req.targets[i];
bool success;
bytes memory response;
if (target.callType == CallType.Swap) {
require(target.tokenIn != address(0), "Invalid token addresses");
uint256 amount = _isTokenNative(target.tokenIn)
? address(this).balance
: ERC20(target.tokenIn).balanceOf(address(this));

if (target.amountInOffset != type(uint256).max) {
// Modify calldata to update amount to send
bytes memory data = target.data;
uint256 offset = target.amountInOffset;
require(data.length >= offset + 32, "Data length insufficient");

assembly {
// Get the starting memory address of data
let dataPtr := add(data, 0x20)

// Store the amount at the specified offset
mstore(add(dataPtr, offset), amount)
}
}

if (_isTokenNative(target.tokenIn)) {
(success, response) = target.forwardAddress.call{ value: amount }(target.data);
} else {
SafeTransferLib.safeApprove(target.tokenIn, target.forwardAddress, amount);
(success, response) = target.forwardAddress.call(target.data);
}
} else if (target.callType == CallType.Wrap) {
WETH(target.forwardAddress).deposit{ value: address(this).balance }();

success = true;
} else if (target.callType == CallType.Unwrap) {
uint256 amount = WETH(target.forwardAddress).balanceOf(address(this));
WETH(target.forwardAddress).withdraw(amount);

success = true;
} else if (target.callType == CallType.Default) {
(success, response) = target.forwardAddress.call(target.data);
} else {
revert PaymentsGatewayInvalidCallType();
}

// check
if (!success) {
// If there is return data, the delegate call reverted with a reason or a custom error, which we bubble up.
if (response.length > 0) {
Expand All @@ -257,13 +306,23 @@ contract PaymentsGatewayExtension is EIP712, ModularExtension, ReentrancyGuard {
}
}

uint256 balanceAfter;
if (req.minAmountOut != 0) {
balanceAfter = isTokenNative ? msg.sender.balance : ERC20(req.tokenAddress).balanceOf(msg.sender);
}
if (req.minAmountOut > 0 && req.tokenOut != address(0)) {
// check final amount out, and transfer to caller
if (_isTokenNative(req.tokenOut)) {
if (address(this).balance < req.minAmountOut) {
revert PaymentsGatewayReceivedLessThanMinAmount();
}

SafeTransferLib.safeTransferAllETH(msg.sender);
} else {
uint256 amount = ERC20(req.tokenOut).balanceOf(address(this));

if (balanceAfter - balanceBefore < req.minAmountOut) {
revert PaymentsGatewayReceivedLessThanMinAmount();
if (amount < req.minAmountOut) {
revert PaymentsGatewayReceivedLessThanMinAmount();
}

SafeTransferLib.safeTransfer(req.tokenOut, msg.sender, amount);
}
}

emit TokenPurchaseInitiated(req.clientId, msg.sender, req.transactionId, req.tokenAddress, req.tokenAmount);
Expand Down Expand Up @@ -326,17 +385,21 @@ contract PaymentsGatewayExtension is EIP712, ModularExtension, ReentrancyGuard {
return keccak256(abi.encodePacked(payoutsHashes));
}

function _hashBytesArray(bytes[] memory array) internal pure returns (bytes32) {
// bytes memory packed;
// for (uint i = 0; i < array.length; i++) {
// packed = abi.encodePacked(packed, keccak256(array[i]));
// }
// return keccak256(packed);
bytes32[] memory hashes = new bytes32[](array.length);
for (uint i = 0; i < array.length; i++) {
hashes[i] = keccak256(array[i]);
function _hashTargetInfo(TargetInfo[] calldata targets) private pure returns (bytes32) {
bytes32[] memory targetsHashes = new bytes32[](targets.length);
for (uint i = 0; i < targets.length; i++) {
targetsHashes[i] = keccak256(
abi.encode(
TARGETINFO_TYPEHASH,
targets[i].forwardAddress,
targets[i].tokenIn,
targets[i].amountInOffset,
targets[i].callType,
targets[i].data
)
);
}
return keccak256(abi.encodePacked(hashes));
return keccak256(abi.encodePacked(targetsHashes));
}

function _distributeFees(address tokenAddress, PayoutInfo[] calldata payouts) private returns (uint256) {
Expand Down Expand Up @@ -371,7 +434,7 @@ contract PaymentsGatewayExtension is EIP712, ModularExtension, ReentrancyGuard {
bool processed = PaymentsGatewayExtensionStorage.data().processed[req.transactionId];

bytes32 payoutsHash = _hashPayoutInfo(req.payouts);
bytes32 dataHash = _hashBytesArray(req.data);
bytes32 targetsHash = _hashTargetInfo(req.targets);
bytes32 structHash = keccak256(
bytes.concat(
abi.encode(
Expand All @@ -384,12 +447,7 @@ contract PaymentsGatewayExtension is EIP712, ModularExtension, ReentrancyGuard {
req.minAmountOut,
req.expirationTimestamp
),
abi.encode(
payoutsHash,
keccak256(abi.encodePacked(req.forwardAddresses)),
keccak256(abi.encodePacked(req.values)),
dataHash
)
abi.encode(payoutsHash, targetsHash)
)
);

Expand Down
Loading

0 comments on commit 6be1017

Please sign in to comment.