Created
March 27, 2022 06:05
-
-
Save pyk/0f9bde407e177a9cccbeecbfd98bc2ac to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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