diff --git a/script/Deploy.sol b/script/Deploy.sol index 9a6cab8..0094792 100644 --- a/script/Deploy.sol +++ b/script/Deploy.sol @@ -34,7 +34,7 @@ contract Deploy is Script { Grateful _grateful = new Grateful(_params.tokens, _params.aavePool); AaveV3Vault _vault = new AaveV3Vault( ERC20(_params.tokens[0]), - ERC20(0x6d80113e533a2C0fe82EaBD35f1875DcEA89Ea97), + ERC20(0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c), _params.aavePool, address(0), IRewardsController(0x8164Cc65827dcFe994AB23944CBC90e0aa80bFcb), diff --git a/src/contracts/Grateful.sol b/src/contracts/Grateful.sol index e4d43a2..c661fe5 100644 --- a/src/contracts/Grateful.sol +++ b/src/contracts/Grateful.sol @@ -67,16 +67,18 @@ contract Grateful is IGrateful, Ownable2Step { address _token, address _receiver, uint256 _amount, - uint256 _interval + uint40 _interval, + uint16 _paymentsAmount ) external onlyWhenTokenWhitelisted(_token) returns (uint256 subscriptionId) { subscriptionId = subscriptionCount++; subscriptions[subscriptionId] = Subscription({ token: _token, sender: msg.sender, - receiver: _receiver, amount: _amount, + receiver: _receiver, interval: _interval, - lastPaymentTime: block.timestamp + paymentsAmount: _paymentsAmount - 1, // Subtract 1 because the first payment is already processed + lastPaymentTime: uint40(block.timestamp) }); _processPayment(msg.sender, _receiver, _token, _amount); @@ -94,9 +96,13 @@ contract Grateful is IGrateful, Ownable2Step { ) { revert Grateful_TooEarlyForNextPayment(); } + if (subscription.paymentsAmount == 0) { + revert Grateful_PaymentsAmountReached(); + } _processPayment(subscription.sender, subscription.receiver, subscription.token, subscription.amount); - subscription.lastPaymentTime = block.timestamp; + subscription.lastPaymentTime = uint40(block.timestamp); + subscription.paymentsAmount--; } // @inheritdoc IGrateful @@ -143,5 +149,7 @@ contract Grateful is IGrateful, Ownable2Step { revert Grateful_TransferFailed(); } } + + emit PaymentProcessed(_sender, _merchant, _token, _amount, yieldingFunds[_merchant]); } } diff --git a/src/interfaces/IGrateful.sol b/src/interfaces/IGrateful.sol index 18724f7..116457a 100644 --- a/src/interfaces/IGrateful.sol +++ b/src/interfaces/IGrateful.sol @@ -16,16 +16,27 @@ interface IGrateful { struct Subscription { address token; address sender; - address receiver; uint256 amount; - uint256 interval; - uint256 lastPaymentTime; + address receiver; + uint40 interval; + uint40 lastPaymentTime; + uint16 paymentsAmount; } /*/////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ + /** + * @notice Emitted when a payment is processed + * @param sender Address of the sender + * @param merchant Address of the merchant + * @param token Address of the token + * @param amount Amount of the token + * @param yielded Indicates if the payment was yielded + */ + event PaymentProcessed(address sender, address merchant, address token, uint256 amount, bool yielded); + /*/////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////*/ @@ -65,6 +76,11 @@ interface IGrateful { */ error Grateful_OnlySenderCanCancelSubscription(); + /** + * @notice Throws if the payments amount has been reached + */ + error Grateful_PaymentsAmountReached(); + /*/////////////////////////////////////////////////////////////// VARIABLES //////////////////////////////////////////////////////////////*/ @@ -135,7 +151,8 @@ interface IGrateful { address _token, address _receiver, uint256 _amount, - uint256 _interval + uint40 _interval, + uint16 _paymentsAmount ) external returns (uint256 subscriptionId); /** diff --git a/test/integration/Grateful.t.sol b/test/integration/Grateful.t.sol index bd25170..d2bb82a 100644 --- a/test/integration/Grateful.t.sol +++ b/test/integration/Grateful.t.sol @@ -5,12 +5,12 @@ import {IGrateful, IntegrationBase} from "test/integration/IntegrationBase.sol"; contract IntegrationGreeter is IntegrationBase { function test_Payment() public { - vm.startPrank(_daiWhale); - _dai.approve(address(_grateful), _amount); - _grateful.pay(_merchant, address(_dai), _amount); + vm.startPrank(_usdcWhale); + _usdc.approve(address(_grateful), _amount); + _grateful.pay(_merchant, address(_usdc), _amount); vm.stopPrank(); - assertEq(_dai.balanceOf(_merchant), _amount); + assertEq(_usdc.balanceOf(_merchant), _amount); } function test_PaymentYieldingFunds() public { @@ -21,27 +21,27 @@ contract IntegrationGreeter is IntegrationBase { assertEq(_grateful.yieldingFunds(_merchant), true); - vm.startPrank(_daiWhale); - _dai.approve(address(_grateful), _amount); - _grateful.pay(_merchant, address(_dai), _amount); + vm.startPrank(_usdcWhale); + _usdc.approve(address(_grateful), _amount); + _grateful.pay(_merchant, address(_usdc), _amount); vm.stopPrank(); vm.warp(block.timestamp + 60 days); vm.prank(_merchant); - _grateful.withdraw(address(_dai)); + _grateful.withdraw(address(_usdc)); - assertGt(_dai.balanceOf(_merchant), _amount); + assertGt(_usdc.balanceOf(_merchant), _amount); } function test_Subscription() public { - vm.startPrank(_daiWhale); - _dai.approve(address(_grateful), _amount * 2); - uint256 subscriptionId = _grateful.subscribe(address(_dai), _merchant, _amount, 30 days); + vm.startPrank(_usdcWhale); + _usdc.approve(address(_grateful), _amount * 2); + uint256 subscriptionId = _grateful.subscribe(address(_usdc), _merchant, _amount, 30 days, 2); vm.stopPrank(); // When subscription is created, a initial payment is made - assertEq(_dai.balanceOf(_merchant), _amount); + assertEq(_usdc.balanceOf(_merchant), _amount); // Shouldn't be able to process the subscription before 30 days have passed vm.expectRevert(IGrateful.Grateful_TooEarlyForNextPayment.selector); @@ -52,6 +52,14 @@ contract IntegrationGreeter is IntegrationBase { _grateful.processSubscription(subscriptionId); - assertEq(_dai.balanceOf(_merchant), _amount * 2); + assertEq(_usdc.balanceOf(_merchant), _amount * 2); + + // Should revert if the payments amount has been reached + + // Fast forward 30 days + vm.warp(block.timestamp + 30 days); + + vm.expectRevert(IGrateful.Grateful_PaymentsAmountReached.selector); + _grateful.processSubscription(subscriptionId); } } diff --git a/test/integration/IntegrationBase.sol b/test/integration/IntegrationBase.sol index b25141f..d927c07 100644 --- a/test/integration/IntegrationBase.sol +++ b/test/integration/IntegrationBase.sol @@ -18,33 +18,33 @@ contract IntegrationBase is Test { address internal _user = makeAddr("user"); address internal _merchant = makeAddr("merchant"); address internal _owner = makeAddr("owner"); - address internal _daiWhale = 0xbf702ea18BB1AB2A710394993a576eC61476cCf3; + address internal _usdcWhale = 0x555d73f2002A457211d690313f942B065eAD1FFF; address[] internal _tokens; - IERC20 internal _dai = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); - address _aDai = 0x018008bfb33d285247A21d44E50697654f754e63; + IERC20 internal _usdc = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); + address _aUsdc = 0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c; address _rewardsController = 0x8164Cc65827dcFe994AB23944CBC90e0aa80bFcb; IPool internal _aavePool = IPool(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2); IGrateful internal _grateful; AaveV3Vault internal _vault; - uint256 internal _amount = 10 * 10 ** 18; // 10 DAI + uint256 internal _amount = 10 * 10 ** 6; // 10 DAI function setUp() public { vm.startPrank(_owner); vm.createSelectFork(vm.rpcUrl("mainnet"), _FORK_BLOCK); vm.label(address(_vault), "Vault"); _tokens = new address[](1); - _tokens[0] = address(_dai); + _tokens[0] = address(_usdc); _grateful = new Grateful(_tokens, _aavePool); _vault = new AaveV3Vault( - ERC20(address(_dai)), - ERC20(_aDai), + ERC20(address(_usdc)), + ERC20(_aUsdc), _aavePool, address(0), IRewardsController(_rewardsController), address(_grateful) ); vm.label(address(_grateful), "Grateful"); - _grateful.addVault(address(_dai), address(_vault)); + _grateful.addVault(address(_usdc), address(_vault)); vm.stopPrank(); } }