Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FuseERC4626 Vaults : unit & integration tests #6

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Prev Previous commit
lint & prettier
eswak committed Apr 5, 2022
commit a76831e56ffeab352d0d118dbafa8988955c30ef
470 changes: 235 additions & 235 deletions src/test/mocks/MockCToken.sol
Original file line number Diff line number Diff line change
@@ -1,235 +1,235 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.10;

import {MockERC20} from "./MockERC20.sol";
import {CToken} from "../../external/CToken.sol";
import {InterestRateModel} from "libcompound/interfaces/InterestRateModel.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";

contract MockInterestRateModel is InterestRateModel {
function getBorrowRate(
uint256,
uint256,
uint256
) external view override returns (uint256) {
return 0;
}

function getSupplyRate(
uint256,
uint256,
uint256,
uint256
) external view override returns (uint256) {
return 0;
}
}

contract MockUnitroller {
function supplyCaps(
address /* cToken*/
) external view returns (uint256) {
return 100e18;
}

function mintGuardianPaused(
address /* cToken*/
) external view returns (bool) {
return false;
}

function borrowGuardianPaused(
address /* cToken*/
) external view returns (bool) {
return false;
}
}

contract MockCToken is MockERC20, CToken {
MockERC20 public token;
bool public error;
bool public isCEther;
InterestRateModel public irm;
address public override comptroller;

uint256 private constant EXCHANGE_RATE_SCALE = 1e18;
uint256 public effectiveExchangeRate = 2e18;

constructor(address _token, bool _isCEther) {
token = MockERC20(_token);
isCEther = _isCEther;
irm = new MockInterestRateModel();
comptroller = address(new MockUnitroller());
}

function setError(bool _error) external {
error = _error;
}

function setEffectiveExchangeRate(uint256 _effectiveExchangeRate) external {
effectiveExchangeRate = _effectiveExchangeRate;
}

function isCToken() external pure returns (bool) {
return true;
}

function underlying() external view override returns (ERC20) {
return ERC20(address(token));
}

function balanceOfUnderlying(address)
external
view
override
returns (uint256)
{
return 0;
}

function mint() external payable {
_mint(
msg.sender,
(msg.value * EXCHANGE_RATE_SCALE) / effectiveExchangeRate
);
}

function mint(uint256 amount) external override returns (uint256) {
token.transferFrom(msg.sender, address(this), amount);
_mint(
msg.sender,
(amount * EXCHANGE_RATE_SCALE) / effectiveExchangeRate
);
return error ? 1 : 0;
}

function borrow(uint256) external override returns (uint256) {
return 0;
}

function redeem(uint256 redeemTokens) external returns (uint256) {
_burn(msg.sender, redeemTokens);
uint256 redeemAmount = (redeemTokens * effectiveExchangeRate) /
EXCHANGE_RATE_SCALE;
if (address(this).balance >= redeemAmount) {
payable(msg.sender).transfer(redeemAmount);
} else {
token.transfer(msg.sender, redeemAmount);
}
return error ? 1 : 0;
}

function redeemUnderlying(uint256 redeemAmount)
external
override
returns (uint256)
{
_burn(
msg.sender,
(redeemAmount * EXCHANGE_RATE_SCALE) / effectiveExchangeRate
);
if (address(this).balance >= redeemAmount) {
payable(msg.sender).transfer(redeemAmount);
} else {
token.transfer(msg.sender, redeemAmount);
}
return error ? 1 : 0;
}

function getAccountSnapshot(address)
external
view
override
returns (
uint256,
uint256,
uint256,
uint256
)
{
return (0, 0, 0, 0);
}

function exchangeRateStored() external view override returns (uint256) {
return
(EXCHANGE_RATE_SCALE * effectiveExchangeRate) / EXCHANGE_RATE_SCALE; // 2:1
}

function exchangeRateCurrent() external override returns (uint256) {
// fake state operation to not allow "view" modifier
effectiveExchangeRate = effectiveExchangeRate;

return
(EXCHANGE_RATE_SCALE * effectiveExchangeRate) / EXCHANGE_RATE_SCALE; // 2:1
}

function getCash() external view override returns (uint256) {
return token.balanceOf(address(this));
}

function totalBorrows() external pure override returns (uint256) {
return 0;
}

function totalReserves() external pure override returns (uint256) {
return 0;
}

function totalFuseFees() external view override returns (uint256) {
return 0;
}

function totalAdminFees() external view override returns (uint256) {
return 0;
}

function interestRateModel()
external
view
override
returns (InterestRateModel)
{
return irm;
}

function reserveFactorMantissa() external view override returns (uint256) {
return 0;
}

function fuseFeeMantissa() external view override returns (uint256) {
return 0;
}

function adminFeeMantissa() external view override returns (uint256) {
return 0;
}

function initialExchangeRateMantissa()
external
view
override
returns (uint256)
{
return 0;
}

function repayBorrow(uint256) external override returns (uint256) {
return 0;
}

function repayBorrowBehalf(address, uint256)
external
override
returns (uint256)
{
return 0;
}

function borrowBalanceCurrent(address) external override returns (uint256) {
return 0;
}

function accrualBlockNumber() external view override returns (uint256) {
return block.number;
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.10;

import {MockERC20} from "./MockERC20.sol";
import {CToken} from "../../external/CToken.sol";
import {InterestRateModel} from "libcompound/interfaces/InterestRateModel.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";

contract MockInterestRateModel is InterestRateModel {
function getBorrowRate(
uint256,
uint256,
uint256
) external view override returns (uint256) {
return 0;
}

function getSupplyRate(
uint256,
uint256,
uint256,
uint256
) external view override returns (uint256) {
return 0;
}
}

contract MockUnitroller {
function supplyCaps(
address /* cToken*/
) external view returns (uint256) {
return 100e18;
}

function mintGuardianPaused(
address /* cToken*/
) external view returns (bool) {
return false;
}

function borrowGuardianPaused(
address /* cToken*/
) external view returns (bool) {
return false;
}
}

contract MockCToken is MockERC20, CToken {
MockERC20 public token;
bool public error;
bool public isCEther;
InterestRateModel public irm;
address public override comptroller;

uint256 private constant EXCHANGE_RATE_SCALE = 1e18;
uint256 public effectiveExchangeRate = 2e18;

constructor(address _token, bool _isCEther) {
token = MockERC20(_token);
isCEther = _isCEther;
irm = new MockInterestRateModel();
comptroller = address(new MockUnitroller());
}

function setError(bool _error) external {
error = _error;
}

function setEffectiveExchangeRate(uint256 _effectiveExchangeRate) external {
effectiveExchangeRate = _effectiveExchangeRate;
}

function isCToken() external pure returns (bool) {
return true;
}

function underlying() external view override returns (ERC20) {
return ERC20(address(token));
}

function balanceOfUnderlying(address)
external
view
override
returns (uint256)
{
return 0;
}

function mint() external payable {
_mint(
msg.sender,
(msg.value * EXCHANGE_RATE_SCALE) / effectiveExchangeRate
);
}

function mint(uint256 amount) external override returns (uint256) {
token.transferFrom(msg.sender, address(this), amount);
_mint(
msg.sender,
(amount * EXCHANGE_RATE_SCALE) / effectiveExchangeRate
);
return error ? 1 : 0;
}

function borrow(uint256) external override returns (uint256) {
return 0;
}

function redeem(uint256 redeemTokens) external returns (uint256) {
_burn(msg.sender, redeemTokens);
uint256 redeemAmount = (redeemTokens * effectiveExchangeRate) /
EXCHANGE_RATE_SCALE;
if (address(this).balance >= redeemAmount) {
payable(msg.sender).transfer(redeemAmount);
} else {
token.transfer(msg.sender, redeemAmount);
}
return error ? 1 : 0;
}

function redeemUnderlying(uint256 redeemAmount)
external
override
returns (uint256)
{
_burn(
msg.sender,
(redeemAmount * EXCHANGE_RATE_SCALE) / effectiveExchangeRate
);
if (address(this).balance >= redeemAmount) {
payable(msg.sender).transfer(redeemAmount);
} else {
token.transfer(msg.sender, redeemAmount);
}
return error ? 1 : 0;
}

function getAccountSnapshot(address)
external
view
override
returns (
uint256,
uint256,
uint256,
uint256
)
{
return (0, 0, 0, 0);
}

function exchangeRateStored() external view override returns (uint256) {
return
(EXCHANGE_RATE_SCALE * effectiveExchangeRate) / EXCHANGE_RATE_SCALE; // 2:1
}

function exchangeRateCurrent() external override returns (uint256) {
// fake state operation to not allow "view" modifier
effectiveExchangeRate = effectiveExchangeRate;

return
(EXCHANGE_RATE_SCALE * effectiveExchangeRate) / EXCHANGE_RATE_SCALE; // 2:1
}

function getCash() external view override returns (uint256) {
return token.balanceOf(address(this));
}

function totalBorrows() external pure override returns (uint256) {
return 0;
}

function totalReserves() external pure override returns (uint256) {
return 0;
}

function totalFuseFees() external view override returns (uint256) {
return 0;
}

function totalAdminFees() external view override returns (uint256) {
return 0;
}

function interestRateModel()
external
view
override
returns (InterestRateModel)
{
return irm;
}

function reserveFactorMantissa() external view override returns (uint256) {
return 0;
}

function fuseFeeMantissa() external view override returns (uint256) {
return 0;
}

function adminFeeMantissa() external view override returns (uint256) {
return 0;
}

function initialExchangeRateMantissa()
external
view
override
returns (uint256)
{
return 0;
}

function repayBorrow(uint256) external override returns (uint256) {
return 0;
}

function repayBorrowBehalf(address, uint256)
external
override
returns (uint256)
{
return 0;
}

function borrowBalanceCurrent(address) external override returns (uint256) {
return 0;
}

function accrualBlockNumber() external view override returns (uint256) {
return block.number;
}
}
48 changes: 26 additions & 22 deletions src/test/mocks/MockERC20.sol
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.10;

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

contract MockERC20 is ERC20 {
constructor() ERC20("MockToken", "MCT", 18) {}

function mint(address account, uint256 amount) public returns (bool) {
_mint(account, amount);
return true;
}

function mockBurn(address account, uint256 amount) public returns (bool) {
_burn(account, amount);
return true;
}

function approveOverride(address owner, address spender, uint256 amount) public {
allowance[owner][spender] = amount;
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.10;

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

contract MockERC20 is ERC20 {
constructor() ERC20("MockToken", "MCT", 18) {}

function mint(address account, uint256 amount) public returns (bool) {
_mint(account, amount);
return true;
}

function mockBurn(address account, uint256 amount) public returns (bool) {
_burn(account, amount);
return true;
}

function approveOverride(
address owner,
address spender,
uint256 amount
) public {
allowance[owner][spender] = amount;
}
}
23 changes: 11 additions & 12 deletions src/test/mocks/MockFusePriceOracle.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.10;

contract MockFusePriceOracle {

bool public constant isPriceOracle = true;
mapping(address=>uint256) public getUnderlyingPrice;

function mockSetPrice(address cToken, uint256 value) external {
getUnderlyingPrice[cToken] = value;
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.10;

contract MockFusePriceOracle {
bool public constant isPriceOracle = true;
mapping(address => uint256) public getUnderlyingPrice;

function mockSetPrice(address cToken, uint256 value) external {
getUnderlyingPrice[cToken] = value;
}
}
8 changes: 4 additions & 4 deletions src/test/utils/Console.sol
Original file line number Diff line number Diff line change
@@ -179,7 +179,7 @@ library console {
}

function log(bytes32[] memory p0) internal view {
for (uint256 i; i < p0.length;) {
for (uint256 i; i < p0.length; ) {
_sendLogPayload(abi.encodeWithSignature("log(bytes32)", p0[i]));
unchecked {
++i;
@@ -192,7 +192,7 @@ library console {
}

function log(uint256[] memory p0) internal view {
for (uint256 i; i < p0.length;) {
for (uint256 i; i < p0.length; ) {
_sendLogPayload(abi.encodeWithSignature("log(uint)", p0[i]));
unchecked {
++i;
@@ -205,7 +205,7 @@ library console {
}

function log(string[] memory p0) internal view {
for (uint256 i; i < p0.length;) {
for (uint256 i; i < p0.length; ) {
_sendLogPayload(abi.encodeWithSignature("log(string)", p0[i]));
unchecked {
++i;
@@ -218,7 +218,7 @@ library console {
}

function log(bool[] memory p0) internal view {
for (uint256 i; i < p0.length;) {
for (uint256 i; i < p0.length; ) {
_sendLogPayload(abi.encodeWithSignature("log(bool)", p0[i]));
unchecked {
++i;
212 changes: 106 additions & 106 deletions src/test/utils/FusePoolUtils.sol
Original file line number Diff line number Diff line change
@@ -1,106 +1,106 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import {Unitroller} from "../../external/Unitroller.sol";

library FusePoolUtils {
address public constant MASTER_PRICE_ORACLE_INITIALIZABLECLONES =
address(0x91cE5566DC3170898C5aeE4ae4dD314654B47415);
address public constant MASTER_PRICE_ORACLE_IMPLEMENTATION =
address(0xb3c8eE7309BE658c186F986388c2377da436D8fb);
address public constant MASTER_PRICE_ORACLE_RARI_DEFAULT =
address(0x1887118E49e0F4A78Bd71B792a49dE03504A764D);
address public constant FUSE_POOL_DIRECTORY =
address(0x835482FE0532f169024d5E9410199369aAD5C77E);
address public constant FUSE_COMPTROLLER_IMPLEMENTATION =
address(0xE16DB319d9dA7Ce40b666DD2E365a4b8B3C18217);
address public constant FUSE_JUMP_RATE_MODEL =
address(0xbAB47e4B692195BF064923178A90Ef999A15f819);
address public constant FUSE_CERC20_DELEGATE =
address(0x67Db14E73C2Dce786B5bbBfa4D010dEab4BBFCF9);

function createPool(address[] memory tokens, address oracle)
external
returns (
address masterOracle,
address troller,
address[] memory cTokens
)
{
// create a new master price oracle
(bool success, bytes memory data) = MASTER_PRICE_ORACLE_INITIALIZABLECLONES
.call(
abi.encodeWithSignature(
"clone(address,bytes)",
MASTER_PRICE_ORACLE_IMPLEMENTATION,
abi.encodeWithSignature(
"initialize(address[],address[],address,address,bool)",
new address[](0), // underlyings
new address[](0), // oracles
MASTER_PRICE_ORACLE_RARI_DEFAULT, // default oracle
address(this), // pool admin
true // canAdminOwerwrite
)
)
);
require(success, "Error creating master price oracle");
assembly {
masterOracle := mload(add(data, 32))
}

// create a new Rari pool (call Rari Capital: Fuse Pool Directory)
(success, data) = FUSE_POOL_DIRECTORY.call(
abi.encodeWithSignature(
"deployPool(string,address,bool,uint256,uint256,address)",
"Test Pool", // name
FUSE_COMPTROLLER_IMPLEMENTATION, // implementation
false, // enforceWhitelist
0.5e18, // closeFactor
1.08e18, // liquidationIncentive
masterOracle // priceOracle
)
);
require(success, "Error creating pool");
assembly {
troller := mload(add(data, 64))
}

// accept admin of the comptroller
Unitroller(troller)._acceptAdmin();

// add token price to master oracle
address[] memory oracles = new address[](tokens.length);
for (uint256 i = 0; i < oracles.length; i++) {
oracles[i] = oracle;
}
(success, data) = masterOracle.call(
abi.encodeWithSignature("add(address[],address[])", tokens, oracles)
);
require(success, "Error setting new token price feed in master oracle");

// add tokens in the fuse pool
cTokens = new address[](tokens.length);
for (uint256 i = 0; i < tokens.length; i++) {
Unitroller(troller)._deployMarket(
false, // isCEther
abi.encode( // CErc20Delegator constructor data
tokens[i], // underlying
troller, // comptroller
FUSE_JUMP_RATE_MODEL, // interestRateModel
"fToken-x", // name
"Fuse pool x Token", // symbol
FUSE_CERC20_DELEGATE, // implementation
bytes(""), // becomeImplementationData
uint256(0), // reserveFactorMantissa
uint256(0) // adminFeeMantissa
),
0.7e18 // collateralFactorMantissa
);
cTokens[i] = Unitroller(troller).cTokensByUnderlying(tokens[i]);
require(
cTokens[i] != address(0),
"Error adding token to Fuse pool"
);
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import {Unitroller} from "../../external/Unitroller.sol";

library FusePoolUtils {
address public constant MASTER_PRICE_ORACLE_INITIALIZABLECLONES =
address(0x91cE5566DC3170898C5aeE4ae4dD314654B47415);
address public constant MASTER_PRICE_ORACLE_IMPLEMENTATION =
address(0xb3c8eE7309BE658c186F986388c2377da436D8fb);
address public constant MASTER_PRICE_ORACLE_RARI_DEFAULT =
address(0x1887118E49e0F4A78Bd71B792a49dE03504A764D);
address public constant FUSE_POOL_DIRECTORY =
address(0x835482FE0532f169024d5E9410199369aAD5C77E);
address public constant FUSE_COMPTROLLER_IMPLEMENTATION =
address(0xE16DB319d9dA7Ce40b666DD2E365a4b8B3C18217);
address public constant FUSE_JUMP_RATE_MODEL =
address(0xbAB47e4B692195BF064923178A90Ef999A15f819);
address public constant FUSE_CERC20_DELEGATE =
address(0x67Db14E73C2Dce786B5bbBfa4D010dEab4BBFCF9);

function createPool(address[] memory tokens, address oracle)
external
returns (
address masterOracle,
address troller,
address[] memory cTokens
)
{
// create a new master price oracle
(bool success, bytes memory data) = MASTER_PRICE_ORACLE_INITIALIZABLECLONES
.call(
abi.encodeWithSignature(
"clone(address,bytes)",
MASTER_PRICE_ORACLE_IMPLEMENTATION,
abi.encodeWithSignature(
"initialize(address[],address[],address,address,bool)",
new address[](0), // underlyings
new address[](0), // oracles
MASTER_PRICE_ORACLE_RARI_DEFAULT, // default oracle
address(this), // pool admin
true // canAdminOwerwrite
)
)
);
require(success, "Error creating master price oracle");
assembly {
masterOracle := mload(add(data, 32))
}

// create a new Rari pool (call Rari Capital: Fuse Pool Directory)
(success, data) = FUSE_POOL_DIRECTORY.call(
abi.encodeWithSignature(
"deployPool(string,address,bool,uint256,uint256,address)",
"Test Pool", // name
FUSE_COMPTROLLER_IMPLEMENTATION, // implementation
false, // enforceWhitelist
0.5e18, // closeFactor
1.08e18, // liquidationIncentive
masterOracle // priceOracle
)
);
require(success, "Error creating pool");
assembly {
troller := mload(add(data, 64))
}

// accept admin of the comptroller
Unitroller(troller)._acceptAdmin();

// add token price to master oracle
address[] memory oracles = new address[](tokens.length);
for (uint256 i = 0; i < oracles.length; i++) {
oracles[i] = oracle;
}
(success, data) = masterOracle.call(
abi.encodeWithSignature("add(address[],address[])", tokens, oracles)
);
require(success, "Error setting new token price feed in master oracle");

// add tokens in the fuse pool
cTokens = new address[](tokens.length);
for (uint256 i = 0; i < tokens.length; i++) {
Unitroller(troller)._deployMarket(
false, // isCEther
abi.encode( // CErc20Delegator constructor data
tokens[i], // underlying
troller, // comptroller
FUSE_JUMP_RATE_MODEL, // interestRateModel
"fToken-x", // name
"Fuse pool x Token", // symbol
FUSE_CERC20_DELEGATE, // implementation
bytes(""), // becomeImplementationData
uint256(0), // reserveFactorMantissa
uint256(0) // adminFeeMantissa
),
0.7e18 // collateralFactorMantissa
);
cTokens[i] = Unitroller(troller).cTokensByUnderlying(tokens[i]);
require(
cTokens[i] != address(0),
"Error adding token to Fuse pool"
);
}
}
}