Skip to content

Instantly share code, notes, and snippets.

@pyk
Created March 27, 2022 06:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pyk/0f9bde407e177a9cccbeecbfd98bc2ac to your computer and use it in GitHub Desktop.
Save pyk/0f9bde407e177a9cccbeecbfd98bc2ac to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.11;
pragma experimental ABIEncoderV2;
import "lib/ds-test/src/test.sol";
import { IERC20 } from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import { FuseLeveragedToken } from "../FuseLeveragedToken.sol";
import { HEVM } from "./HEVM.sol";
import { gohm, fgohm, usdc, fusdc, sushiRouter } from "./Arbitrum.sol";
import { UniswapV2Adapter } from "../uniswap/UniswapV2Adapter.sol";
import { GOHMUSDCOracle } from "../oracles/GOHMUSDCOracle.sol";
import { IfERC20 } from "../interfaces/IfERC20.sol";
/**
* @title Fuse Leveraged Token Access Control Test
* @author bayu (github.com/pyk)
* @notice Make sure the access control works as expected
*/
contract FuseLeveragedTokenAccessControlTest is DSTest {
HEVM private hevm;
function setUp() public {
hevm = new HEVM();
}
/// @notice Make sure non-owner cannot set the maxMint value
function testFailNonOwnerCannotSetMaxMint() public {
// Create new FLT; by default the deployer is the owner
address dummy = hevm.addr(100);
FuseLeveragedToken flt = new FuseLeveragedToken("gOHM 2x Long", "gOHMRISE", dummy, dummy, fgohm, fusdc);
// Transfer the ownership
address newOwner = hevm.addr(1);
flt.transferOwnership(newOwner);
// Non-owner trying to set the maxMint value
flt.setMaxMint(1 ether); // This should be failed
}
/// @notice Make sure owner can set the maxMint value
function testOwnerCanSetMaxMint() public {
// Create new FLT; by default the deployer is the owner
address dummy = hevm.addr(100);
FuseLeveragedToken flt = new FuseLeveragedToken("gOHM 2x Long", "gOHMRISE", dummy, dummy, fgohm, fusdc);
// Make sure the default value is set
assertEq(flt.maxMint(), type(uint256).max);
// Owner set the MaxMint
uint256 newMaxMint = 1 ether;
flt.setMaxMint(newMaxMint);
// Make sure the value is updated
assertEq(flt.maxMint(), newMaxMint);
}
/// @notice Make sure non-owner cannot set fees value
function testFailNonOwnerCannotSetFees() public {
// Create new FLT; by default the deployer is the owner
address dummy = hevm.addr(100);
FuseLeveragedToken flt = new FuseLeveragedToken("gOHM 2x Long", "gOHMRISE", dummy, dummy, fgohm, fusdc);
// Transfer the ownership
address newOwner = hevm.addr(1);
flt.transferOwnership(newOwner);
// Non-owner trying to set the fees value
flt.setFees(0.01 ether); // This should be failed
}
/// @notice Make sure owner can set the fees value
function testOwnerCanSetFees() public {
// Create new FLT; by default the deployer is the owner
address dummy = hevm.addr(100);
FuseLeveragedToken flt = new FuseLeveragedToken("gOHM 2x Long", "gOHMRISE", dummy, dummy, fgohm, fusdc);
// Make sure the default value is set
assertEq(flt.fees(), 0.001 ether); // 0.1%
// Owner set the Fees
uint256 newFees = 0.002 ether;
flt.setFees(newFees);
// Make sure the value is updated
assertEq(flt.fees(), newFees);
}
/// @notice Make sure non-owner cannot call the bootstrap function
function testFailNonOwnerCannotBootstrapTheFLT() public {
// Create new FLT; by default the deployer is the owner
address dummy = hevm.addr(100);
FuseLeveragedToken flt = new FuseLeveragedToken("gOHM 2x Long", "gOHMRISE", dummy, dummy, fgohm, fusdc);
// Transfer the ownership
address newOwner = hevm.addr(1);
flt.transferOwnership(newOwner);
// Non-owner try to bootstrap the FLT
flt.bootstrap(2 ether, 333 * 1e6);
}
/// @notice Make sure owner can bootstrap the FLT
function testOwnerCanBootstrapTheFLT() public {
// A hack to make sure current block number > accrual block number on Rari Fuse
hevm.roll(block.number * 100);
// Add supply to the Rari Fuse
uint256 supplyAmount = 100_000 * 1e6; // 100K USDC
hevm.setUSDCBalance(address(this), supplyAmount);
IERC20(usdc).approve(fusdc, supplyAmount);
IfERC20(fusdc).mint(supplyAmount);
// Create the Uniswap Adapter
UniswapV2Adapter adapter = new UniswapV2Adapter(sushiRouter);
// Create the collateral oracle
GOHMUSDCOracle oracle = new GOHMUSDCOracle();
// Create new FLT
FuseLeveragedToken flt = new FuseLeveragedToken("gOHM 2x Long", "gOHMRISE", address(adapter), address(oracle), fgohm, fusdc);
// Top up gOHM balance to this contract
uint256 collateralAmount = 1 ether;
hevm.setGOHMBalance(address(this), collateralAmount);
// Approve the contract to spend gOHM
IERC20(gohm).approve(address(flt), collateralAmount);
// Bootstrap the FLT
uint256 nav = 333 * 1e6; // 333 USDC
flt.bootstrap(collateralAmount, nav);
// Make sure the isBootstrap is set to true
assertTrue(flt.isBootstrapped());
// Make sure the total collateral is correct
uint256 totalCollateral = flt.totalCollateral();
assertEq(totalCollateral, 1899999999999999999, "Check total collateral");
uint256 balance = IERC20(fgohm).balanceOf(address(flt));
assertGt(balance, 0);
// Make sure the total debt is correct
uint256 price = oracle.getPrice();
uint256 debt = (0.95 ether * price) / 1 ether;
assertEq(flt.totalDebt(), debt, "Check total debt");
// Make sure the total shares is correct
assertEq(flt.totalSupply(), (((totalCollateral * price) / 1 ether) - debt) * 1 ether / nav, "Check total shares");
// Make sure the token is minted to this contract
assertEq(flt.balanceOf(address(this)), flt.totalSupply(), "Check minted balance");
}
/// @notice Make sure owner cannot bootstrap the FLT twice
function testFailOwnerCannotBootstrapTheFLTTwice() public {
// A hack to make sure current block number > accrual block number on Rari Fuse
hevm.roll(block.number * 100);
// Add supply to the Rari Fuse
uint256 supplyAmount = 100_000 * 1e6; // 100K USDC
hevm.setUSDCBalance(address(this), supplyAmount);
IERC20(usdc).approve(fusdc, supplyAmount);
IfERC20(fusdc).mint(supplyAmount);
// Create the Uniswap Adapter
UniswapV2Adapter adapter = new UniswapV2Adapter(sushiRouter);
// Create the collateral oracle
GOHMUSDCOracle oracle = new GOHMUSDCOracle();
// Create new FLT
FuseLeveragedToken flt = new FuseLeveragedToken("gOHM 2x Long", "gOHMRISE", address(adapter), address(oracle), fgohm, fusdc);
// Top up gOHM balance to this contract
uint256 collateralAmount = 1 ether;
hevm.setGOHMBalance(address(this), collateralAmount);
// Approve the contract to spend gOHM
IERC20(gohm).approve(address(flt), collateralAmount);
// Bootstrap the FLT
uint256 nav = 333 * 1e6; // 333 USDC
flt.bootstrap(collateralAmount, nav);
// Bootstrap again; this should be failed
flt.bootstrap(collateralAmount, nav);
}
/// @notice Make sure onFlashSwapExactTokensForTokensViaETH can only called by Uniswap Adapter
function testFailOnFlashSwapExactTokensForTokensViaETHCannotBeCalledByRandomCreatureInTheDarkForest() public {
// Create the Uniswap Adapter
UniswapV2Adapter adapter = new UniswapV2Adapter(sushiRouter);
// Create the collateral oracle
GOHMUSDCOracle oracle = new GOHMUSDCOracle();
// Create new FLT
FuseLeveragedToken flt = new FuseLeveragedToken("gOHM 2x Long", "gOHMRISE", address(adapter), address(oracle), fgohm, fusdc);
// Call the callback; this should be failed
flt.onFlashSwapExactTokensForTokensViaETH(1 ether, bytes(""));
}
/// @notice Make sure non-owner cannot set uniswapAdapter
function testFailNonOwnerCannotSetUniswapAdapter() public {
// Create new FLT; by default the deployer is the owner
address dummy = hevm.addr(100);
FuseLeveragedToken flt = new FuseLeveragedToken("gOHM 2x Long", "gOHMRISE", dummy, dummy, fgohm, fusdc);
// Transfer the ownership
address newOwner = hevm.addr(1);
flt.transferOwnership(newOwner);
// Non-owner trying to set the uniswapAdapter
flt.setUniswapAdapter(hevm.addr(2)); // This should be reverted
}
/// @notice Make sure owner can set the uniswapAdapter
function testOwnerCanSetUniswapAdapter() public {
// Create new FLT; by default the deployer is the owner
address dummy = hevm.addr(100);
FuseLeveragedToken flt = new FuseLeveragedToken("gOHM 2x Long", "gOHMRISE", dummy, dummy, fgohm, fusdc);
address newAdapter = hevm.addr(1);
flt.setUniswapAdapter(newAdapter);
assertEq(flt.uniswapAdapter(), newAdapter);
}
/// @notice Make sure non-owner cannot set oracle
function testFailNonOwnerCannotSetOracle() public {
// Create new FLT; by default the deployer is the owner
address dummy = hevm.addr(100);
FuseLeveragedToken flt = new FuseLeveragedToken("gOHM 2x Long", "gOHMRISE", dummy, dummy, fgohm, fusdc);
// Transfer the ownership
address newOwner = hevm.addr(1);
flt.transferOwnership(newOwner);
// Non-owner trying to set the uniswapAdapter
flt.setOracle(hevm.addr(2)); // This should be reverted
}
/// @notice Make sure owner can set the oracle
function testOwnerCanSetOracle() public {
// Create new FLT; by default the deployer is the owner
address dummy = hevm.addr(100);
FuseLeveragedToken flt = new FuseLeveragedToken("gOHM 2x Long", "gOHMRISE", dummy, dummy, fgohm, fusdc);
address newOracle = hevm.addr(1);
flt.setOracle(newOracle);
assertEq(flt.oracle(), newOracle);
}
/// @notice Make sure non-owner cannot set fee recipient
function testFailNonOwnerCannotSetFeeRecipient() public {
// Create new FLT; by default the deployer is the owner
address dummy = hevm.addr(100);
FuseLeveragedToken flt = new FuseLeveragedToken("gOHM 2x Long", "gOHMRISE", dummy, dummy, fgohm, fusdc);
// Transfer the ownership
address newOwner = hevm.addr(1);
flt.transferOwnership(newOwner);
// Non-owner trying to set the fee recipient
flt.setFeeRecipient(hevm.addr(2)); // This should be reverted
}
/// @notice Make sure owner can set the feeRecipient
function testOwnerCanSetFeeRecipient() public {
// Create new FLT; by default the deployer is the owner
address dummy = hevm.addr(100);
FuseLeveragedToken flt = new FuseLeveragedToken("gOHM 2x Long", "gOHMRISE", dummy, dummy, fgohm, fusdc);
address newRecipient = hevm.addr(1);
flt.setFeeRecipient(newRecipient);
assertEq(flt.feeRecipient(), newRecipient);
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.11;
pragma experimental ABIEncoderV2;
import "lib/ds-test/src/test.sol";
import { IERC20 } from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import { FuseLeveragedToken } from "../FuseLeveragedToken.sol";
import { HEVM } from "./HEVM.sol";
import { gohm, usdc, fgohm, fusdc, sushiRouter } from "./Arbitrum.sol";
import { UniswapV2Adapter } from "../uniswap/UniswapV2Adapter.sol";
import { GOHMUSDCOracle } from "../oracles/GOHMUSDCOracle.sol";
import { IfERC20 } from "../interfaces/IfERC20.sol";
/**
* @title FLT User
* @author bayu (github.com/pyk)
* @notice Mock contract to simulate user interaction
*/
contract User {
FuseLeveragedToken private flt;
constructor(FuseLeveragedToken _flt) {
flt = _flt;
}
/// @notice Simulate user's mint
function mint(uint256 _shares) external returns (uint256 _collateral) {
IERC20(gohm).approve(address(flt), type(uint256).max);
_collateral = flt.mint(_shares, address(this));
IERC20(gohm).approve(address(flt), 0);
}
/// @notice Simulate user's mint with custom recipient
function mint(uint256 _shares, address _recipient) external returns (uint256 _collateral) {
IERC20(gohm).approve(address(flt), type(uint256).max);
_collateral = flt.mint(_shares, _recipient);
IERC20(gohm).approve(address(flt), 0);
}
/// @notice Simulate user's redeem
function redeem(uint256 _shares) external returns (uint256 _collateral) {
IERC20(flt).approve(address(flt), type(uint256).max);
_collateral = flt.redeem(_shares);
IERC20(flt).approve(address(flt), 0);
}
}
/**
* @title Fuse Leveraged Token User Test
* @author bayu (github.com/pyk)
* @notice Make sure all user interactions are working as expected
*/
contract FuseLeveragedTokenUserTest is DSTest {
HEVM private hevm;
function setUp() public {
hevm = new HEVM();
}
/// @notice Make sure the default storage is correctly set
function testDefaultStorage() public {
address dummy = hevm.addr(100);
FuseLeveragedToken flt = new FuseLeveragedToken("gOHM 2x Long", "gOHMRISE", dummy, dummy, fgohm, fusdc);
assertEq(flt.name(), "gOHM 2x Long");
assertEq(flt.symbol(), "gOHMRISE");
assertEq(flt.decimals(), 18);
assertEq(flt.collateral(), gohm);
assertEq(flt.debt(), usdc);
assertEq(flt.fCollateral(), fgohm);
assertEq(flt.fDebt(), fusdc);
assertEq(flt.uniswapAdapter(), dummy);
assertEq(flt.oracle(), dummy);
assertTrue(!flt.isBootstrapped());
assertEq(flt.maxMint(), type(uint256).max);
assertEq(flt.fees(), 0.001 ether);
}
/// @notice Make sure the read-only function is correctly set
function testDefaultReadOnly() public {
address dummy = hevm.addr(100);
FuseLeveragedToken flt = new FuseLeveragedToken("gOHM 2x Long", "gOHMRISE", dummy, dummy, fgohm, fusdc);
assertEq(flt.totalCollateral(), 0);
assertEq(flt.totalDebt(), 0);
assertEq(flt.collateralPerShares(), 0);
assertEq(flt.collateralValuePerShares(), 0);
assertEq(flt.debtPerShares(), 0);
assertEq(flt.nav(), 0);
assertEq(flt.leverageRatio(), 0);
}
/// @notice Make sure user cannot mint when FLT is not bootstrapped
function testFailUserCannotMintIfFLTIsNotBoostrapped() public {
// Create new FLT
address dummy = hevm.addr(100);
FuseLeveragedToken flt = new FuseLeveragedToken("gOHM 2x Long", "gOHMRISE", dummy, dummy, fgohm, fusdc);
// Create new User
User user = new User(flt);
// FlT is not bootstrapped but the user trying to mint
user.mint(1 ether); // This should be reverted
}
/// @notice Utility function to deploy and bootstrap FLT
function bootstrap() internal returns (FuseLeveragedToken) {
// A hack to make sure current block number > accrual block number on Rari Fuse
hevm.roll(block.number * 100);
// Add supply to the Rari Fuse
uint256 supplyAmount = 100_000 * 1e6; // 100K USDC
hevm.setUSDCBalance(address(this), supplyAmount);
IERC20(usdc).approve(fusdc, supplyAmount);
IfERC20(fusdc).mint(supplyAmount);
// Create the Uniswap Adapter
UniswapV2Adapter adapter = new UniswapV2Adapter(sushiRouter);
// Create the collateral oracle
GOHMUSDCOracle oracle = new GOHMUSDCOracle();
// Create new FLT
FuseLeveragedToken flt = new FuseLeveragedToken("gOHM 2x Long", "gOHMRISE", address(adapter), address(oracle), fgohm, fusdc);
// Top up gOHM balance to this contract
uint256 collateralAmount = 1 ether;
hevm.setGOHMBalance(address(this), collateralAmount);
// Approve the contract to spend gOHM
IERC20(gohm).approve(address(flt), collateralAmount);
// Bootstrap the FLT
uint256 nav = 333 * 1e6; // 333 USDC
flt.bootstrap(collateralAmount, nav);
return flt;
}
/// @notice Make sure user cannot mint more than maxMint
function testFailUserCannotMintMoreThanMaxMint() public {
// Create new FLT
FuseLeveragedToken flt = bootstrap();
// Set the max mint
flt.setMaxMint(2 ether);
// Create new User
User user = new User(flt);
// User trying to mint more than max mint
user.mint(5 ether); // This should be reverted
}
/// @notice Make sure mint is correct
function testUserCanMint() public {
// Create new FLT
FuseLeveragedToken flt = bootstrap();
// Previous collateral & debt per shares
uint256 prevCPS = flt.collateralPerShares();
uint256 prevDPS = flt.debtPerShares();
uint256 nav = flt.nav();
uint256 tc = flt.totalCollateral();
uint256 td = flt.totalDebt();
// Create new user
User user = new User(flt);
// Top up user balance
hevm.setGOHMBalance(address(user), 1 ether); // 1 gOHM
// Get preview mint amount
uint256 shares = 1 ether;
uint256 previewMintAmount = flt.previewMint(shares);
// User mint
uint256 collateralAmount = user.mint(shares);
// Check preview mint
assertEq(previewMintAmount, collateralAmount, "check preview");
// Make sure user token is transfered to the user
assertEq(IERC20(flt).balanceOf(address(user)), shares, "check user balance");
// Make sure fee is deducted
assertEq(IERC20(flt).balanceOf(address(flt)), 0.001 ether, "check collected fees");
// Make sure FLT debit the correct collateral amount
assertEq(IERC20(gohm).balanceOf(address(user)), 1 ether - collateralAmount, "check debited collateral");
// Make sure total collateral and total debt are not changed
assertEq(flt.collateralPerShares(), prevCPS, "check cps");
assertEq(flt.debtPerShares(), prevDPS, "check dps");
// Make sure NAV not changed
assertEq(flt.nav(), nav, "check nav");
assertGt(flt.totalCollateral(), tc, "check total collateral");
assertGt(flt.totalDebt(), td, "check total debt");
}
/// @notice Make sure previewMint is return the same thing in one block
function testPreviewMint() public {
// Create new FLT
FuseLeveragedToken flt = bootstrap();
assertEq(flt.previewMint(1 ether), flt.previewMint(1 ether));
}
/// @notice Make sure user can mint with custom recipient
function testUserCanMintToCustomRecipient() public {
// Create new FLT
FuseLeveragedToken flt = bootstrap();
// Create new user
User user = new User(flt);
// Top up user balance
hevm.setGOHMBalance(address(user), 1 ether); // 1 gOHM
// Get preview mint amount
uint256 shares = 1 ether;
// User mint
address recipient = hevm.addr(1);
user.mint(shares, recipient);
// Make sure user token is transfered to the user
assertEq(IERC20(flt).balanceOf(recipient), shares, "check user balance");
}
/// @notice Make sure previewRedeem is return the same thing in one block
function testPreviewRedeem() public {
// Create new FLT
FuseLeveragedToken flt = bootstrap();
assertEq(flt.previewRedeem(1 ether), flt.previewRedeem(1 ether));
}
/// @notice Make sure user can redeem the token
function testUserCanRedeem() public {
// Create new FLT
FuseLeveragedToken flt = bootstrap();
// Create new user
User user = new User(flt);
// Top up user balance
hevm.setGOHMBalance(address(user), 1 ether); // 1 gOHM
// User mint
uint256 shares = 1 ether;
uint256 mintCollateral = user.mint(shares);
// Previous collateral & debt per shares
uint256 prevCPS = flt.collateralPerShares();
uint256 prevDPS = flt.debtPerShares();
uint256 nav = flt.nav();
uint256 tc = flt.totalCollateral();
uint256 td = flt.totalDebt();
// Get preview mint amount
uint256 previewRedeemAmount = flt.previewRedeem(shares);
// Redeem the collateral
uint256 collateralAmount = user.redeem(shares);
// Check preview mint
assertEq(previewRedeemAmount, collateralAmount, "check preview");
// Make sure the token is burned
assertEq(IERC20(flt).balanceOf(address(user)), 0, "check user balance");
// Make sure fee is deducted (mint & redeem fees)
assertEq(IERC20(flt).balanceOf(address(flt)), 0.002 ether, "check collected fees");
// Make sure user receive the collateral
assertEq(IERC20(gohm).balanceOf(address(user)), 1 ether - mintCollateral + collateralAmount, "check received collateral");
// Make sure total collateral and total debt are not changed
assertEq(flt.collateralPerShares(), prevCPS, "check cps");
assertEq(flt.debtPerShares(), prevDPS, "check dps");
// Make sure NAV not changed
assertEq(flt.nav(), nav, "check nav");
assertLt(flt.totalCollateral(), tc, "check total collateral");
assertLt(flt.totalDebt(), td, "check total debt");
}
/// @notice Make sure collect send to fee recipient
function testCollectFees() public {
// Create new FLT
FuseLeveragedToken flt = bootstrap();
// Create new user
User user = new User(flt);
// Top up user balance
hevm.setGOHMBalance(address(user), 1 ether); // 1 gOHM
// User mint
uint256 shares = 1 ether;
user.mint(shares);
// Check prev balance
uint256 prevBalance = flt.balanceOf(address(this));
// Collect fees
flt.collect();
// Make sure fees is tranfered to the fee recipient
assertEq(flt.balanceOf(address(this)), prevBalance + 0.001 ether);
assertEq(flt.balanceOf(address(flt)), 0);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment