Skip to content

Commit

Permalink
test: add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
0xChin committed Dec 2, 2024
1 parent ef629bc commit c52b792
Show file tree
Hide file tree
Showing 7 changed files with 288 additions and 30 deletions.
44 changes: 44 additions & 0 deletions test/aave-v3/mocks/PoolMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;

import {ERC20} from "solmate/tokens/ERC20.sol";

import {ERC20Mock} from "../../mocks/ERC20Mock.sol";
import {IPool} from "yield-daddy/aave-v3/AaveV3ERC4626.sol";

contract PoolMock is IPool {
mapping(address => address) internal reserveAToken;

Check warning on line 10 in test/aave-v3/mocks/PoolMock.sol

View workflow job for this annotation

GitHub Actions / Lint Commit Messages

'reserveAToken' should start with _

function setReserveAToken(address _reserve, address _aTokenAddress) external {
reserveAToken[_reserve] = _aTokenAddress;
}

function supply(address asset, uint256 amount, address onBehalfOf, uint16) external override {
// Transfer asset
ERC20 token = ERC20(asset);
token.transferFrom(msg.sender, address(this), amount);

// Mint aTokens
address aTokenAddress = reserveAToken[asset];
ERC20Mock aToken = ERC20Mock(aTokenAddress);
aToken.mint(onBehalfOf, amount);
}

function withdraw(address asset, uint256 amount, address to) external override returns (uint256) {
// Burn aTokens
address aTokenAddress = reserveAToken[asset];
ERC20Mock aToken = ERC20Mock(aTokenAddress);
aToken.burn(msg.sender, amount);

// Transfer asset
ERC20 token = ERC20(asset);
token.transfer(to, amount);
return amount;
}

function getReserveData(
address asset
) external view override returns (IPool.ReserveData memory data) {
data.aTokenAddress = reserveAToken[asset];
}
}
29 changes: 29 additions & 0 deletions test/aave-v3/mocks/RewardsControllerMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;

import {ERC20Mock} from "../../mocks/ERC20Mock.sol";
import {IRewardsController} from "yield-daddy/aave-v3/AaveV3ERC4626.sol";

contract RewardsControllerMock is IRewardsController {
uint256 public constant CLAIM_AMOUNT = 10 ** 18;
ERC20Mock public aave;

constructor(
address _aave
) {
aave = ERC20Mock(_aave);
}

function claimAllRewards(
address[] calldata,
address to
) external override returns (address[] memory rewardsList, uint256[] memory claimedAmounts) {
aave.mint(to, CLAIM_AMOUNT);

rewardsList = new address[](1);
rewardsList[0] = address(aave);

claimedAmounts = new uint256[](1);
claimedAmounts[0] = CLAIM_AMOUNT;
}
}
4 changes: 0 additions & 4 deletions test/invariants/PROPERTIES.md

This file was deleted.

14 changes: 14 additions & 0 deletions test/mocks/ERC20Mock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;

import {ERC20} from "solmate/tokens/ERC20.sol";

contract ERC20Mock is ERC20("MockERC20", "MOCK", 18) {
function mint(address to, uint256 amount) public {
_mint(to, amount);
}

function burn(address from, uint256 amount) public {
_burn(from, amount);
}
}
201 changes: 201 additions & 0 deletions test/unit/Grateful.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

// Forge standard library imports for testing
import {Test} from "forge-std/Test.sol";
import {console} from "forge-std/console.sol";

Check warning on line 6 in test/unit/Grateful.t.sol

View workflow job for this annotation

GitHub Actions / Lint Commit Messages

Variable "console" is unused

// OpenZeppelin imports for ERC20 and contract utilities
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

// Solmate ERC20 import for vault creation
import {ERC20} from "solmate/tokens/ERC20.sol";

// Grateful contract and related interfaces
import {Grateful, IGrateful} from "contracts/Grateful.sol";
import {AaveV3Vault} from "contracts/vaults/AaveV3Vault.sol";

// Mock contracts for testing
import {PoolMock} from "test/aave-v3/mocks/PoolMock.sol";
import {RewardsControllerMock} from "test/aave-v3/mocks/RewardsControllerMock.sol";
import {ERC20Mock} from "test/mocks/ERC20Mock.sol";

// Aave V3 interfaces
import {IPool, IRewardsController} from "yield-daddy/aave-v3/AaveV3ERC4626.sol";

