Skip to content

Commit

Permalink
feat: add counterfactual one time interaction with grateful contract
Browse files Browse the repository at this point in the history
  • Loading branch information
0xChin committed Sep 3, 2024
1 parent 6ee478b commit ff6a6e8
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 29 deletions.
51 changes: 46 additions & 5 deletions src/contracts/Grateful.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";

import {OneTime} from "contracts/OneTime.sol";
import {console} from "forge-std/console.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";
import {IGrateful} from "interfaces/IGrateful.sol";
import {AaveV3ERC4626, IPool, IRewardsController} from "yield-daddy/aave-v3/AaveV3ERC4626.sol";

import {Bytes32AddressLib} from "solmate/utils/Bytes32AddressLib.sol";
import {AaveV3ERC4626, IPool} from "yield-daddy/aave-v3/AaveV3ERC4626.sol";

contract Grateful is IGrateful, Ownable2Step {
using Bytes32AddressLib for bytes32;

// @inheritdoc IGrateful
IPool public aavePool;

Expand All @@ -28,6 +31,8 @@ contract Grateful is IGrateful, Ownable2Step {

mapping(uint256 => Subscription) public subscriptions;

mapping(address => bool) public oneTimePayments;

// @inheritdoc IGrateful
uint256 public subscriptionCount;

Expand Down Expand Up @@ -125,15 +130,51 @@ contract Grateful is IGrateful, Ownable2Step {
subscription.paymentsAmount--;
}

// @inheritdoc IGrateful
function createOneTimePayment(
address _merchant,
address _token,
uint256 _amount
) external onlyWhenTokenWhitelisted(_token) returns (address oneTimeAddress) {
oneTimeAddress = address(new OneTime(IERC20(_token), _merchant, _amount));
uint256 _amount,
uint256 _salt,
uint256 _paymentId,
address precomputed
) external onlyWhenTokenWhitelisted(_token) returns (OneTime oneTime) {
oneTimePayments[precomputed] = true;
oneTime =
new OneTime{salt: bytes32(_salt)}(IGrateful(address(this)), IERC20(_token), _merchant, _amount, _paymentId);
emit OneTimePaymentCreated(_merchant, _token, _amount);
}

function receiveOneTimePayment(address _merchant, address _token, uint256 _paymentId, uint256 _amount) external {
if (!oneTimePayments[msg.sender]) {
revert Grateful_OneTimeNotFound();
}
_processPayment(msg.sender, _merchant, _token, _amount, _paymentId, 0);
}

function computeOneTimeAddress(
address _merchant,
address _token,
uint256 _amount,
uint256 _salt,
uint256 _paymentId
) external view returns (OneTime oneTime) {
return OneTime(
keccak256(
abi.encodePacked(
bytes1(0xFF),
address(this),
bytes32(_salt),
keccak256(
abi.encodePacked(
type(OneTime).creationCode, abi.encode(address(this), _token, _merchant, _amount, _paymentId)
)
)
)
).fromLast20Bytes()
);
}

// @inheritdoc IGrateful
function withdraw(address _token) external onlyWhenTokenWhitelisted(_token) {
AaveV3ERC4626 vault = vaults[_token];
Expand Down
18 changes: 4 additions & 14 deletions src/contracts/OneTime.sol
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
import {IERC20} from "forge-std/interfaces/IERC20.sol";
import {IGrateful} from "interfaces/IGrateful.sol";

contract OneTime {
IERC20 public token;
address public merchant;
uint256 public amount;
bool public paid;

constructor(IERC20 _token, address _merchant, uint256 _amount) {
token = _token;
merchant = _merchant;
amount = _amount;
}

function processPayment() public {
paid = true;
token.transfer(merchant, amount);
constructor(IGrateful _grateful, IERC20 _token, address _merchant, uint256 _amount, uint256 _paymentId) {

Check warning on line 5 in src/contracts/OneTime.sol

View workflow job for this annotation

GitHub Actions / Lint Commit Messages

Constructor keyword not available before 0.4.22 (undefined)
_token.approve(address(_grateful), _amount);
_grateful.receiveOneTimePayment(_merchant, address(_token), _paymentId, _amount);
}
}
36 changes: 33 additions & 3 deletions src/interfaces/IGrateful.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import {OneTime} from "contracts/OneTime.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";

Check warning on line 5 in src/interfaces/IGrateful.sol

View workflow job for this annotation

GitHub Actions / Lint Commit Messages

Variable "IERC20" is unused
import {AaveV3ERC4626, IPool} from "yield-daddy/aave-v3/AaveV3ERC4626.sol";

Expand Down Expand Up @@ -91,6 +92,11 @@ interface IGrateful {
*/
error Grateful_PaymentsAmountReached();

/**
* @notice Throws if the one-time payment is not found
*/
error Grateful_OneTimeNotFound();

/*///////////////////////////////////////////////////////////////
VARIABLES
//////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -170,12 +176,36 @@ interface IGrateful {
/// @param _merchant Address of the merchant
/// @param _token Address of the token
/// @param _amount Amount of the token
/// @return oneTimeAddress Address of the one-time payment
/// @return oneTime Contract of the one-time payment
function createOneTimePayment(
address _merchant,
address _token,
uint256 _amount
) external returns (address oneTimeAddress);
uint256 _amount,
uint256 _salt,
uint256 _paymentId,
address precomputed
) external returns (OneTime oneTime);

/// @notice Receives a one-time payment
/// @param _merchant Address of the merchant
/// @param _token Address of the token
/// @param _paymentId Id of the payment
/// @param _amount Amount of the token
function receiveOneTimePayment(address _merchant, address _token, uint256 _paymentId, uint256 _amount) external;

/// @notice Computes the address of a one-time payment
/// @param _merchant Address of the merchant
/// @param _token Address of the token
/// @param _amount Amount of the token
/// @param _salt Salt used to compute the address
/// @return oneTime Address of the one-time payment
function computeOneTimeAddress(
address _merchant,
address _token,
uint256 _amount,
uint256 _salt,
uint256 _paymentId
) external view returns (OneTime oneTime);

/**
* @notice Processes a subscription
Expand Down
16 changes: 9 additions & 7 deletions test/integration/Grateful.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,19 @@ contract IntegrationGreeter is IntegrationBase {
}

function test_OneTimePayment() public {
// 1. Merchant calls api to make one time payment to his address
vm.prank(_gratefulAutomation);
address oneTimeAddress = _grateful.createOneTimePayment(_merchant, address(_usdc), _amount);
// 1. Calculate payment id
uint256 paymentId = _grateful.calculateId(_usdcWhale, _merchant, address(_usdc), _amount);

// 2. Precompute address
address precomputed = address(_grateful.computeOneTimeAddress(_merchant, address(_usdc), _amount, 4, paymentId));

// 2. Once the payment address is created, the client sends the payment
// 3. Once the payment address is precomputed, the client sends the payment
vm.prank(_usdcWhale);
_usdc.transfer(oneTimeAddress, _amount); // Only tx sent by the client, doesn't need contract interaction
_usdc.transfer(precomputed, _amount); // Only tx sent by the client, doesn't need contract interaction

// 3. The payment is processed
// 4. Merchant calls api to make one time payment to his address
vm.prank(_gratefulAutomation);
OneTime(oneTimeAddress).processPayment();
_grateful.createOneTimePayment(_merchant, address(_usdc), _amount, 4, paymentId, precomputed);

// Merchant receives the payment
assertEq(_usdc.balanceOf(_merchant), _amount);
Expand Down

0 comments on commit ff6a6e8

Please sign in to comment.