diff --git a/src/contracts/Grateful.sol b/src/contracts/Grateful.sol index b7dc3a7..c609a49 100644 --- a/src/contracts/Grateful.sol +++ b/src/contracts/Grateful.sol @@ -151,72 +151,7 @@ contract Grateful is IGrateful, Ownable2Step { uint256 _amount, uint256 _id ) external onlyWhenTokenWhitelisted(_token) { - _processPayment(msg.sender, _merchant, _token, _amount, _id, new address[](0), new uint256[](0)); - } - - /// @inheritdoc IGrateful - function pay( - address _merchant, - address _token, - uint256 _amount, - uint256 _id, - address[] calldata _recipients, - uint256[] calldata _percentages - ) external onlyWhenTokenWhitelisted(_token) { - _processPayment(msg.sender, _merchant, _token, _amount, _id, _recipients, _percentages); - } - - /// @inheritdoc IGrateful - function createOneTimePayment( - address _merchant, - address[] memory _tokens, - uint256 _amount, - uint256 _salt, - uint256 _paymentId, - address precomputed, - address[] calldata _recipients, - uint256[] calldata _percentages - ) external onlyWhenTokensWhitelisted(_tokens) returns (OneTime oneTime) { - oneTimePayments[precomputed] = true; - oneTime = new OneTime{salt: bytes32(_salt)}( - IGrateful(address(this)), _tokens, _merchant, _amount, _paymentId, _recipients, _percentages - ); - emit OneTimePaymentCreated(_merchant, _tokens, _amount); - } - - /// @inheritdoc IGrateful - function receiveOneTimePayment( - address _merchant, - address _token, - uint256 _paymentId, - uint256 _amount, - address[] calldata _recipients, - uint256[] calldata _percentages - ) external { - if (!oneTimePayments[msg.sender]) { - revert Grateful_OneTimeNotFound(); - } - _processPayment(msg.sender, _merchant, _token, _amount, _paymentId, _recipients, _percentages); - } - - /// @inheritdoc IGrateful - function computeOneTimeAddress( - address _merchant, - address[] memory _tokens, - uint256 _amount, - uint256 _salt, - uint256 _paymentId, - address[] calldata _recipients, - uint256[] calldata _percentages - ) external view returns (OneTime oneTime) { - bytes memory bytecode = abi.encodePacked( - type(OneTime).creationCode, - abi.encode(address(this), _tokens, _merchant, _amount, _paymentId, _recipients, _percentages) - ); - bytes32 bytecodeHash = keccak256(bytecode); - bytes32 addressHash = keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(_salt), bytecodeHash)); - address computedAddress = address(uint160(uint256(addressHash))); - return OneTime(computedAddress); + _processPayment(msg.sender, _merchant, _token, _amount, _id); } /// @inheritdoc IGrateful @@ -229,29 +164,16 @@ contract Grateful is IGrateful, Ownable2Step { address precomputed ) external onlyWhenTokensWhitelisted(_tokens) returns (OneTime oneTime) { oneTimePayments[precomputed] = true; - oneTime = new OneTime{salt: bytes32(_salt)}( - IGrateful(address(this)), _tokens, _merchant, _amount, _paymentId, new address[](0), new uint256[](0) - ); + oneTime = new OneTime{salt: bytes32(_salt)}(IGrateful(address(this)), _tokens, _merchant, _amount, _paymentId); emit OneTimePaymentCreated(_merchant, _tokens, _amount); } /// @inheritdoc IGrateful - function receiveOneTimePayment( - address _merchant, - IERC20[] memory _tokens, - uint256 _paymentId, - uint256 _amount - ) external { + function receiveOneTimePayment(address _merchant, address _token, uint256 _paymentId, uint256 _amount) external { if (!oneTimePayments[msg.sender]) { revert Grateful_OneTimeNotFound(); } - for (uint256 i = 0; i < _tokens.length; i++) { - if (_tokens[i].balanceOf(msg.sender) >= _amount) { - _processPayment( - msg.sender, _merchant, address(_tokens[i]), _amount, _paymentId, new address[](0), new uint256[](0) - ); - } - } + _processPayment(msg.sender, _merchant, _token, _amount, _paymentId); } /// @inheritdoc IGrateful @@ -262,10 +184,8 @@ contract Grateful is IGrateful, Ownable2Step { uint256 _salt, uint256 _paymentId ) external view returns (OneTime oneTime) { - bytes memory bytecode = abi.encodePacked( - type(OneTime).creationCode, - abi.encode(address(this), _tokens, _merchant, _amount, _paymentId, new address[](0), new uint256[](0)) - ); + bytes memory bytecode = + abi.encodePacked(type(OneTime).creationCode, abi.encode(address(this), _tokens, _merchant, _amount, _paymentId)); bytes32 bytecodeHash = keccak256(bytecode); bytes32 addressHash = keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(_salt), bytecodeHash)); address computedAddress = address(uint160(uint256(addressHash))); @@ -379,18 +299,14 @@ contract Grateful is IGrateful, Ownable2Step { * @param _merchant Address of the merchant. * @param _token Address of the token. * @param _amount Amount of the token. - * @param _paymentId ID of the payment. - * @param _recipients List of recipients for payment splitting. - * @param _percentages Corresponding percentages for each recipient. + * @param _paymentId ID of the payment */ function _processPayment( address _sender, address _merchant, address _token, uint256 _amount, - uint256 _paymentId, - address[] memory _recipients, - uint256[] memory _percentages + uint256 _paymentId ) internal { // Transfer the full amount from the sender to this contract IERC20(_token).safeTransferFrom(_sender, address(this), _amount); @@ -400,61 +316,28 @@ contract Grateful is IGrateful, Ownable2Step { revert Grateful_PaymentIdAlreadyUsed(); } + paymentIds[_paymentId] = true; + // Apply the fee uint256 amountWithFee = applyFee(_merchant, _amount); // Transfer fee to owner IERC20(_token).safeTransfer(owner(), _amount - amountWithFee); - // If payment splitting is requested - if (_recipients.length > 0) { - if (_recipients.length != _percentages.length) { - revert Grateful_MismatchedArrays(); - } - uint256 totalPercentage = 0; - for (uint256 i = 0; i < _percentages.length; i++) { - totalPercentage += _percentages[i]; - } - if (totalPercentage != 10_000) { - revert Grateful_InvalidTotalPercentage(); - } - - // Distribute amountWithFee among recipients - for (uint256 i = 0; i < _recipients.length; i++) { - address recipient = _recipients[i]; - uint256 recipientShare = (amountWithFee * _percentages[i]) / 10_000; - - if (yieldingFunds[recipient]) { - AaveV3ERC4626 vault = vaults[_token]; - if (address(vault) == address(0)) { - IERC20(_token).safeTransfer(recipient, recipientShare); - } else { - uint256 _shares = vault.deposit(recipientShare, address(this)); - shares[recipient][_token] += _shares; - } - } else { - // Transfer tokens to recipient - IERC20(_token).safeTransfer(recipient, recipientShare); - } - } - } else { - // Proceed as before, paying the merchant - if (yieldingFunds[_merchant]) { - AaveV3ERC4626 vault = vaults[_token]; - if (address(vault) == address(0)) { - IERC20(_token).safeTransfer(_merchant, amountWithFee); - } else { - uint256 _shares = vault.deposit(amountWithFee, address(this)); - shares[_merchant][_token] += _shares; - } - } else { - // Transfer tokens to merchant + // Proceed as before, paying the merchant + if (yieldingFunds[_merchant]) { + AaveV3ERC4626 vault = vaults[_token]; + if (address(vault) == address(0)) { IERC20(_token).safeTransfer(_merchant, amountWithFee); + } else { + uint256 _shares = vault.deposit(amountWithFee, address(this)); + shares[_merchant][_token] += _shares; } + } else { + // Transfer tokens to merchant + IERC20(_token).safeTransfer(_merchant, amountWithFee); } - paymentIds[_paymentId] = true; - emit PaymentProcessed(_sender, _merchant, _token, _amount, yieldingFunds[_merchant], _paymentId); } } diff --git a/src/contracts/OneTime.sol b/src/contracts/OneTime.sol index 04d5a05..ec214a1 100644 --- a/src/contracts/OneTime.sol +++ b/src/contracts/OneTime.sol @@ -13,22 +13,14 @@ contract OneTime { IGrateful immutable grateful; - constructor( - IGrateful _grateful, - address[] memory _tokens, - address _merchant, - uint256 _amount, - uint256 _paymentId, - address[] memory _recipients, - uint256[] memory _percentages - ) { + constructor(IGrateful _grateful, address[] memory _tokens, address _merchant, uint256 _amount, uint256 _paymentId) { grateful = _grateful; for (uint256 i = 0; i < _tokens.length; i++) { IERC20 token = IERC20(_tokens[i]); if (token.balanceOf(address(this)) >= _amount) { token.safeIncreaseAllowance(address(_grateful), _amount); - _grateful.receiveOneTimePayment(_merchant, address(token), _paymentId, _amount, _recipients, _percentages); + _grateful.receiveOneTimePayment(_merchant, address(token), _paymentId, _amount); } } } diff --git a/src/interfaces/IGrateful.sol b/src/interfaces/IGrateful.sol index 6019b9d..6003478 100644 --- a/src/interfaces/IGrateful.sol +++ b/src/interfaces/IGrateful.sol @@ -177,47 +177,6 @@ interface IGrateful { */ function pay(address _merchant, address _token, uint256 _amount, uint256 _id) external; - /** - * @notice Makes a payment to a merchant with payment splitting. - * @param _merchant Address of the merchant receiving payment. - * @param _token Address of the token used for payment. - * @param _amount Amount of the token to be paid. - * @param _id ID of the payment. - * @param _recipients List of recipients for payment splitting. - * @param _percentages Corresponding percentages for each recipient (in basis points, 10000 = 100%). - */ - function pay( - address _merchant, - address _token, - uint256 _amount, - uint256 _id, - address[] calldata _recipients, - uint256[] calldata _percentages - ) external; - - /** - * @notice Creates a one-time payment with payment splitting. - * @param _merchant Address of the merchant. - * @param _tokens Array of token addresses. - * @param _amount Amount of the token. - * @param _salt Salt used for address computation. - * @param _paymentId ID of the payment. - * @param precomputed Precomputed address of the OneTime contract. - * @param _recipients List of recipients for payment splitting. - * @param _percentages Corresponding percentages for each recipient. - * @return oneTime Address of the created OneTime contract. - */ - function createOneTimePayment( - address _merchant, - address[] memory _tokens, - uint256 _amount, - uint256 _salt, - uint256 _paymentId, - address precomputed, - address[] calldata _recipients, - uint256[] calldata _percentages - ) external returns (OneTime oneTime); - /** * @notice Creates a one-time payment without payment splitting. * @param _merchant Address of the merchant. @@ -237,58 +196,14 @@ interface IGrateful { address precomputed ) external returns (OneTime oneTime); - /** - * @notice Receives a one-time payment with payment splitting. - * @param _merchant Address of the merchant. - * @param _token Address of the token. - * @param _paymentId ID of the payment. - * @param _amount Amount of the token. - * @param _recipients List of recipients for payment splitting. - * @param _percentages Corresponding percentages for each recipient. - */ - function receiveOneTimePayment( - address _merchant, - address _token, - uint256 _paymentId, - uint256 _amount, - address[] calldata _recipients, - uint256[] calldata _percentages - ) external; - /** * @notice Receives a one-time payment without payment splitting. * @param _merchant Address of the merchant. - * @param _tokens Array of token addresses. + * @param _token Token address. * @param _paymentId ID of the payment. * @param _amount Amount of the token. */ - function receiveOneTimePayment( - address _merchant, - IERC20[] memory _tokens, - uint256 _paymentId, - uint256 _amount - ) external; - - /** - * @notice Computes the address of a one-time payment contract with payment splitting. - * @param _merchant Address of the merchant. - * @param _tokens Array of token addresses. - * @param _amount Amount of the token. - * @param _salt Salt used for address computation. - * @param _paymentId ID of the payment. - * @param _recipients List of recipients for payment splitting. - * @param _percentages Corresponding percentages for each recipient. - * @return oneTime Address of the computed OneTime contract. - */ - function computeOneTimeAddress( - address _merchant, - address[] memory _tokens, - uint256 _amount, - uint256 _salt, - uint256 _paymentId, - address[] calldata _recipients, - uint256[] calldata _percentages - ) external view returns (OneTime oneTime); + function receiveOneTimePayment(address _merchant, address _token, uint256 _paymentId, uint256 _amount) external; /** * @notice Computes the address of a one-time payment contract without payment splitting. diff --git a/test/integration/Grateful.t.sol b/test/integration/Grateful.t.sol index 81d281a..416d9d0 100644 --- a/test/integration/Grateful.t.sol +++ b/test/integration/Grateful.t.sol @@ -196,76 +196,4 @@ contract IntegrationGreeter is IntegrationBase { uint256 feeAmount = _AMOUNT_USDC - _grateful.applyFee(_merchant, _AMOUNT_USDC); assertEq(_usdc.balanceOf(_owner), feeAmount); } - - function test_PaymentSplit() public { - // 1. Define recipients and percentages - address[] memory recipients = new address[](2); - recipients[0] = makeAddr("recipient1"); // Recipient 1 - recipients[1] = makeAddr("recipient2"); // Recipient 2 - - uint256[] memory percentages = new uint256[](2); - percentages[0] = 7000; // 70% - percentages[1] = 3000; // 30% - - // 2. Approve Grateful contract to spend USDC - vm.startPrank(_payer); - _usdc.approve(address(_grateful), _AMOUNT_USDC); - - // 3. Make a payment with splitting - uint256 paymentId = _grateful.calculateId(_payer, _merchant, address(_usdc), _AMOUNT_USDC); - - _grateful.pay(_merchant, address(_usdc), _AMOUNT_USDC, paymentId, recipients, percentages); - vm.stopPrank(); - - // 4. Calculate expected amounts after fee - uint256 amountAfterFee = _grateful.applyFee(_merchant, _AMOUNT_USDC); - uint256 expectedAmountRecipient0 = (amountAfterFee * percentages[0]) / 10_000; - uint256 expectedAmountRecipient1 = (amountAfterFee * percentages[1]) / 10_000; - - // 5. Check balances of recipients - assertEq(_usdc.balanceOf(recipients[0]), expectedAmountRecipient0); - assertEq(_usdc.balanceOf(recipients[1]), expectedAmountRecipient1); - - // Ensure the merchant did not receive any funds directly - assertEq(_usdc.balanceOf(_merchant), 0); - } - - function test_OneTimePaymentSplit() public { - // 1. Define recipients and percentages - address[] memory recipients = new address[](2); - recipients[0] = makeAddr("recipient1"); // Recipient 1 - recipients[1] = makeAddr("recipient2"); // Recipient 2 - - uint256[] memory percentages = new uint256[](2); - percentages[0] = 7000; // 70% - percentages[1] = 3000; // 30% - - // 2. Calculate payment id - uint256 paymentId = _grateful.calculateId(_payer, _merchant, address(_usdc), _AMOUNT_USDC); - - // 3. Precompute address - address precomputed = - address(_grateful.computeOneTimeAddress(_merchant, _tokens, _AMOUNT_USDC, 4, paymentId, recipients, percentages)); - - // 4. Once the payment address is precomputed, the client sends the payment - vm.prank(_payer); - _usdc.transfer(precomputed, _AMOUNT_USDC); - - // 5. Merchant calls api to make one time payment to his address - vm.prank(_gratefulAutomation); - _grateful.createOneTimePayment(_merchant, _tokens, _AMOUNT_USDC, 4, paymentId, precomputed, recipients, percentages); - - // 6. Calculate expected amounts after fee - uint256 amountAfterFee = _grateful.applyFee(_merchant, _AMOUNT_USDC); - uint256 expectedAmountRecipient0 = (amountAfterFee * percentages[0]) / 10_000; - uint256 expectedAmountRecipient1 = (amountAfterFee * percentages[1]) / 10_000; - - // 7. Check balances of recipients - assertEq(_usdc.balanceOf(recipients[0]), expectedAmountRecipient0); - assertEq(_usdc.balanceOf(recipients[1]), expectedAmountRecipient1); - - // Ensure owner received the fee - uint256 feeAmount = _AMOUNT_USDC - amountAfterFee; - assertEq(_usdc.balanceOf(_owner), feeAmount); - } }