Skip to content

Commit

Permalink
Implement missing logic and tests for CDPManager01_Fallback
Browse files Browse the repository at this point in the history
Signed-off-by: 34x4p08 <[email protected]>
  • Loading branch information
chainpioneer committed Apr 12, 2021
1 parent b298395 commit b86c5d1
Show file tree
Hide file tree
Showing 11 changed files with 192 additions and 329 deletions.
8 changes: 8 additions & 0 deletions contracts/interfaces/IKeydonixOracleEth.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pragma abicoder v2;
import "../interfaces/IKeydonixOracleUsd.sol";

interface IKeydonixOracleEth {

// returns Q112-encoded value
function assetToEth(address asset, uint amount, IKeydonixOracleUsd.ProofDataStruct calldata proofData) external view returns (uint);
}
68 changes: 31 additions & 37 deletions contracts/test-helpers/KeydonixOracleMainAsset_Mock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,59 +11,60 @@ import "../helpers/SafeMath.sol";
import "../interfaces/IAggregator.sol";
import "../helpers/IUniswapV2Factory.sol";
import "../interfaces/IKeydonixOracleUsd.sol";
import "../interfaces/IOracleRegistry.sol";
import "../interfaces/IOracleEth.sol";
import "../interfaces/IKeydonixOracleEth.sol";

