Skip to content

Commit

Permalink
feat: add extend and cancel subscription
Browse files Browse the repository at this point in the history
  • Loading branch information
0xChin committed Oct 1, 2024
1 parent 009245b commit da25de8
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 49 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"coverage": "forge coverage --report summary --report lcov --match-path 'test/unit/*'",
"deploy:arbitrum-sepolia": "bash -c 'source .env && forge script Deploy -vvvvv --rpc-url $ARBITRUM_SEPOLIA_RPC --broadcast --chain arbitrum-sepolia --private-key $ARBITRUM_SEPOLIA_DEPLOYER_PK --verify --verifier blockscout --verifier-url https://arbitrum-sepolia.blockscout.com/api/'",
"deploy:mainnet": "bash -c 'source .env && forge script Deploy -vvvvv --rpc-url $MAINNET_RPC --broadcast --chain mainnet --private-key $MAINNET_DEPLOYER_PK'",
"deploy:optimism": "bash -c 'source .env && forge script Deploy -vvvvv --rpc-url $OPTIMISM_RPC --broadcast --chain optimism --private-key $OPTIMISM_DEPLOYER_PK --verify'",
"deploy:optimism-sepolia": "bash -c 'source .env && forge script Deploy -vvvvv --rpc-url $OPTIMISM_SEPOLIA_RPC --broadcast --chain optimism-sepolia --private-key $OPTIMISM_SEPOLIA_DEPLOYER_PK --verify --verifier blockscout --verifier-url https://optimism-sepolia.blockscout.com/api/'",
"deploy:sepolia": "bash -c 'source .env && forge script Deploy -vvvvv --rpc-url $SEPOLIA_RPC --broadcast --chain sepolia --private-key $SEPOLIA_DEPLOYER_PK'",
"deploy:v-optimism": "bash -c 'source .env && forge script Deploy -vvvvv --rpc-url $VIRTUAL_OPTIMISM_RPC --broadcast --private-key $OPTIMISM_DEPLOYER_PK --verify'",
Expand Down
16 changes: 7 additions & 9 deletions script/Deploy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ contract Deploy is Script {
address[] memory _tokens = new address[](1);
_tokens[0] = address(0x5fd84259d66Cd46123540766Be93DFE6D43130D7);

address[] memory _tokensOptimism = new address[](2);
_tokensOptimism[0] = address(0x94b008aA00579c1307B0EF2c499aD98a8ce58e58);
_tokensOptimism[1] = address(0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1);

address[] memory _tokensOptimismSepolia = new address[](2);
_tokensOptimismSepolia[0] = address(0x7F5c764cBc14f9669B88837ca1490cCa17c31607);
_tokensOptimismSepolia[1] = address(0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1);
Expand All @@ -35,6 +39,9 @@ contract Deploy is Script {
// Mainnet
_deploymentParams[1] = DeploymentParams(_tokens, IPool(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2), 100);

// Optimism
_deploymentParams[10] = DeploymentParams(_tokensOptimism, IPool(0x794a61358D6845594F94dc1DB02A252b5b4814aD), 100);

// Optimism Sepolia
_deploymentParams[11_155_420] = DeploymentParams(_tokens, IPool(0xb50201558B00496A145fE76f7424749556E326D8), 100);

Expand All @@ -52,15 +59,6 @@ contract Deploy is Script {

vm.startBroadcast();
Grateful _grateful = new Grateful(_params.tokens, _params.aavePool, _params.initialFee);
AaveV3Vault _vault = new AaveV3Vault(
ERC20(_params.tokens[0]),
ERC20(0x460b97BD498E1157530AEb3086301d5225b91216),
_params.aavePool,
address(0),
IRewardsController(0x3A203B14CF8749a1e3b7314c6c49004B77Ee667A),
address(_grateful)
);
_grateful.addVault(_params.tokens[0], address(_vault));

// Deploy TestToken
TestToken _testToken = new TestToken("Test Token", "TEST", 18);
Expand Down
77 changes: 56 additions & 21 deletions src/contracts/Grateful.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ contract Grateful is IGrateful, Ownable2Step {
MODIFIERS
//////////////////////////////////////////////////////////////*/

modifier onlyWhenTokenWhitelisted(address _token) {
modifier onlyWhenTokenWhitelisted(
address _token
) {
if (!tokensWhitelisted[_token]) {
revert Grateful_TokenNotWhitelisted();
}
Expand Down Expand Up @@ -93,7 +95,9 @@ contract Grateful is IGrateful, Ownable2Step {
}

/// @inheritdoc IGrateful
function applyFee(uint256 amount) public view returns (uint256) {
function applyFee(
uint256 amount
) public view returns (uint256) {
uint256 feeAmount = (amount * fee) / 10_000;
return amount - feeAmount;
}
Expand All @@ -103,7 +107,9 @@ contract Grateful is IGrateful, Ownable2Step {
//////////////////////////////////////////////////////////////*/

/// @inheritdoc IGrateful
function addToken(address _token) external onlyOwner {
function addToken(

Check warning on line 110 in src/contracts/Grateful.sol

View workflow job for this annotation

GitHub Actions / Lint Commit Messages

Function order is incorrect, external function can not go after public view function (line 98)
address _token
) external onlyOwner {
tokensWhitelisted[_token] = true;
IERC20(_token).approve(address(aavePool), type(uint256).max);
}
Expand Down Expand Up @@ -159,6 +165,7 @@ contract Grateful is IGrateful, Ownable2Step {
address _token,
address _receiver,
uint256 _amount,
uint256 _subscriptionPlanId,
uint40 _interval,
uint16 _paymentsAmount,
address[] memory _recipients,
Expand All @@ -175,6 +182,7 @@ contract Grateful is IGrateful, Ownable2Step {
subscription.token = _token;
subscription.sender = msg.sender;
subscription.amount = _amount;
subscription.subscriptionPlanId = _subscriptionPlanId;
subscription.receiver = _receiver;
subscription.interval = _interval;
subscription.paymentsAmount = _paymentsAmount - 1;
Expand All @@ -185,6 +193,8 @@ contract Grateful is IGrateful, Ownable2Step {
// Precompute paymentId
uint256 paymentId = calculateId(msg.sender, _receiver, _token, _amount);

emit SubscriptionCreated(subscriptionId, msg.sender, _receiver, _amount, _subscriptionPlanId);

// Call _processPayment
_processPayment(msg.sender, _receiver, _token, _amount, paymentId, subscriptionId, _recipients, _percentages);
}
Expand All @@ -194,14 +204,19 @@ contract Grateful is IGrateful, Ownable2Step {
address _token,
address _receiver,
uint256 _amount,
uint256 _subscriptionPlanId,
uint40 _interval,
uint16 _paymentsAmount
) external onlyWhenTokenWhitelisted(_token) returns (uint256 subscriptionId) {
return subscribe(_token, _receiver, _amount, _interval, _paymentsAmount, new address[](0), new uint256[](0));
return subscribe(
_token, _receiver, _amount, _subscriptionPlanId, _interval, _paymentsAmount, new address[](0), new uint256[](0)
);
}

/// @inheritdoc IGrateful
function processSubscription(uint256 subscriptionId) external {
function processSubscription(
uint256 subscriptionId
) external {
Subscription storage subscription = subscriptions[subscriptionId];

if (subscription.amount == 0) {
Expand All @@ -228,6 +243,36 @@ contract Grateful is IGrateful, Ownable2Step {
subscription.paymentsAmount--;
}

/// @inheritdoc IGrateful
function extendSubscription(uint256 subscriptionId, uint16 additionalPayments) external {
Subscription storage subscription = subscriptions[subscriptionId];

if (subscription.amount == 0) {
revert Grateful_SubscriptionDoesNotExist();
}
if (subscription.sender != msg.sender) {
revert Grateful_OnlySenderCanExtendSubscription();
}

subscription.paymentsAmount += additionalPayments;
}

/// @inheritdoc IGrateful
function cancelSubscription(
uint256 subscriptionId
) external {
Subscription storage subscription = subscriptions[subscriptionId];

if (subscription.amount == 0) {
revert Grateful_SubscriptionDoesNotExist();
}
if (subscription.sender != msg.sender && subscription.receiver != msg.sender) {
revert Grateful_OnlySenderOrReceiverCanCancelSubscription();
}

delete subscriptions[subscriptionId];
}

/// @inheritdoc IGrateful
function createOneTimePayment(
address _merchant,
Expand Down Expand Up @@ -324,7 +369,9 @@ contract Grateful is IGrateful, Ownable2Step {
}

/// @inheritdoc IGrateful
function withdraw(address _token) external onlyWhenTokenWhitelisted(_token) {
function withdraw(
address _token
) external onlyWhenTokenWhitelisted(_token) {
AaveV3ERC4626 vault = vaults[_token];
if (address(vault) == address(0)) {
revert Grateful_VaultNotSet();
Expand All @@ -334,27 +381,15 @@ contract Grateful is IGrateful, Ownable2Step {
vault.redeem(_shares, msg.sender, address(this));
}

/// @inheritdoc IGrateful
function cancelSubscription(uint256 subscriptionId) external {
Subscription storage subscription = subscriptions[subscriptionId];

if (subscription.amount == 0) {
revert Grateful_SubscriptionDoesNotExist();
}
if (subscription.sender != msg.sender) {
revert Grateful_OnlySenderCanCancelSubscription();
}

delete subscriptions[subscriptionId];
}

/// @inheritdoc IGrateful
function switchYieldingFunds() external {
yieldingFunds[msg.sender] = !yieldingFunds[msg.sender];
}

/// @inheritdoc IGrateful
function setFee(uint256 _newFee) external onlyOwner {
function setFee(
uint256 _newFee
) external onlyOwner {
fee = _newFee;
}

Expand Down
66 changes: 49 additions & 17 deletions src/interfaces/IGrateful.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface IGrateful {
address token;
address sender;
uint256 amount;
uint256 subscriptionPlanId;
address receiver;
uint40 interval;
uint16 paymentsAmount;
Expand Down Expand Up @@ -66,6 +67,14 @@ interface IGrateful {
*/
event OneTimePaymentCreated(address indexed merchant, address indexed token, uint256 amount);

event SubscriptionCreated(
uint256 indexed subscriptionId,
address indexed sender,
address indexed receiver,
uint256 amount,
uint256 subscriptionPlanId
);

/*///////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -97,8 +106,11 @@ interface IGrateful {
/// @notice Thrown when the one-time payment is not found.
error Grateful_OneTimeNotFound();

/// @notice Thrown when only the sender can cancel the subscription.
error Grateful_OnlySenderCanCancelSubscription();
/// @notice Thrown when only the sender or receiver can cancel the subscription.
error Grateful_OnlySenderOrReceiverCanCancelSubscription();

/// @notice Thrown when only the sender can extend subscription.
error Grateful_OnlySenderCanExtendSubscription();

/*///////////////////////////////////////////////////////////////
VARIABLES
Expand All @@ -110,17 +122,23 @@ interface IGrateful {
/// @notice Checks if a token is whitelisted.
/// @param _token Address of the token.
/// @return True if the token is whitelisted, false otherwise.
function tokensWhitelisted(address _token) external view returns (bool);
function tokensWhitelisted(
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);
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.
function vaults(address _token) external view returns (AaveV3ERC4626);
function vaults(
address _token
) external view returns (AaveV3ERC4626);

/// @notice Returns the amount of shares for a merchant.
/// @param _merchant Address of the merchant.
Expand All @@ -131,7 +149,9 @@ interface IGrateful {
/// @notice Checks if an address is a registered one-time payment.
/// @param _address Address to check.
/// @return True if it's a registered one-time payment, false otherwise.
function oneTimePayments(address _address) external view returns (bool);
function oneTimePayments(
address _address
) external view returns (bool);

/// @notice Returns the total number of subscriptions.
/// @return Number of subscriptions.
Expand All @@ -149,7 +169,9 @@ interface IGrateful {
* @notice Adds a token to the whitelist.
* @param _token Address of the token to be added.
*/
function addToken(address _token) external;
function addToken(

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

View workflow job for this annotation

GitHub Actions / Lint Commit Messages

Function order is incorrect, external function can not go after external view function (line 162)
address _token
) external;

/**
* @notice Adds a vault for a specific token.
Expand Down Expand Up @@ -200,6 +222,7 @@ interface IGrateful {
address _token,
address _receiver,
uint256 _amount,
uint256 _subscriptionPlanId,
uint40 _interval,
uint16 _paymentsAmount,
address[] calldata _recipients,
Expand All @@ -219,15 +242,24 @@ interface IGrateful {
address _token,
address _receiver,
uint256 _amount,
uint256 _subscriptionPlanId,
uint40 _interval,
uint16 _paymentsAmount
) external returns (uint256 subscriptionId);

function cancelSubscription(
uint256 subscriptionId
) external;

function extendSubscription(uint256 subscriptionId, uint16 additionalPayments) external;

/**
* @notice Processes a subscription payment.
* @param subscriptionId ID of the subscription.
*/
function processSubscription(uint256 subscriptionId) external;
function processSubscription(
uint256 subscriptionId
) external;

/**
* @notice Creates a one-time payment.
Expand Down Expand Up @@ -338,13 +370,9 @@ interface IGrateful {
* @notice Withdraws funds from the vault.
* @param _token Address of the token being withdrawn.
*/
function withdraw(address _token) external;

/**
* @notice Cancels a subscription.
* @param subscriptionId ID of the subscription to be cancelled.
*/
function cancelSubscription(uint256 subscriptionId) external;
function withdraw(
address _token
) external;

/**
* @notice Toggles the merchant's preference to yield funds.
Expand All @@ -371,11 +399,15 @@ interface IGrateful {
* @param amount Amount before fee.
* @return amountWithFee Amount after fee is applied.
*/
function applyFee(uint256 amount) external view returns (uint256 amountWithFee);
function applyFee(
uint256 amount
) external view returns (uint256 amountWithFee);

/**
* @notice Sets a new fee.
* @param _newFee New fee to be applied (in basis points, 10000 = 100%).
*/
function setFee(uint256 _newFee) external;
function setFee(
uint256 _newFee
) external;
}
Loading

0 comments on commit da25de8

Please sign in to comment.