contract UnitGrateful is Test {
using SafeERC20 for IERC20;

/*//////////////////////////////////////////////////////////////
CONTRACTS & VARIABLES
//////////////////////////////////////////////////////////////*/

// Main contracts
Grateful public grateful;
ERC20Mock public token;
ERC20Mock public aToken;
ERC20Mock public aave;
PoolMock public aavePool;
AaveV3Vault public aaveVault;
RewardsControllerMock public rewardsController;

// Addresses
address public owner = makeAddr("owner");
address public merchant = makeAddr("merchant");
address public user = makeAddr("user");

// Token and fee parameters
address[] public tokens;
uint256 public initialFee = 0.01 ether; // 1%
uint256 public initialPerformanceFee = 0.05 ether; // 5%
uint256 public tokenInitialSupply = 1_000_000 * 1e18; // 1 million tokens

function setUp() public {
/*//////////////////////////////////////////////////////////////
DEPLOY MOCK TOKEN
//////////////////////////////////////////////////////////////*/

// Deploy mock ERC20 tokens
token = new ERC20Mock();
aToken = new ERC20Mock();
aave = new ERC20Mock();

// Label the token addresses for easier debugging
vm.label(address(token), "Mock Token");
vm.label(address(aToken), "Mock AToken");
vm.label(address(aave), "Mock Aave");

/*//////////////////////////////////////////////////////////////
DEPLOY AAVE MOCK CONTRACTS
//////////////////////////////////////////////////////////////*/

// Deploy a mock Aave V3 pool
aavePool = new PoolMock();

// Deploy a mock Rewards Controller with its own Aave token
rewardsController = new RewardsControllerMock(address(aave));

// Label the Aave pool and rewards controller addresses
vm.label(address(aavePool), "Aave Pool Mock");
vm.label(address(rewardsController), "Rewards Controller Mock");

/*//////////////////////////////////////////////////////////////
DEPLOY GRATEFUL CONTRACT
//////////////////////////////////////////////////////////////*/

// Prepare the tokens array with the mock token address
tokens = new address[](1);
tokens[0] = address(token);

// Deploy the Grateful contract
vm.startPrank(owner);
grateful = new Grateful(tokens, IPool(address(aavePool)), initialFee, initialPerformanceFee, owner);

// Label the Grateful contract address
vm.label(address(grateful), "Grateful");

/*//////////////////////////////////////////////////////////////
DEPLOY AAVE V3 VAULT
//////////////////////////////////////////////////////////////*/

// Set reserve AToken in the mock pool
aavePool.setReserveAToken(address(token), address(aToken));

// Deploy the AaveV3Vault with the token, mock Aave pool, and rewards controller
aaveVault = new AaveV3Vault(
ERC20(address(token)),
ERC20(address(aToken)),
IPool(address(aavePool)),
owner,
IRewardsController(address(rewardsController)),
address(grateful)
);

// Label the AaveV3Vault address
vm.label(address(aaveVault), "AaveV3Vault");

/*//////////////////////////////////////////////////////////////
SETUP GRATEFUL CONTRACT
//////////////////////////////////////////////////////////////*/

// Add the AaveV3Vault to the Grateful contract
grateful.addVault(address(token), address(aaveVault));

// Stop acting as owner
vm.stopPrank();
}

/*//////////////////////////////////////////////////////////////
TEST FUNCTIONS
//////////////////////////////////////////////////////////////*/

function test_ConstructorWhenPassingValidArgs() public {
// Deploy the Grateful contract
grateful = new Grateful(tokens, IPool(address(aavePool)), initialFee, initialPerformanceFee, owner);

// Check that the Grateful contract is deployed correctly
assertEq(grateful.owner(), owner);
assertEq(address(grateful.aavePool()), address(aavePool));
assertEq(grateful.fee(), initialFee);
assertEq(grateful.performanceFee(), initialPerformanceFee);
assertEq(grateful.tokensWhitelisted(address(token)), true);
}

function test_ConstructorWhenPassingInvalidPoolAddress() public {
vm.expectRevert(abi.encodeWithSelector(IGrateful.Grateful_InvalidAddress.selector));
grateful = new Grateful(tokens, IPool(address(0)), initialFee, initialPerformanceFee, owner);
}

function test_ConstructorWhenPassingInvalidMaxFee(
uint256 invalidFee
) public {
vm.assume(invalidFee > grateful.MAX_FEE());
vm.expectRevert(abi.encodeWithSelector(IGrateful.Grateful_FeeRateTooHigh.selector));
grateful = new Grateful(tokens, IPool(address(aavePool)), invalidFee, initialPerformanceFee, owner);
}

function test_ConstructorWhenPassingInvalidMaxPerformanceFee(
uint256 invalidPerformanceFee
) public {
vm.assume(invalidPerformanceFee > grateful.MAX_PERFORMANCE_FEE());
vm.expectRevert(abi.encodeWithSelector(IGrateful.Grateful_FeeRateTooHigh.selector));
grateful = new Grateful(tokens, IPool(address(aavePool)), initialFee, invalidPerformanceFee, owner);
}

function test_AddAndRemoveToken() public {
address tokenToAdd = address(new ERC20Mock());
vm.prank(owner);
grateful.addToken(tokenToAdd);
assertEq(grateful.tokensWhitelisted(tokenToAdd), true);

vm.prank(owner);
grateful.removeToken(tokenToAdd);
assertEq(grateful.tokensWhitelisted(tokenToAdd), false);
}

function test_AddAndRemoveVault() public {
address newToken = address(new ERC20Mock());
AaveV3Vault newVault = new AaveV3Vault(
ERC20(newToken),
ERC20(address(aToken)),
IPool(address(aavePool)),
owner,
IRewardsController(address(rewardsController)),
address(grateful)
);

vm.prank(owner);
grateful.addToken(newToken);

vm.prank(owner);
grateful.addVault(newToken, address(newVault));

assertEq(address(grateful.vaults(newToken)), address(newVault));

vm.prank(owner);
grateful.removeVault(address(newToken));

assertEq(address(grateful.vaults(address(newToken))), address(0));
}
}
1 change: 0 additions & 1 deletion test/unit/Greeter.t.sol

This file was deleted.

25 changes: 0 additions & 25 deletions test/unit/Greeter.tree

This file was deleted.

0 comments on commit c52b792

Please sign in to comment.