mirror of
https://github.com/Instadapp/fluid-contracts-public.git
synced 2024-07-29 21:57:37 +00:00
383 lines
15 KiB
Solidity
383 lines
15 KiB
Solidity
|
//SPDX-License-Identifier: MIT
|
||
|
pragma solidity 0.8.21;
|
||
|
|
||
|
import "forge-std/Test.sol";
|
||
|
|
||
|
import { IFluidLiquidity } from "../../../../contracts/liquidity/interfaces/iLiquidity.sol";
|
||
|
|
||
|
import { FluidVaultT1 } from "../../../../contracts/protocols/vault/vaultT1/coreModule/main.sol";
|
||
|
import { IFluidVaultT1 } from "../../../../contracts/protocols/vault/interfaces/iVaultT1.sol";
|
||
|
import { FluidVaultT1Admin } from "../../../../contracts/protocols/vault/vaultT1/adminModule/main.sol";
|
||
|
import { FluidVaultRewards } from "../../../../contracts/protocols/vault/rewards/main.sol";
|
||
|
import { Events } from "../../../../contracts/protocols/vault/rewards/events.sol";
|
||
|
|
||
|
import { MockOracle } from "../../../../contracts/mocks/mockOracle.sol";
|
||
|
|
||
|
import { FluidLendingRewardsRateModel } from "../../../../contracts/protocols/lending/lendingRewardsRateModel/main.sol";
|
||
|
import { FluidLendingFactory } from "../../../../contracts/protocols/lending/lendingFactory/main.sol";
|
||
|
|
||
|
import { IFluidReserveContract } from "../../../../contracts/reserve/interfaces/iReserveContract.sol";
|
||
|
import { FluidReserveContract } from "../../../../contracts/reserve/main.sol";
|
||
|
import { FluidReserveContractProxy } from "../../../../contracts/reserve/proxy.sol";
|
||
|
|
||
|
import { ErrorTypes } from "../../../../contracts/protocols/vault/errorTypes.sol";
|
||
|
import { Error } from "../../../../contracts/protocols/vault/error.sol";
|
||
|
|
||
|
import { TestERC20 } from "../../testERC20.sol";
|
||
|
import { TestERC20Dec6 } from "../../testERC20Dec6.sol";
|
||
|
|
||
|
import { VaultFactoryBaseTest } from "../factory/vaultFactory.t.sol";
|
||
|
|
||
|
contract VaultRewardsTest is VaultFactoryBaseTest, Events {
|
||
|
FluidVaultT1 vaultOne;
|
||
|
MockOracle oracleOne;
|
||
|
|
||
|
TestERC20Dec6 supplyToken;
|
||
|
TestERC20 borrowToken;
|
||
|
|
||
|
IFluidLiquidity liquidityProxy;
|
||
|
|
||
|
FluidReserveContract reserveContractImpl;
|
||
|
FluidReserveContract reserveContract; //proxy
|
||
|
|
||
|
uint256 RATE_PRECISION = 10000;
|
||
|
uint256 rewardsMagnifierAtZero = 2 * RATE_PRECISION;
|
||
|
uint256 rewardsMagnifier1AtTVL = 1 * RATE_PRECISION;
|
||
|
uint256 startingTVL = 0;
|
||
|
|
||
|
FluidVaultRewards vaultRewards;
|
||
|
|
||
|
uint256 vaultRewardsDuration = 365 days;
|
||
|
uint256 vaultRewardsStartTime;
|
||
|
uint256 vaultRewardsEndTime;
|
||
|
|
||
|
address owner = address(0x123F);
|
||
|
address rebalancer = address(0x678A);
|
||
|
address authUser = address(0x987B);
|
||
|
|
||
|
address[] rebalancers;
|
||
|
address[] auths;
|
||
|
|
||
|
address vaultAuthContractAuthorizedUser = address(0x123D);
|
||
|
|
||
|
function setUp() public virtual override {
|
||
|
super.setUp();
|
||
|
|
||
|
rebalancers = new address[](1);
|
||
|
rebalancers[0] = owner;
|
||
|
|
||
|
auths = new address[](1);
|
||
|
auths[0] = authUser;
|
||
|
|
||
|
liquidityProxy = IFluidLiquidity(address(liquidity));
|
||
|
|
||
|
supplyToken = TestERC20Dec6(address(USDC));
|
||
|
borrowToken = TestERC20(address(DAI));
|
||
|
|
||
|
vaultOne = FluidVaultT1(_deployVaultTokens(address(supplyToken), address(borrowToken)));
|
||
|
|
||
|
// set default allowances for vault
|
||
|
_setUserAllowancesDefault(address(liquidity), address(admin), address(supplyToken), address(vaultOne));
|
||
|
_setUserAllowancesDefault(address(liquidity), address(admin), address(borrowToken), address(vaultOne));
|
||
|
|
||
|
// set default allowances for mockProtocol
|
||
|
_setUserAllowancesDefault(address(liquidity), admin, address(supplyToken), address(mockProtocol));
|
||
|
_setUserAllowancesDefault(address(liquidity), admin, address(borrowToken), address(mockProtocol));
|
||
|
|
||
|
_supply(mockProtocol, address(supplyToken), alice, 1e6 * 1e6);
|
||
|
_supply(mockProtocol, address(borrowToken), alice, 1e6 * 1e18);
|
||
|
|
||
|
_setApproval(USDC, address(vaultOne), alice);
|
||
|
_setApproval(USDC, address(vaultOne), bob);
|
||
|
_setApproval(DAI, address(vaultOne), bob);
|
||
|
_setApproval(DAI, address(vaultOne), alice);
|
||
|
|
||
|
reserveContractImpl = new FluidReserveContract(liquidityProxy);
|
||
|
reserveContract = FluidReserveContract(
|
||
|
payable(new FluidReserveContractProxy(address(reserveContractImpl), new bytes(0)))
|
||
|
);
|
||
|
reserveContract.initialize(auths, rebalancers, owner);
|
||
|
// reserve contract proxy admin is 'admin'
|
||
|
// reserve contract owner is 'owner'
|
||
|
vm.prank(authUser);
|
||
|
reserveContract.updateRebalancer(rebalancer, true);
|
||
|
|
||
|
vaultRewards = new FluidVaultRewards(
|
||
|
IFluidReserveContract(address(reserveContract)),
|
||
|
IFluidVaultT1(address(vaultOne)),
|
||
|
IFluidLiquidity(address(liquidity)),
|
||
|
2 ether, // distributing rewards amount
|
||
|
vaultRewardsDuration,
|
||
|
alice, // start initiator
|
||
|
address(supplyToken)
|
||
|
);
|
||
|
|
||
|
// revert if not initiator
|
||
|
vm.expectRevert(
|
||
|
abi.encodeWithSelector(Error.FluidVaultError.selector, ErrorTypes.VaultRewards__NotTheInitiator)
|
||
|
);
|
||
|
vm.prank(admin);
|
||
|
vaultRewards.start();
|
||
|
|
||
|
// reverts before started
|
||
|
vm.expectRevert(
|
||
|
abi.encodeWithSelector(Error.FluidVaultError.selector, ErrorTypes.VaultRewards__RewardsNotStartedOrEnded)
|
||
|
);
|
||
|
vaultRewards.calculateMagnifier();
|
||
|
|
||
|
// start rewards
|
||
|
vm.warp(block.timestamp + 100);
|
||
|
vaultRewardsStartTime = block.timestamp;
|
||
|
vaultRewardsEndTime = block.timestamp + vaultRewardsDuration;
|
||
|
vm.prank(alice);
|
||
|
vaultRewards.start();
|
||
|
|
||
|
assertEq(uint256(vaultRewards.startTime()), vaultRewardsStartTime);
|
||
|
assertEq(uint256(vaultRewards.endTime()), vaultRewardsEndTime);
|
||
|
|
||
|
// revert if already started
|
||
|
vm.expectRevert(
|
||
|
abi.encodeWithSelector(Error.FluidVaultError.selector, ErrorTypes.VaultRewards__AlreadyStarted)
|
||
|
);
|
||
|
vm.prank(alice);
|
||
|
vaultRewards.start();
|
||
|
|
||
|
vm.warp(block.timestamp + 1);
|
||
|
|
||
|
vm.prank(admin);
|
||
|
vaultFactory.setVaultAuth(address(vaultOne), address(vaultRewards), true);
|
||
|
|
||
|
_setApproval(USDC, address(vaultOne), alice);
|
||
|
_setApproval(DAI, address(vaultOne), alice);
|
||
|
|
||
|
oracleOne = MockOracle(_setDefaultVaultSettings(address(vaultOne)));
|
||
|
|
||
|
_setOracleOnePrice(1e39);
|
||
|
}
|
||
|
|
||
|
// ################### HELPERS #####################
|
||
|
|
||
|
function _deployVaultTokens(address supplyToken_, address borrowToken_) internal returns (address vault_) {
|
||
|
vm.prank(alice);
|
||
|
|
||
|
bytes memory vaultT1CreationCode = abi.encodeCall(vaultT1Deployer.vaultT1, (supplyToken_, borrowToken_));
|
||
|
vault_ = address(FluidVaultT1(vaultFactory.deployVault(address(vaultT1Deployer), vaultT1CreationCode)));
|
||
|
}
|
||
|
|
||
|
function _setDefaultVaultSettings(address vault_) internal returns (address oracle_) {
|
||
|
FluidVaultT1Admin vaultAdmin_ = FluidVaultT1Admin(vault_);
|
||
|
vm.prank(alice);
|
||
|
vaultAdmin_.updateCoreSettings(
|
||
|
10000, // supplyFactor_ => 100%
|
||
|
10000, // borrowFactor_ => 100%
|
||
|
8000, // collateralFactor_ => 80%
|
||
|
8100, // liquidationThreshold_ => 81%
|
||
|
9000, // liquidationMaxLimit_ => 90%
|
||
|
500, // withdrawGap_ => 5%
|
||
|
0, // liquidationPenalty_ => 0%
|
||
|
0 // borrowFee_ => 0.01%
|
||
|
);
|
||
|
|
||
|
oracle_ = address(new MockOracle());
|
||
|
vm.prank(alice);
|
||
|
vaultAdmin_.updateOracle(address(oracle_));
|
||
|
|
||
|
vm.prank(alice);
|
||
|
vaultAdmin_.updateRebalancer(address(alice));
|
||
|
}
|
||
|
|
||
|
function _setOracleOnePrice(uint price) internal {
|
||
|
oracleOne.setPrice(price);
|
||
|
}
|
||
|
|
||
|
// ################### TESTS #####################
|
||
|
|
||
|
function test_contructor_RevertIfReserveContractAddressZero() public {
|
||
|
vm.expectRevert(abi.encodeWithSelector(Error.FluidVaultError.selector, ErrorTypes.VaultRewards__AddressZero));
|
||
|
vaultRewards = new FluidVaultRewards(
|
||
|
IFluidReserveContract(address(0)),
|
||
|
IFluidVaultT1(address(vaultOne)),
|
||
|
IFluidLiquidity(address(liquidity)),
|
||
|
2 ether, // distributing rewards amount
|
||
|
vaultRewardsDuration,
|
||
|
alice, // start initiator
|
||
|
address(supplyToken)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
function test_contructor_RevertIfVaultAddressZero() public {
|
||
|
vm.expectRevert(abi.encodeWithSelector(Error.FluidVaultError.selector, ErrorTypes.VaultRewards__AddressZero));
|
||
|
vaultRewards = new FluidVaultRewards(
|
||
|
IFluidReserveContract(address(reserveContract)),
|
||
|
IFluidVaultT1(address(0)),
|
||
|
IFluidLiquidity(address(liquidity)),
|
||
|
2 ether, // distributing rewards amount
|
||
|
vaultRewardsDuration,
|
||
|
alice, // start initiator
|
||
|
address(supplyToken)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
function test_contructor_RevertIfInitiatorAddressZero() public {
|
||
|
vm.expectRevert(abi.encodeWithSelector(Error.FluidVaultError.selector, ErrorTypes.VaultRewards__AddressZero));
|
||
|
vaultRewards = new FluidVaultRewards(
|
||
|
IFluidReserveContract(address(reserveContract)),
|
||
|
IFluidVaultT1(address(vaultOne)),
|
||
|
IFluidLiquidity(address(liquidity)),
|
||
|
2 ether, // distributing rewards amount
|
||
|
vaultRewardsDuration,
|
||
|
address(0), // start initiator
|
||
|
address(supplyToken)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
function test_contructor_RevertIfLiquidityAddressZero() public {
|
||
|
vm.expectRevert(abi.encodeWithSelector(Error.FluidVaultError.selector, ErrorTypes.VaultRewards__AddressZero));
|
||
|
vaultRewards = new FluidVaultRewards(
|
||
|
IFluidReserveContract(address(reserveContract)),
|
||
|
IFluidVaultT1(address(vaultOne)),
|
||
|
IFluidLiquidity(address(0)),
|
||
|
2 ether, // distributing rewards amount
|
||
|
vaultRewardsDuration,
|
||
|
alice, // start initiator
|
||
|
address(supplyToken)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
function test_contructor_RevertIfCollateralTokenAddressZero() public {
|
||
|
vm.expectRevert(abi.encodeWithSelector(Error.FluidVaultError.selector, ErrorTypes.VaultRewards__AddressZero));
|
||
|
vaultRewards = new FluidVaultRewards(
|
||
|
IFluidReserveContract(address(reserveContract)),
|
||
|
IFluidVaultT1(address(vaultOne)),
|
||
|
IFluidLiquidity(address(liquidity)),
|
||
|
2 ether, // distributing rewards amount
|
||
|
vaultRewardsDuration,
|
||
|
alice, // start initiator
|
||
|
address(0)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
function test_contructor_RevertIfRewardsAmountZero() public {
|
||
|
vm.expectRevert(abi.encodeWithSelector(Error.FluidVaultError.selector, ErrorTypes.VaultRewards__InvalidParams));
|
||
|
vaultRewards = new FluidVaultRewards(
|
||
|
IFluidReserveContract(address(reserveContract)),
|
||
|
IFluidVaultT1(address(vaultOne)),
|
||
|
IFluidLiquidity(address(liquidity)),
|
||
|
0, // distributing rewards amount
|
||
|
vaultRewardsDuration,
|
||
|
alice, // start initiator
|
||
|
address(supplyToken)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
function test_contructor_RevertIfDurationZero() public {
|
||
|
vm.expectRevert(abi.encodeWithSelector(Error.FluidVaultError.selector, ErrorTypes.VaultRewards__InvalidParams));
|
||
|
vaultRewards = new FluidVaultRewards(
|
||
|
IFluidReserveContract(address(reserveContract)),
|
||
|
IFluidVaultT1(address(vaultOne)),
|
||
|
IFluidLiquidity(address(liquidity)),
|
||
|
2 ether, // distributing rewards amount
|
||
|
0,
|
||
|
alice, // start initiator
|
||
|
address(supplyToken)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
function test_calculateMagnifier_AfterEndTime() public {
|
||
|
vm.warp(vaultRewardsEndTime + 1);
|
||
|
(uint256 calculatedMagnifier, ) = vaultRewards.calculateMagnifier();
|
||
|
assertEq(calculatedMagnifier, RATE_PRECISION);
|
||
|
}
|
||
|
|
||
|
function test_rebalance_RevertIfNoRebalancer() public {
|
||
|
vm.expectRevert(abi.encodeWithSelector(Error.FluidVaultError.selector, ErrorTypes.VaultRewards__Unauthorized));
|
||
|
vm.prank(alice);
|
||
|
vaultRewards.rebalance();
|
||
|
}
|
||
|
|
||
|
function test_rebalance_RevertIfMagnifierNotChanged() public {
|
||
|
vm.prank(alice);
|
||
|
vaultOne.operate(
|
||
|
0, // new position
|
||
|
10 ether,
|
||
|
1 ether,
|
||
|
alice
|
||
|
);
|
||
|
|
||
|
assertEq(vaultResolver.getVaultEntireData(address(vaultOne)).configs.supplyRateMagnifier, 10000);
|
||
|
(uint256 calculatedMagnifier, ) = vaultRewards.calculateMagnifier();
|
||
|
|
||
|
vm.prank(rebalancer);
|
||
|
vaultRewards.rebalance();
|
||
|
|
||
|
assertEq(vaultResolver.getVaultEntireData(address(vaultOne)).configs.supplyRateMagnifier, calculatedMagnifier);
|
||
|
|
||
|
vm.warp(block.timestamp + 2 days);
|
||
|
|
||
|
vm.expectRevert(
|
||
|
abi.encodeWithSelector(
|
||
|
Error.FluidVaultError.selector,
|
||
|
ErrorTypes.VaultRewards__NewMagnifierSameAsOldMagnifier
|
||
|
)
|
||
|
);
|
||
|
vm.prank(rebalancer);
|
||
|
vaultRewards.rebalance();
|
||
|
}
|
||
|
|
||
|
function test_rebalance_OnePointFiveMagnifier() public {
|
||
|
// rewardsAmount = 2 ether
|
||
|
// so if currentTVL is 4 ether, rate magnifier would have to be 1.5
|
||
|
int256 tvlForOnePointFive = 4 ether;
|
||
|
|
||
|
vm.prank(alice);
|
||
|
vaultOne.operate(
|
||
|
0, // new position
|
||
|
tvlForOnePointFive,
|
||
|
0,
|
||
|
alice
|
||
|
);
|
||
|
vm.warp(block.timestamp + vaultRewardsStartTime);
|
||
|
(uint256 calculatedMagnifier, ) = vaultRewards.calculateMagnifier();
|
||
|
|
||
|
uint256 expectedMagnifier = (15 * RATE_PRECISION) / 10; // 1.5 * RATE_PRECISION;
|
||
|
assertEq(calculatedMagnifier, expectedMagnifier);
|
||
|
|
||
|
assertEq(vaultResolver.getVaultEntireData(address(vaultOne)).configs.supplyRateMagnifier, 10000);
|
||
|
|
||
|
vm.expectEmit(true, true, true, false);
|
||
|
emit LogUpdateMagnifier(address(vaultOne), expectedMagnifier);
|
||
|
vm.prank(rebalancer);
|
||
|
vaultRewards.rebalance();
|
||
|
|
||
|
assertEq(vaultResolver.getVaultEntireData(address(vaultOne)).configs.supplyRateMagnifier, expectedMagnifier);
|
||
|
}
|
||
|
|
||
|
function test_rebalance_OnePointTwentyFiveMagnifier() public {
|
||
|
// rewardsAmount = 2 ether
|
||
|
// so if currentTVL is 8 ether, rate magnifier would have to be 1.25
|
||
|
int256 tvlForOnePointTwentyFive = 8 ether;
|
||
|
|
||
|
vm.prank(alice);
|
||
|
vaultOne.operate(
|
||
|
0, // new position
|
||
|
tvlForOnePointTwentyFive,
|
||
|
0,
|
||
|
alice
|
||
|
);
|
||
|
vm.warp(block.timestamp + vaultRewardsStartTime);
|
||
|
(uint256 calculatedMagnifier, ) = vaultRewards.calculateMagnifier();
|
||
|
|
||
|
uint256 expectedMagnifier = (125 * RATE_PRECISION) / 100; // 1.25 * RATE_PRECISION;
|
||
|
assertEq(calculatedMagnifier, expectedMagnifier);
|
||
|
|
||
|
assertEq(vaultResolver.getVaultEntireData(address(vaultOne)).configs.supplyRateMagnifier, 10000);
|
||
|
|
||
|
vm.expectEmit(true, true, true, false);
|
||
|
emit LogUpdateMagnifier(address(vaultOne), expectedMagnifier);
|
||
|
vm.prank(rebalancer);
|
||
|
vaultRewards.rebalance();
|
||
|
|
||
|
assertEq(vaultResolver.getVaultEntireData(address(vaultOne)).configs.supplyRateMagnifier, expectedMagnifier);
|
||
|
}
|
||
|
}
|