/**
* @title KeydonixOracleMainAsset_Mock
* @dev Calculates the USD price of desired tokens
**/
contract KeydonixOracleMainAsset_Mock is IKeydonixOracleUsd {
contract KeydonixOracleMainAsset_Mock is IKeydonixOracleEth, IKeydonixOracleUsd {
using SafeMath for uint;

uint public constant ETH_USD_DENOMINATOR = 100000000;
uint public constant ETH_USD_DENOMINATOR = 1e8;

IAggregator public immutable ethUsdChainlinkAggregator;
uint public constant Q112 = 2 ** 112;

IUniswapV2Factory public immutable uniswapFactory;

IOracleRegistry public oracleRegistry;

address public immutable WETH;

constructor(
IUniswapV2Factory uniFactory,
address weth,
IAggregator chainlinkAggregator
IOracleRegistry _oracleRegistry
)
public
{
require(address(uniFactory) != address(0), "Unit Protocol: ZERO_ADDRESS");
require(weth != address(0), "Unit Protocol: ZERO_ADDRESS");
require(address(chainlinkAggregator) != address(0), "Unit Protocol: ZERO_ADDRESS");
require(address(_oracleRegistry) != address(0), "Unit Protocol: ZERO_ADDRESS");

uniswapFactory = uniFactory;
WETH = weth;
ethUsdChainlinkAggregator = chainlinkAggregator;
WETH = _oracleRegistry.WETH();
oracleRegistry = _oracleRegistry;
}

// override with mock; only for tests
function assetToUsd(address asset, uint amount, ProofDataStruct memory proofData) public override view returns (uint) {

if (asset == WETH) {
return ethToUsd(amount);
}

address uniswapPair = uniswapFactory.getPair(asset, WETH);
require(uniswapPair != address(0), "Unit Protocol: UNISWAP_PAIR_DOES_NOT_EXIST");

// token reserve of {Token}/WETH pool
uint tokenReserve = ERC20Like(asset).balanceOf(uniswapPair);

// revert if there is no liquidity
require(tokenReserve != 0, "Unit Protocol: UNISWAP_EMPTY_POOL");

return ethToUsd(assetToEth(asset, amount, proofData)).div(tokenReserve);
/**
* @notice USD token's rate is UniswapV2 Token/WETH pool's average time weighted price between proofs' blockNumber and current block number
* @notice Merkle proof must be in range [MIN_BLOCKS_BACK ... MAX_BLOCKS_BACK] blocks ago
* @notice {Token}/WETH pair must exists on Uniswap
* @param asset The token address
* @param amount Amount of tokens
* @param proofData Merkle proof data
* @return Q112-encoded price of tokens in USD
**/
function assetToUsd(address asset, uint amount, IKeydonixOracleUsd.ProofDataStruct memory proofData) public override view returns (uint) {
uint priceInEth = assetToEth(asset, amount, proofData);
return IOracleEth(oracleRegistry.oracleByAsset(WETH)).ethToUsd(priceInEth);
}

// override with mock; only for tests
function assetToEth(address asset, uint amount, ProofDataStruct memory proofData) public override view returns (uint) {
function assetToEth(address asset, uint amount, IKeydonixOracleUsd.ProofDataStruct memory proofData) public override view returns (uint) {
if (amount == 0) { return 0; }

if (asset == WETH) { return amount.mul(Q112); }

address uniswapPair = uniswapFactory.getPair(asset, WETH);
require(uniswapPair != address(0), "Unit Protocol: UNISWAP_PAIR_DOES_NOT_EXIST");
Expand All @@ -73,16 +74,9 @@ contract KeydonixOracleMainAsset_Mock is IKeydonixOracleUsd {
// WETH reserve of {Token}/WETH pool
uint wethReserve = ERC20Like(WETH).balanceOf(uniswapPair);

return amount.mul(wethReserve).mul(Q112);
}
// Asset reserve of {Token}/WETH pool
uint assetReserve = ERC20Like(asset).balanceOf(uniswapPair);

/**
* @notice ETH/USD price feed from Chainlink, see for more info: https://feeds.chain.link/eth-usd
* returns Price of given amount of Ether in USD (0 decimals)
**/
function ethToUsd(uint ethAmount) public override view returns (uint) {
require(ethUsdChainlinkAggregator.latestTimestamp() > block.timestamp - 6 hours, "Unit Protocol: OUTDATED_CHAINLINK_PRICE");
uint ethUsdPrice = uint(ethUsdChainlinkAggregator.latestAnswer());
return ethAmount.mul(ethUsdPrice).div(ETH_USD_DENOMINATOR);
return amount.mul(wethReserve).mul(Q112).div(assetReserve);
}
}
43 changes: 37 additions & 6 deletions contracts/test-helpers/KeydonixOraclePoolToken_Mock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ pragma experimental ABIEncoderV2;
import "../helpers/IUniswapV2PairFull.sol";
import "../helpers/SafeMath.sol";
import "../interfaces/IKeydonixOracleUsd.sol";
import "../interfaces/IVaultParameters.sol";
import "../interfaces/IOracleRegistry.sol";
import "../interfaces/IOracleEth.sol";

/**
* @title KeydonixOraclePoolToken_Mock
Expand All @@ -17,22 +20,33 @@ import "../interfaces/IKeydonixOracleUsd.sol";
contract KeydonixOraclePoolToken_Mock is IKeydonixOracleUsd {
using SafeMath for uint;

constructor(address _keydonixOracleMainAsset_Mock) public {
uniswapOracleMainAsset = IKeydonixOracleUsd(_keydonixOracleMainAsset_Mock);
uint public constant Q112 = 2 ** 112;

IOracleRegistry public immutable oracleRegistry;

IVaultParameters public immutable vaultParameters;

constructor(address _oracleRegistry, address _vaultParameters) public {
require(_oracleRegistry != address(0), "Unit Protocol: ZERO_ADDRESS");
require(_vaultParameters != address(0), "Unit Protocol: ZERO_ADDRESS");
oracleRegistry = IOracleRegistry(_oracleRegistry);
vaultParameters = IVaultParameters(_vaultParameters);
}

// override with mock; only for tests
function assetToUsd(address asset, uint amount, ProofDataStruct memory proofData) public override view returns (uint) {
function assetToUsd(address asset, uint amount, ProofDataStruct calldata proofData) public override view returns (uint) {

IUniswapV2PairFull pair = IUniswapV2PairFull(asset);

proofData;

uint ePool; // current WETH pool

(uint112 _reserve0, uint112 _reserve1,) = pair.getReserves();

if (pair.token0() == uniswapOracleMainAsset.WETH()) {
if (pair.token0() == oracleRegistry.WETH()) {
ePool = _reserve0;
} else if (pair.token1() == uniswapOracleMainAsset.WETH()) {
} else if (pair.token1() == oracleRegistry.WETH()) {
ePool = _reserve1;
} else {
revert("Unit Protocol: NOT_REGISTERED_PAIR");
Expand All @@ -41,6 +55,23 @@ contract KeydonixOraclePoolToken_Mock is IKeydonixOracleUsd {
uint lpSupply = pair.totalSupply();
uint totalValueInEth_q112 = amount.mul(ePool).mul(2).mul(Q112);

return uniswapOracleMainAsset.ethToUsd(totalValueInEth_q112).div(lpSupply);
return IOracleEth(oracleRegistry.oracleByAsset(oracleRegistry.WETH())).ethToUsd(totalValueInEth_q112).div(lpSupply);
}

function _selectOracle(address asset) internal view returns (address oracle) {
uint oracleType = _getOracleType(asset);
require(oracleType != 0, "Unit Protocol: INVALID_ORACLE_TYPE");
oracle = oracleRegistry.oracleByType(oracleType);
require(oracle != address(0), "Unit Protocol: DISABLED_ORACLE");
}

function _getOracleType(address asset) internal view returns (uint) {
uint[] memory keydonixOracleTypes = oracleRegistry.getKeydonixOracleTypes();
for (uint i = 0; i < keydonixOracleTypes.length; i++) {
if (vaultParameters.isOracleTypeEnabled(keydonixOracleTypes[i], asset)) {
return keydonixOracleTypes[i];
}
}
revert("Unit Protocol: NO_ORACLE_FOUND");
}
}
9 changes: 2 additions & 7 deletions contracts/vault-managers/CDPManager01_Fallback.sol
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ contract CDPManager01_Fallback is ReentrancyGuard {

} else {

// check oracle
uint oracleType = _selectOracleType(asset);

bool spawned = vault.debts(asset, msg.sender) != 0;
Expand Down Expand Up @@ -187,7 +186,7 @@ contract CDPManager01_Fallback is ReentrancyGuard {
}
}

function _ensurePositionCollateralization(address asset, address owner, IKeydonixOracleUsd.ProofDataStruct memory proofData) internal view {
function _ensurePositionCollateralization(address asset, address owner, IKeydonixOracleUsd.ProofDataStruct calldata proofData) internal view {
// collateral value of the position in USD
uint usdValue_q112 = getCollateralUsdValue_q112(asset, owner, proofData);

Expand All @@ -207,8 +206,6 @@ contract CDPManager01_Fallback is ReentrancyGuard {
**/
function triggerLiquidation(address asset, address owner, IKeydonixOracleUsd.ProofDataStruct calldata proofData) external nonReentrant {

uint oracleType = _selectOracleType(asset);

// USD value of the collateral
uint usdValue_q112 = getCollateralUsdValue_q112(asset, owner, proofData);

Expand Down Expand Up @@ -289,9 +286,7 @@ contract CDPManager01_Fallback is ReentrancyGuard {
IKeydonixOracleUsd.ProofDataStruct calldata proofData
) public view returns (uint) {
uint debt = vault.getTotalDebt(asset, owner);
if (debt == 0) return uint(-1);

uint oracleType = _getOracleType(asset);
if (debt == 0) return uint(0);

uint usdValue_q112 = getCollateralUsdValue_q112(asset, owner, proofData);

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"test": "truffle test",
"test:curve": "truffle test test/CDPManager_WrappedAssets.test.js test/LiquidationTrigger_WrappedAssets.test.js test/LiquidationAuction.test.js",
"test:single-point": "truffle test test/*Keep3r*.test.js test/*Wrapped*.test.js test/Li*Bearing*.test.js test/CDP*Bearing*.test.js test/LiquidationAuction.test.js test/LiquidationTrigger_Chainlink.test.js test/LiquidationTrigger_PoolToken_Chainlink.test.js",
"test:keydonix": "truffle test test/*Keydonix*.test.js",
"build": "rm -rf build && truffle compile",
"coverage": "truffle run coverage"
},
Expand Down
96 changes: 0 additions & 96 deletions test/CDPManager_Keydonix.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ const {
expectEvent,
ether,
} = require('openzeppelin-test-helpers');
const balance = require('./helpers/balances');
const BN = web3.utils.BN;
const { expect } = require('chai');
const utils = require('./helpers/utils');
Expand Down Expand Up @@ -45,31 +44,6 @@ const time = require('./helpers/time');
expect(mainAmountInPosition).to.be.bignumber.equal(mainAmount);
expect(usdpBalance).to.be.bignumber.equal(usdpAmount);
})

it('Should spawn position using ETH', async function() {
const mainAmount = ether('2');
const usdpAmount = ether('1');

const wethInVaultBefore = await this.weth.balanceOf(this.vault.address);

const { logs } = await this.utils.spawnEth(mainAmount, usdpAmount);

expectEvent.inLogs(logs, 'Join', {
asset: this.weth.address,
owner: deployer,
main: mainAmount,
usdp: usdpAmount,
});

const wethInVaultAfter = await this.weth.balanceOf(this.vault.address);
expect(wethInVaultAfter.sub(wethInVaultBefore)).to.be.bignumber.equal(mainAmount);

const mainAmountInPosition = await this.vault.collaterals(this.weth.address, deployer);
const usdpBalance = await this.usdp.balanceOf(deployer);

expect(mainAmountInPosition).to.be.bignumber.equal(mainAmount);
expect(usdpBalance).to.be.bignumber.equal(usdpAmount);
})
})

describe('Repay & withdraw', function() {
Expand Down Expand Up @@ -114,7 +88,6 @@ const time = require('./helpers/time');

// get some usdp to cover fee
await this.utils.updatePrice();
await this.utils.spawnEth(ether('2'), ether('1'), ether('2'));

// repay debt partially
await this.utils.repay(this.mainCollateral, deployer, usdpAmount.div(new BN(2)));
Expand All @@ -124,7 +97,6 @@ const time = require('./helpers/time');
expectedDebt.div(new BN(2)).div(new BN(10 ** 12))
);

await this.utils.repayAllAndWithdraw(this.mainCollateral, deployer);
})

