Skip to content

Commit

Permalink
Add AIP for V4 AL Service Provider Proposal (bgd-labs#357)
Browse files Browse the repository at this point in the history
  • Loading branch information
CheyenneAtapour authored Jun 26, 2024
1 parent ee5f182 commit e8167f7
Show file tree
Hide file tree
Showing 6 changed files with 543 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Raw diff

```json
{}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IProposalGenericExecutor} from 'aave-helpers/interfaces/IProposalGenericExecutor.sol';
import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol';

/**
* @title V4 AL Service Provider Proposal
* @author Aave Labs
* - Snapshot: https://snapshot.org/#/aave.eth/proposal/0x70dfd865b78c4c391e2b0729b907d152e6e8a0da683416d617d8f84782036349
* - Discussion: https://governance.aave.com/t/arfc-al-service-provider-proposal/17974
*/
contract AaveV3Ethereum_V4ALServiceProviderProposal_20240614 is IProposalGenericExecutor {
address public constant AAVE_LABS = 0x1c037b3C22240048807cC9d7111be5d455F640bd;

// 3 million GHO upfront
uint256 public constant GHO_UPFRONT_AMOUNT = 3_000_000 ether;

// 9 million GHO streamed over the year
uint256 public constant GHO_STREAM_AMOUNT = 9_000_000 ether;
uint256 public constant GHO_STREAM_DURATION = 365 days;

uint256 public constant ACTUAL_STREAM =
(GHO_STREAM_AMOUNT / GHO_STREAM_DURATION) * GHO_STREAM_DURATION;

function execute() external {
AaveV3Ethereum.COLLECTOR.createStream(
AAVE_LABS,
ACTUAL_STREAM,
AaveV3EthereumAssets.GHO_UNDERLYING,
block.timestamp,
block.timestamp + GHO_STREAM_DURATION
);

AaveV3Ethereum.COLLECTOR.transfer(
AaveV3EthereumAssets.GHO_UNDERLYING,
AAVE_LABS,
GHO_UPFRONT_AMOUNT
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol';

import 'forge-std/Test.sol';
import 'forge-std/StdUtils.sol';
import {ProtocolV3TestBase, ReserveConfig} from 'aave-helpers/ProtocolV3TestBase.sol';
import {AaveV3Ethereum_V4ALServiceProviderProposal_20240614} from './AaveV3Ethereum_V4ALServiceProviderProposal_20240614.sol';
import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';

/**
* @dev Test for AaveV3Ethereum_V4ALServiceProviderProposal_20240614
* command: FOUNDRY_PROFILE=mainnet forge test --match-path=src/20240614_AaveV3Ethereum_V4ALServiceProviderProposal/AaveV3Ethereum_V4ALServiceProviderProposal_20240614.t.sol -vv
*/
contract AaveV3Ethereum_V4ALServiceProviderProposal_20240614_Test is ProtocolV3TestBase {
AaveV3Ethereum_V4ALServiceProviderProposal_20240614 internal proposal;

address public constant AAVE_LABS = 0x1c037b3C22240048807cC9d7111be5d455F640bd;
uint256 public constant GHO_UPFRONT_AMOUNT = 3_000_000 ether;
uint256 public constant GHO_STREAM_AMOUNT = 9_000_000 ether;
uint256 public constant GHO_STREAM_DURATION = 365 days;
uint256 public constant ACTUAL_STREAM_AMOUNT_GHO =
(GHO_STREAM_AMOUNT / GHO_STREAM_DURATION) * GHO_STREAM_DURATION;

function setUp() public {
vm.createSelectFork(vm.rpcUrl('mainnet'), 20092863);
proposal = new AaveV3Ethereum_V4ALServiceProviderProposal_20240614();
}

/**
* @dev executes the generic test suite including e2e and config snapshots
*/
function test_defaultProposalExecution() public {
defaultTest(
'AaveV3Ethereum_V4ALServiceProviderProposal_20240614',
AaveV3Ethereum.POOL,
address(proposal)
);
}

function testProposalExecution() public {
uint256 nextCollectorStreamID = AaveV3Ethereum.COLLECTOR.getNextStreamId();
uint256 ALGHOBalanceBefore = IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(AAVE_LABS);
uint256 CollectorV3GHOBalanceBefore = IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(
address(AaveV3Ethereum.COLLECTOR)
);

executePayload(vm, address(proposal));

// Check balances directly after proposal execution (upfront payment distributed to Aave Labs)
assertEq(
IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(AAVE_LABS),
ALGHOBalanceBefore + GHO_UPFRONT_AMOUNT
);
assertEq(
IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(address(AaveV3Ethereum.COLLECTOR)),
CollectorV3GHOBalanceBefore - GHO_UPFRONT_AMOUNT
);

// Checking if the streams have been created properly
// Scoping to avoid "stack too deep" error
{
(
address senderGHO,
address recipientGHO,
uint256 depositGHO,
address tokenAddressGHO,
uint256 startTimeGHO,
uint256 stopTimeGHO,
uint256 remainingBalanceGHO,

) = AaveV3Ethereum.COLLECTOR.getStream(nextCollectorStreamID);

assertEq(senderGHO, address(AaveV3Ethereum.COLLECTOR));
assertEq(recipientGHO, AAVE_LABS);
assertEq(depositGHO, ACTUAL_STREAM_AMOUNT_GHO);
assertEq(tokenAddressGHO, AaveV3EthereumAssets.GHO_UNDERLYING);
assertEq(stopTimeGHO - startTimeGHO, GHO_STREAM_DURATION);
assertEq(remainingBalanceGHO, ACTUAL_STREAM_AMOUNT_GHO);
}

// Checking if Aave Labs can withdraw from the stream
vm.startPrank(AAVE_LABS);
vm.warp(block.timestamp + GHO_STREAM_DURATION);

// Currently Collector has less funds than stream amount
assertLe(
IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(address(AaveV3Ethereum.COLLECTOR)),
ACTUAL_STREAM_AMOUNT_GHO
);

// Partial withdrawal of Collector's remaining balance
AaveV3Ethereum.COLLECTOR.withdrawFromStream(
nextCollectorStreamID,
CollectorV3GHOBalanceBefore - GHO_UPFRONT_AMOUNT
);
uint256 nextALGHOBalance = IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(AAVE_LABS);

// Aave Labs received the entirety of Collector's balance
assertEq(ALGHOBalanceBefore, nextALGHOBalance - CollectorV3GHOBalanceBefore);

// Check Collector balance after stream withdrawal
uint256 CollectorV3GHOBalanceAfter = IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(
address(AaveV3Ethereum.COLLECTOR)
);

assertEq(CollectorV3GHOBalanceAfter, 0);

vm.stopPrank();
}

// Test giving V2 Collector more funds and full withdrawing
function testProposalExecutionPrankFunds() public {
uint256 nextCollectorStreamID = AaveV3Ethereum.COLLECTOR.getNextStreamId();
uint256 ALGHOBalanceBefore = IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(AAVE_LABS);

// Giving the Collector enough funds to cover the stream
deal(
AaveV3EthereumAssets.GHO_UNDERLYING,
address(AaveV3Ethereum.COLLECTOR),
GHO_UPFRONT_AMOUNT + GHO_STREAM_AMOUNT
);

uint256 CollectorV3GHOBalanceBefore = IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(
address(AaveV3Ethereum.COLLECTOR)
);

executePayload(vm, address(proposal));

// Check balances directly after proposal execution (upfront payment distributed to Aave Labs)
assertEq(
IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(AAVE_LABS),
ALGHOBalanceBefore + GHO_UPFRONT_AMOUNT
);
assertEq(
IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(address(AaveV3Ethereum.COLLECTOR)),
CollectorV3GHOBalanceBefore - GHO_UPFRONT_AMOUNT
);

// Checking if the streams have been created properly
// Scoping to avoid "stack too deep" error
{
(
address senderGHO,
address recipientGHO,
uint256 depositGHO,
address tokenAddressGHO,
uint256 startTimeGHO,
uint256 stopTimeGHO,
uint256 remainingBalanceGHO,

) = AaveV3Ethereum.COLLECTOR.getStream(nextCollectorStreamID);

assertEq(senderGHO, address(AaveV3Ethereum.COLLECTOR));
assertEq(recipientGHO, AAVE_LABS);
assertEq(depositGHO, ACTUAL_STREAM_AMOUNT_GHO);
assertEq(tokenAddressGHO, AaveV3EthereumAssets.GHO_UNDERLYING);
assertEq(stopTimeGHO - startTimeGHO, GHO_STREAM_DURATION);
assertEq(remainingBalanceGHO, ACTUAL_STREAM_AMOUNT_GHO);
}

// Checking if Aave Labs can withdraw from the stream
vm.startPrank(AAVE_LABS);
vm.warp(block.timestamp + GHO_STREAM_DURATION);

assertGe(
IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(address(AaveV3Ethereum.COLLECTOR)),
ACTUAL_STREAM_AMOUNT_GHO
);

AaveV3Ethereum.COLLECTOR.withdrawFromStream(nextCollectorStreamID, ACTUAL_STREAM_AMOUNT_GHO);
uint256 nextALGHOBalance = IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(AAVE_LABS);

assertEq(
ALGHOBalanceBefore,
nextALGHOBalance - (ACTUAL_STREAM_AMOUNT_GHO + GHO_UPFRONT_AMOUNT)
);

// Check Collector balance after stream withdrawal
uint256 CollectorV3GHOBalanceAfter = IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(
address(AaveV3Ethereum.COLLECTOR)
);

assertEq(
CollectorV3GHOBalanceAfter,
CollectorV3GHOBalanceBefore - (ACTUAL_STREAM_AMOUNT_GHO + GHO_UPFRONT_AMOUNT)
);

vm.stopPrank();
}

// Test showing V2 Collector currently doesn't have enough funds
function testProposalExecutionFailureFunding() public {
uint256 nextCollectorStreamID = AaveV3Ethereum.COLLECTOR.getNextStreamId();
uint256 ALGHOBalanceBefore = IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(AAVE_LABS);

uint256 CollectorV3GHOBalanceBefore = IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(
address(AaveV3Ethereum.COLLECTOR)
);

executePayload(vm, address(proposal));

// Check balances directly after proposal execution (upfront payment distributed to Aave Labs)
assertEq(
IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(AAVE_LABS),
ALGHOBalanceBefore + GHO_UPFRONT_AMOUNT
);
assertEq(
IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(address(AaveV3Ethereum.COLLECTOR)),
CollectorV3GHOBalanceBefore - GHO_UPFRONT_AMOUNT
);

// Checking if the streams have been created properly
// Scoping to avoid "stack too deep" error
{
(
address senderGHO,
address recipientGHO,
uint256 depositGHO,
address tokenAddressGHO,
uint256 startTimeGHO,
uint256 stopTimeGHO,
uint256 remainingBalanceGHO,

) = AaveV3Ethereum.COLLECTOR.getStream(nextCollectorStreamID);

assertEq(senderGHO, address(AaveV3Ethereum.COLLECTOR));
assertEq(recipientGHO, AAVE_LABS);
assertEq(depositGHO, ACTUAL_STREAM_AMOUNT_GHO);
assertEq(tokenAddressGHO, AaveV3EthereumAssets.GHO_UNDERLYING);
assertEq(stopTimeGHO - startTimeGHO, GHO_STREAM_DURATION);
assertEq(remainingBalanceGHO, ACTUAL_STREAM_AMOUNT_GHO);
}

// Checking if Aave Labs can withdraw from the stream
vm.startPrank(AAVE_LABS);
vm.warp(block.timestamp + GHO_STREAM_DURATION);

assertLe(
IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(address(AaveV3Ethereum.COLLECTOR)),
ACTUAL_STREAM_AMOUNT_GHO
);

vm.expectRevert(stdError.arithmeticError);
AaveV3Ethereum.COLLECTOR.withdrawFromStream(nextCollectorStreamID, ACTUAL_STREAM_AMOUNT_GHO);

vm.stopPrank();
}

function testProposalExecutionFuzzWithdrawalAmounts(
uint256 withdrawalAmount,
uint256 waitPeriod
) public {
waitPeriod = bound(waitPeriod, 1, GHO_STREAM_DURATION);
withdrawalAmount = bound(
withdrawalAmount,
1,
waitPeriod * (ACTUAL_STREAM_AMOUNT_GHO / GHO_STREAM_DURATION)
);

// Giving the Collector enough funds to cover the stream
deal(
AaveV3EthereumAssets.GHO_UNDERLYING,
address(AaveV3Ethereum.COLLECTOR),
GHO_UPFRONT_AMOUNT + GHO_STREAM_AMOUNT
);

assertGe(
IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(address(AaveV3Ethereum.COLLECTOR)),
ACTUAL_STREAM_AMOUNT_GHO
);

uint256 ALGHOBalanceBefore = IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(AAVE_LABS);
uint256 CollectorV3GHOBalanceBefore = IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(
address(AaveV3Ethereum.COLLECTOR)
);

uint256 nextCollectorStreamID = AaveV3Ethereum.COLLECTOR.getNextStreamId();
executePayload(vm, address(proposal));

// Check balances directly after proposal execution (upfront payment distributed to Aave Labs)
assertEq(
IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(AAVE_LABS),
ALGHOBalanceBefore + GHO_UPFRONT_AMOUNT
);
assertEq(
IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(address(AaveV3Ethereum.COLLECTOR)),
CollectorV3GHOBalanceBefore - GHO_UPFRONT_AMOUNT
);

vm.startPrank(AAVE_LABS);
vm.warp(block.timestamp + waitPeriod);

// Withdraw fuzzed amount from stream
AaveV3Ethereum.COLLECTOR.withdrawFromStream(nextCollectorStreamID, withdrawalAmount);

uint256 ALGHOBalanceAfter = IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(AAVE_LABS);
uint256 CollectorV3GHOBalanceAfter = IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(
address(AaveV3Ethereum.COLLECTOR)
);

assertEq(ALGHOBalanceAfter - ALGHOBalanceBefore, withdrawalAmount + GHO_UPFRONT_AMOUNT);
assertEq(
CollectorV3GHOBalanceAfter,
CollectorV3GHOBalanceBefore - (withdrawalAmount + GHO_UPFRONT_AMOUNT)
);

vm.stopPrank();
}
}
Loading

0 comments on commit e8167f7

Please sign in to comment.