diff --git a/src/contracts/Grateful.sol b/src/contracts/Grateful.sol index c609a49..33a737c 100644 --- a/src/contracts/Grateful.sol +++ b/src/contracts/Grateful.sol @@ -32,9 +32,6 @@ contract Grateful is IGrateful, Ownable2Step { /// @inheritdoc IGrateful mapping(address => bool) public tokensWhitelisted; - /// @inheritdoc IGrateful - mapping(address => bool) public yieldingFunds; - /// @inheritdoc IGrateful mapping(address => AaveV3ERC4626) public vaults; @@ -149,9 +146,10 @@ contract Grateful is IGrateful, Ownable2Step { address _merchant, address _token, uint256 _amount, - uint256 _id + uint256 _id, + bool _yieldFunds ) external onlyWhenTokenWhitelisted(_token) { - _processPayment(msg.sender, _merchant, _token, _amount, _id); + _processPayment(msg.sender, _merchant, _token, _amount, _id, _yieldFunds); } /// @inheritdoc IGrateful @@ -161,19 +159,27 @@ contract Grateful is IGrateful, Ownable2Step { uint256 _amount, uint256 _salt, uint256 _paymentId, + bool _yieldFunds, address precomputed ) external onlyWhenTokensWhitelisted(_tokens) returns (OneTime oneTime) { oneTimePayments[precomputed] = true; - oneTime = new OneTime{salt: bytes32(_salt)}(IGrateful(address(this)), _tokens, _merchant, _amount, _paymentId); + oneTime = + new OneTime{salt: bytes32(_salt)}(IGrateful(address(this)), _tokens, _merchant, _amount, _paymentId, _yieldFunds); emit OneTimePaymentCreated(_merchant, _tokens, _amount); } /// @inheritdoc IGrateful - function receiveOneTimePayment(address _merchant, address _token, uint256 _paymentId, uint256 _amount) external { + function receiveOneTimePayment( + address _merchant, + address _token, + uint256 _paymentId, + uint256 _amount, + bool _yieldFunds + ) external { if (!oneTimePayments[msg.sender]) { revert Grateful_OneTimeNotFound(); } - _processPayment(msg.sender, _merchant, _token, _amount, _paymentId); + _processPayment(msg.sender, _merchant, _token, _amount, _paymentId, _yieldFunds); } /// @inheritdoc IGrateful @@ -182,10 +188,12 @@ contract Grateful is IGrateful, Ownable2Step { address[] memory _tokens, uint256 _amount, uint256 _salt, - uint256 _paymentId + uint256 _paymentId, + bool _yieldFunds ) external view returns (OneTime oneTime) { - bytes memory bytecode = - abi.encodePacked(type(OneTime).creationCode, abi.encode(address(this), _tokens, _merchant, _amount, _paymentId)); + bytes memory bytecode = abi.encodePacked( + type(OneTime).creationCode, abi.encode(address(this), _tokens, _merchant, _amount, _paymentId, _yieldFunds) + ); bytes32 bytecodeHash = keccak256(bytecode); bytes32 addressHash = keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(_salt), bytecodeHash)); address computedAddress = address(uint160(uint256(addressHash))); @@ -265,11 +273,6 @@ contract Grateful is IGrateful, Ownable2Step { } } - /// @inheritdoc IGrateful - function switchYieldingFunds() external { - yieldingFunds[msg.sender] = !yieldingFunds[msg.sender]; - } - /// @inheritdoc IGrateful function setFee( uint256 _newFee @@ -300,13 +303,15 @@ contract Grateful is IGrateful, Ownable2Step { * @param _token Address of the token. * @param _amount Amount of the token. * @param _paymentId ID of the payment + * @param _yieldFunds Whether to yield funds or not */ function _processPayment( address _sender, address _merchant, address _token, uint256 _amount, - uint256 _paymentId + uint256 _paymentId, + bool _yieldFunds ) internal { // Transfer the full amount from the sender to this contract IERC20(_token).safeTransferFrom(_sender, address(this), _amount); @@ -325,7 +330,7 @@ contract Grateful is IGrateful, Ownable2Step { IERC20(_token).safeTransfer(owner(), _amount - amountWithFee); // Proceed as before, paying the merchant - if (yieldingFunds[_merchant]) { + if (_yieldFunds) { AaveV3ERC4626 vault = vaults[_token]; if (address(vault) == address(0)) { IERC20(_token).safeTransfer(_merchant, amountWithFee); @@ -338,6 +343,6 @@ contract Grateful is IGrateful, Ownable2Step { IERC20(_token).safeTransfer(_merchant, amountWithFee); } - emit PaymentProcessed(_sender, _merchant, _token, _amount, yieldingFunds[_merchant], _paymentId); + emit PaymentProcessed(_sender, _merchant, _token, _amount, _yieldFunds, _paymentId); } } diff --git a/src/contracts/OneTime.sol b/src/contracts/OneTime.sol index ec214a1..49937af 100644 --- a/src/contracts/OneTime.sol +++ b/src/contracts/OneTime.sol @@ -13,14 +13,21 @@ contract OneTime { IGrateful immutable grateful; - constructor(IGrateful _grateful, address[] memory _tokens, address _merchant, uint256 _amount, uint256 _paymentId) { + constructor( + IGrateful _grateful, + address[] memory _tokens, + address _merchant, + uint256 _amount, + uint256 _paymentId, + bool _yieldFunds + ) { 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); + _grateful.receiveOneTimePayment(_merchant, address(token), _paymentId, _amount, _yieldFunds); } } } diff --git a/src/interfaces/IGrateful.sol b/src/interfaces/IGrateful.sol index 6003478..914a471 100644 --- a/src/interfaces/IGrateful.sol +++ b/src/interfaces/IGrateful.sol @@ -103,13 +103,6 @@ interface IGrateful { address _token ) external view returns (bool); - /// @notice Returns the yielding preference of a merchant. - /// @param _merchant Address of the merchant. - /// @return True if the merchant prefers yielding funds, false otherwise. - function yieldingFunds( - address _merchant - ) external view returns (bool); - /// @notice Returns the vault associated with a token. /// @param _token Address of the token. /// @return Address of the vault contract. @@ -174,8 +167,9 @@ interface IGrateful { * @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 _yieldFunds Whether to yield funds or not */ - function pay(address _merchant, address _token, uint256 _amount, uint256 _id) external; + function pay(address _merchant, address _token, uint256 _amount, uint256 _id, bool _yieldFunds) external; /** * @notice Creates a one-time payment without payment splitting. @@ -184,6 +178,7 @@ interface IGrateful { * @param _amount Amount of the token. * @param _salt Salt used for address computation. * @param _paymentId ID of the payment. + * @param _yieldFunds Whether to yield funds or not * @param precomputed Precomputed address of the OneTime contract. * @return oneTime Address of the created OneTime contract. */ @@ -193,6 +188,7 @@ interface IGrateful { uint256 _amount, uint256 _salt, uint256 _paymentId, + bool _yieldFunds, address precomputed ) external returns (OneTime oneTime); @@ -201,9 +197,16 @@ interface IGrateful { * @param _merchant Address of the merchant. * @param _token Token address. * @param _paymentId ID of the payment. + * @param _yieldFunds Whether to yield funds or not * @param _amount Amount of the token. */ - function receiveOneTimePayment(address _merchant, address _token, uint256 _paymentId, uint256 _amount) external; + function receiveOneTimePayment( + address _merchant, + address _token, + uint256 _paymentId, + uint256 _amount, + bool _yieldFunds + ) external; /** * @notice Computes the address of a one-time payment contract without payment splitting. @@ -212,6 +215,7 @@ interface IGrateful { * @param _amount Amount of the token. * @param _salt Salt used for address computation. * @param _paymentId ID of the payment. + * @param _yieldFunds Whether to yield funds or not * @return oneTime Address of the computed OneTime contract. */ function computeOneTimeAddress( @@ -219,7 +223,8 @@ interface IGrateful { address[] memory _tokens, uint256 _amount, uint256 _salt, - uint256 _paymentId + uint256 _paymentId, + bool _yieldFunds ) external view returns (OneTime oneTime); /** @@ -252,11 +257,6 @@ interface IGrateful { */ function withdrawMultiple(address[] memory _tokens, uint256[] memory _assets) external; - /** - * @notice Toggles the merchant's preference to yield funds. - */ - function switchYieldingFunds() external; - /** * @notice Calculates the ID of a payment. * @param _sender Address of the sender. diff --git a/test/integration/Grateful.t.sol b/test/integration/Grateful.t.sol index 416d9d0..0fccc96 100644 --- a/test/integration/Grateful.t.sol +++ b/test/integration/Grateful.t.sol @@ -5,31 +5,24 @@ import {OneTime} from "contracts/OneTime.sol"; import {IntegrationBase} from "test/integration/IntegrationBase.sol"; contract IntegrationGreeter is IntegrationBase { - function _approveAndPay(address payer, address merchant, uint256 amount) internal { + function _approveAndPay(address payer, address merchant, uint256 amount, bool _yieldFunds) internal { uint256 paymentId = _grateful.calculateId(payer, merchant, address(_usdc), amount); vm.startPrank(payer); _usdc.approve(address(_grateful), amount); - _grateful.pay(merchant, address(_usdc), amount, paymentId); + _grateful.pay(merchant, address(_usdc), amount, paymentId, _yieldFunds); vm.stopPrank(); } function test_Payment() public { - _approveAndPay(_payer, _merchant, _AMOUNT_USDC); + _approveAndPay(_payer, _merchant, _AMOUNT_USDC, false); assertEq(_usdc.balanceOf(_merchant), _grateful.applyFee(_merchant, _AMOUNT_USDC)); } function test_PaymentYieldingFunds() public { - assertEq(_grateful.yieldingFunds(_merchant), false); + _approveAndPay(_payer, _merchant, _AMOUNT_USDC, true); - vm.prank(_merchant); - _grateful.switchYieldingFunds(); - - assertEq(_grateful.yieldingFunds(_merchant), true); - - _approveAndPay(_payer, _merchant, _AMOUNT_USDC); - - vm.warp(block.timestamp + 60 days); + vm.warp(block.timestamp + 1 days); vm.prank(_merchant); _grateful.withdraw(address(_usdc)); @@ -42,7 +35,8 @@ contract IntegrationGreeter is IntegrationBase { uint256 paymentId = _grateful.calculateId(_payer, _merchant, address(_usdc), _AMOUNT_USDC); // 2. Precompute address - address precomputed = address(_grateful.computeOneTimeAddress(_merchant, _tokens, _AMOUNT_USDC, 4, paymentId)); + address precomputed = + address(_grateful.computeOneTimeAddress(_merchant, _tokens, _AMOUNT_USDC, 4, paymentId, false)); // 3. Once the payment address is precomputed, the client sends the payment vm.prank(_payer); @@ -50,7 +44,7 @@ contract IntegrationGreeter is IntegrationBase { // 4. Merchant calls api to make one time payment to his address vm.prank(_gratefulAutomation); - _grateful.createOneTimePayment(_merchant, _tokens, _AMOUNT_USDC, 4, paymentId, precomputed); + _grateful.createOneTimePayment(_merchant, _tokens, _AMOUNT_USDC, 4, paymentId, false, precomputed); // Merchant receives the payment assertEq(_usdc.balanceOf(_merchant), _grateful.applyFee(_merchant, _AMOUNT_USDC)); @@ -61,7 +55,8 @@ contract IntegrationGreeter is IntegrationBase { uint256 paymentId = _grateful.calculateId(_payer, _merchant, address(_usdc), _AMOUNT_USDC); // 2. Precompute address - address precomputed = address(_grateful.computeOneTimeAddress(_merchant, _tokens, _AMOUNT_USDC, 4, paymentId)); + address precomputed = + address(_grateful.computeOneTimeAddress(_merchant, _tokens, _AMOUNT_USDC, 4, paymentId, false)); // 3. Once the payment address is precomputed, the client sends the payment vm.prank(_payer); @@ -69,7 +64,8 @@ contract IntegrationGreeter is IntegrationBase { // 4. Merchant calls api to make one time payment to his address vm.prank(_gratefulAutomation); - OneTime _oneTime = _grateful.createOneTimePayment(_merchant, _tokens, _AMOUNT_USDC, 4, paymentId, precomputed); + OneTime _oneTime = + _grateful.createOneTimePayment(_merchant, _tokens, _AMOUNT_USDC, 4, paymentId, false, precomputed); // Merchant receives the payment assertEq(_usdc.balanceOf(_merchant), _grateful.applyFee(_merchant, _AMOUNT_USDC)); @@ -95,7 +91,7 @@ contract IntegrationGreeter is IntegrationBase { _grateful.setCustomFee(200, _merchant); // Process payment with custom fee of 2% - _approveAndPay(_payer, _merchant, _AMOUNT_USDC); + _approveAndPay(_payer, _merchant, _AMOUNT_USDC, false); // Expected amounts uint256 expectedCustomFee = (_AMOUNT_USDC * 200) / 10_000; // 2% fee @@ -115,7 +111,7 @@ contract IntegrationGreeter is IntegrationBase { vm.warp(block.timestamp + 1); // Process payment with custom fee of 0% - _approveAndPay(_payer, _merchant, _AMOUNT_USDC); + _approveAndPay(_payer, _merchant, _AMOUNT_USDC, false); // Expected amounts uint256 expectedZeroFee = 0; // 0% fee @@ -141,7 +137,7 @@ contract IntegrationGreeter is IntegrationBase { vm.warp(block.timestamp + 1); // Process payment after unsetting custom fee - _approveAndPay(_payer, _merchant, _AMOUNT_USDC); + _approveAndPay(_payer, _merchant, _AMOUNT_USDC, false); // Expected amounts uint256 expectedFeeAfterUnset = (_AMOUNT_USDC * 100) / 10_000; // 1% fee @@ -151,7 +147,7 @@ contract IntegrationGreeter is IntegrationBase { assertEq( _usdc.balanceOf(_merchant), expectedMerchantAmount + expectedMerchantAmount2 + expectedMerchantAmount3, - "Merchant balance mismatch after fourth payment" + "Merchant balance mismatch after third payment" ); assertEq( _usdc.balanceOf(_owner), @@ -168,31 +164,27 @@ contract IntegrationGreeter is IntegrationBase { uint256 paymentId = _grateful.calculateId(_payer, _merchant, address(_usdc), _AMOUNT_USDC); // 2. Precompute address - address precomputed = address(_grateful.computeOneTimeAddress(_merchant, _tokens, _AMOUNT_USDC, 4, paymentId)); + address precomputed = address(_grateful.computeOneTimeAddress(_merchant, _tokens, _AMOUNT_USDC, 4, paymentId, true)); // 3. Once the payment address is precomputed, the client sends the payment vm.prank(_payer); _usdc.transfer(precomputed, _AMOUNT_USDC); - // 4. Set merchant to yield funds - vm.prank(_merchant); - _grateful.switchYieldingFunds(); - - // 5. Grateful automation calls api to make one time payment to his address + // 4. Grateful automation calls api to make one time payment to his address vm.prank(_gratefulAutomation); - _grateful.createOneTimePayment(_merchant, _tokens, _AMOUNT_USDC, 4, paymentId, precomputed); + _grateful.createOneTimePayment(_merchant, _tokens, _AMOUNT_USDC, 4, paymentId, true, precomputed); - // 6. Advance time + // 5. Advance time so yield is generated vm.warp(block.timestamp + 1 days); - // 7. Merchant withdraws funds + // 6. Merchant withdraws funds vm.prank(_merchant); _grateful.withdraw(address(_usdc)); - // 8. Check if merchant's balance is greater than the amount with fee applied + // 7. Check if merchant's balance is greater than the amount with fee applied assertGt(_usdc.balanceOf(_merchant), _grateful.applyFee(_merchant, _AMOUNT_USDC)); - // 9. Check that owner holds the fee amount + // 8. Check that owner holds the fee amount uint256 feeAmount = _AMOUNT_USDC - _grateful.applyFee(_merchant, _AMOUNT_USDC); assertEq(_usdc.balanceOf(_owner), feeAmount); }