it('Should partially repay the debt of a position and withdraw collaterals partially', async function() {
Expand All @@ -151,60 +123,6 @@ const time = require('./helpers/time');
expect(mainAmountInPosition).to.be.bignumber.equal(mainAmount.sub(mainToWithdraw));
expect(usdpInPosition).to.be.bignumber.equal(usdpAmount.sub(usdpToWithdraw));
})

it('Should partially repay the debt of a position and withdraw collaterals partially using ETH', async function() {
const mainAmount = ether('2');
const usdpAmount = ether('1');

await this.utils.spawnEth(mainAmount, usdpAmount);

const mainToWithdraw = ether('1');
const usdpToWithdraw = ether('0.5');

const wethBalanceBefore = await balance.current(this.weth.address);

const { logs } = await this.utils.withdrawAndRepayEth(mainToWithdraw, usdpToWithdraw);

expectEvent.inLogs(logs, 'Exit', {
asset: this.weth.address,
owner: deployer,
main: mainToWithdraw,
usdp: usdpToWithdraw,
});

const mainAmountInPosition = await this.vault.collaterals(this.weth.address, deployer);
const usdpInPosition = await this.vault.debts(this.weth.address, deployer);
const wethBalanceAfter = await balance.current(this.weth.address);

expect(mainAmountInPosition).to.be.bignumber.equal(mainAmount.sub(mainToWithdraw));
expect(usdpInPosition).to.be.bignumber.equal(usdpAmount.sub(usdpToWithdraw));
expect(wethBalanceBefore.sub(wethBalanceAfter)).to.be.bignumber.equal(mainToWithdraw);
})

it('Should repay the debt of a position and withdraw collaterals using ETH', async function() {
const mainAmount = ether('2');
const usdpAmount = ether('1');

await this.utils.spawnEth(mainAmount, usdpAmount);

const wethInVaultBefore = await this.weth.balanceOf(this.vault.address);

const { logs } = await this.utils.repayAllAndWithdrawEth(deployer);

expectEvent.inLogs(logs, 'Exit', {
asset: this.weth.address,
owner: deployer,
main: mainAmount,
usdp: usdpAmount,
});

const wethInVaultAfter = await this.weth.balanceOf(this.vault.address);

const mainAmountInPosition = await this.vault.collaterals(this.weth.address, deployer);

expect(mainAmountInPosition).to.be.bignumber.equal(new BN(0));
expect(wethInVaultBefore.sub(wethInVaultAfter)).to.be.bignumber.equal(mainAmount);
})
})

it('Should deposit collaterals to position and mint USDP', async function () {
Expand Down Expand Up @@ -296,20 +214,6 @@ const time = require('./helpers/time');
})
})

describe('Join', function () {
it('Reverts non-spawned position', async function() {
const mainAmount = ether('100');
const usdpAmount = ether('20');

const tx = this.utils.join(
this.mainCollateral,
mainAmount,
usdpAmount,
);
await this.utils.expectRevert(tx, "Unit Protocol: NOT_SPAWNED_POSITION");
})
})

describe('Exit', function () {
it('Reverts non valuable tx', async function() {
const mainAmount = ether('100');
Expand Down
Loading

0 comments on commit b86c5d1

Please sign in to comment.