Skip to content

Commit

Permalink
Liquidity Tests
Browse files Browse the repository at this point in the history
- Added a check on strategy withdrawal to ensure that redeeming aTokens or cTokens results in stablecoin withdrawals
- Added `getLendingPeriods` to `ILiquidityEngine`
- Refactored `getLendingPeriodKey` and `getWithdrawalWindowKey` on StrategyLibV1 to drop `ignoreMissingKey` check
- Added more tests to increase coverage
  • Loading branch information
heyaibi committed Apr 8, 2022
1 parent a1e89d4 commit 63a69a8
Show file tree
Hide file tree
Showing 26 changed files with 1,108 additions and 23 deletions.
2 changes: 1 addition & 1 deletion contracts/core/delegates/VaultDelegateBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ abstract contract VaultDelegateBase is IVaultDelegate, Recoverable {
IERC20, /*token*/
bytes32 coverKey,
bytes32 strategyName,
uint256 amount
uint256 /*amount*/
) external override nonReentrant {
s.mustNotBePaused();
s.senderMustBeVaultContract(coverKey);
Expand Down
4 changes: 4 additions & 0 deletions contracts/core/liquidity/LiquidityEngine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ contract LiquidityEngine is ILiquidityEngine, Recoverable {
s.setLendingPeriodsInternal(0, lendingPeriod, withdrawalWindow);
}

function getLendingPeriods(bytes32 coverKey) external view override returns (uint256 lendingPeriod, uint256 withdrawalWindow) {
return s.getLendingPeriodsInternal(coverKey);
}

function getDisabledStrategies() external view override returns (address[] memory strategies) {
return s.getDisabledStrategiesInternal();
}
Expand Down
4 changes: 3 additions & 1 deletion contracts/core/liquidity/strategies/AaveStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ contract AaveStrategy is ILendingStrategy, Recoverable {
// Check how many DAI we received
stablecoinWithdrawn = stablecoin.balanceOf(address(this));

require(stablecoinWithdrawn > 0, "Redeeming aToken failed");

// Immediately send DAI to the vault aToken came from
stablecoin.ensureApproval(address(vault), stablecoinWithdrawn);
vault.receiveFromStrategy(stablecoin, coverKey, getName(), stablecoinWithdrawn);
Expand All @@ -177,7 +179,7 @@ contract AaveStrategy is ILendingStrategy, Recoverable {
return keccak256(abi.encodePacked(_KEY, coverKey, NS_WITHDRAWALS));
}

function getWeight() external pure override returns (uint256) {
function getWeight() external pure virtual override returns (uint256) {
return 10_000; // 100%
}

Expand Down
2 changes: 2 additions & 0 deletions contracts/core/liquidity/strategies/CompoundStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ contract CompoundStrategy is ILendingStrategy, Recoverable {
// Check how many DAI we received
stablecoinWithdrawn = stablecoin.balanceOf(address(this));

require(stablecoinWithdrawn > 0, "Redeeming cDai failed");

// Immediately send DAI to the vault cDAI came from
stablecoin.ensureApproval(address(vault), stablecoinWithdrawn);
vault.receiveFromStrategy(stablecoin, coverKey, getName(), stablecoinWithdrawn);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "../interfaces/external/ICompoundERC20DelegatorLike.sol";
import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";
import "./FakeToken.sol";

contract FakeCompoundERC20Delegator is ICompoundERC20DelegatorLike, ERC20 {
contract FakeCompoundDaiDelegator is ICompoundERC20DelegatorLike, ERC20 {
FakeToken public dai;
FakeToken public cDai;

Expand Down
32 changes: 32 additions & 0 deletions contracts/fakes/FaultyAaveLendingPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Neptune Mutual Protocol (https://neptunemutual.com)
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.0;
import "../interfaces/external/IAaveV2LendingPoolLike.sol";
import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";
import "./FakeToken.sol";

contract FaultyAaveLendingPool is IAaveV2LendingPoolLike, ERC20 {
FakeToken public aToken;

constructor(FakeToken _aToken) ERC20("aDAI", "aDAI") {
aToken = _aToken;
}

function deposit(
address asset,
uint256 amount,
address,
uint16
) external override {
IERC20(asset).transferFrom(msg.sender, address(this), amount);
}

function withdraw(
address, /*asset*/
uint256 amount,
address /*to*/
) external override returns (uint256) {
aToken.transferFrom(msg.sender, address(this), amount);
return amount;
}
}
48 changes: 48 additions & 0 deletions contracts/fakes/FaultyCompoundDaiDelegator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Neptune Mutual Protocol (https://neptunemutual.com)
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.0;
import "../interfaces/external/ICompoundERC20DelegatorLike.sol";
import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";
import "./FakeToken.sol";

contract FaultyCompoundDaiDelegator is ICompoundERC20DelegatorLike, ERC20 {
FakeToken public dai;
FakeToken public cDai;
uint256 public returnValue;

function setReturnValue(uint256 _returnValue) external {
returnValue = _returnValue;
}

constructor(
FakeToken _dai,
FakeToken _cDai,
uint256 _returnValue
) ERC20("cDAI", "cDAI") {
dai = _dai;
cDai = _cDai;
returnValue = _returnValue;
}

/**
* @notice Sender supplies assets into the market and receives cTokens in exchange
* @dev Accrues interest whether or not the operation succeeds, unless reverted
* @param mintAmount The amount of the underlying asset to supply
* @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
*/
function mint(uint256 mintAmount) external override returns (uint256) {
dai.transferFrom(msg.sender, address(this), mintAmount);
return returnValue;
}

/**
* @notice Sender redeems cTokens in exchange for the underlying asset
* @dev Accrues interest whether or not the operation succeeds, unless reverted
* @param redeemTokens The number of cTokens to redeem into underlying
* @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
*/
function redeem(uint256 redeemTokens) external override returns (uint256) {
cDai.transferFrom(msg.sender, address(this), redeemTokens);
return returnValue;
}
}
17 changes: 17 additions & 0 deletions contracts/fakes/InvalidStrategy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Neptune Mutual Protocol (https://neptunemutual.com)
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.0;

import "../core/liquidity/strategies/AaveStrategy.sol";

contract InvalidStrategy is AaveStrategy {
constructor(
IStore _s,
IAaveV2LendingPoolLike _lendingPool,
address _aToken
) AaveStrategy(_s, _lendingPool, _aToken) {} // solhint-disable-line

function getWeight() external pure override returns (uint256) {
return 20_000; // 100%
}
}
2 changes: 2 additions & 0 deletions contracts/interfaces/ILiquidityEngine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ interface ILiquidityEngine is IMember {

function setLendingPeriodsDefault(uint256 lendingPeriod, uint256 withdrawalWindow) external;

function getLendingPeriods(bytes32 coverKey) external view returns (uint256 lendingPeriod, uint256 withdrawalWindow);

function setLiquidityStateUpdateInterval(uint256 value) external;

function getDisabledStrategies() external view returns (address[] memory strategies);
Expand Down
4 changes: 2 additions & 2 deletions contracts/libraries/PriceLibV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,12 @@ library PriceLibV1 {
return (2 * reserve1 * lpTokens) / supply;
}

function getLastUpdateOnInternal(IStore s) public view returns (uint256) {
function getLastUpdateOnInternal(IStore s) external view returns (uint256) {
bytes32 key = getLastUpdateKey();
return s.getUintByKey(key);
}

function setLastUpdateOn(IStore s) public {
function setLastUpdateOn(IStore s) external {
bytes32 key = getLastUpdateKey();
s.setUintByKey(key, block.timestamp); // solhint-disable-line
}
Expand Down
24 changes: 8 additions & 16 deletions contracts/libraries/StrategyLibV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ library StrategyLibV1 {
}

function getLendingPeriodsInternal(IStore s, bytes32 coverKey) external view returns (uint256 lendingPeriod, uint256 withdrawalWindow) {
lendingPeriod = s.getUintByKey(getLendingPeriodKey(coverKey, true));
withdrawalWindow = s.getUintByKey(getWithdrawalWindowKey(coverKey, true));
lendingPeriod = s.getUintByKey(getLendingPeriodKey(coverKey));
withdrawalWindow = s.getUintByKey(getWithdrawalWindowKey(coverKey));

if (lendingPeriod == 0) {
lendingPeriod = s.getUintByKey(getLendingPeriodKey(0, true));
withdrawalWindow = s.getUintByKey(getWithdrawalWindowKey(0, true));
lendingPeriod = s.getUintByKey(getLendingPeriodKey(0));
withdrawalWindow = s.getUintByKey(getWithdrawalWindowKey(0));
}
}

Expand All @@ -53,29 +53,21 @@ library StrategyLibV1 {
uint256 lendingPeriod,
uint256 withdrawalWindow
) external {
s.setUintByKey(getLendingPeriodKey(coverKey, true), lendingPeriod);
s.setUintByKey(getWithdrawalWindowKey(coverKey, true), withdrawalWindow);
s.setUintByKey(getLendingPeriodKey(coverKey), lendingPeriod);
s.setUintByKey(getWithdrawalWindowKey(coverKey), withdrawalWindow);

emit LendingPeriodSet(lendingPeriod, withdrawalWindow);
}

function getLendingPeriodKey(bytes32 coverKey, bool ignoreMissingKey) public pure returns (bytes32) {
if (ignoreMissingKey == false) {
require(coverKey > 0, "Invalid Cover Key");
}

function getLendingPeriodKey(bytes32 coverKey) public pure returns (bytes32) {
if (coverKey > 0) {
return keccak256(abi.encodePacked(ProtoUtilV1.NS_COVER_LIQUIDITY_LENDING_PERIOD, coverKey));
}

return ProtoUtilV1.NS_COVER_LIQUIDITY_LENDING_PERIOD;
}

function getWithdrawalWindowKey(bytes32 coverKey, bool ignoreMissingKey) public pure returns (bytes32) {
if (ignoreMissingKey == false) {
require(coverKey > 0, "Invalid Cover Key");
}

function getWithdrawalWindowKey(bytes32 coverKey) public pure returns (bytes32) {
if (coverKey > 0) {
return keccak256(abi.encodePacked(ProtoUtilV1.NS_COVER_LIQUIDITY_WITHDRAWAL_WINDOW, coverKey));
}
Expand Down
19 changes: 19 additions & 0 deletions test/specs/liquidity/engine/add-strategies.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,25 @@ describe('Liquidity Engine: addStrategies', () => {
await liquidityEngine.connect(bob).addStrategies([aaveStrategy.address]).should.be.rejectedWith('Forbidden')
})

it('reverts when too much weight is specified', async () => {
const aToken = await deployer.deploy(cache, 'FakeToken', 'Neptune Mutual Token', 'NPM', helper.ether(100_000_000))

const aaveLendingPool = await deployer.deploy(cache, 'FakeAaveLendingPool', aToken.address)

const invalidStrategy = await deployer.deployWithLibraries(cache, 'InvalidStrategy', {
AccessControlLibV1: accessControlLibV1.address,
BaseLibV1: baseLibV1.address,
NTransferUtilV2: transferLib.address,
ProtoUtilV1: protoUtilV1.address,
RegistryLibV1: registryLibV1.address,
StoreKeyUtil: storeKeyUtil.address,
ValidationLibV1: validationLibV1.address
}, store.address, aaveLendingPool.address, aToken.address)

await deployed.protocol.addContract(key.PROTOCOL.CNS.STRATEGY_AAVE, invalidStrategy.address)
await liquidityEngine.addStrategies([invalidStrategy.address]).should.be.rejectedWith('Weight too much')
})

it('reverts when protocol is paused', async () => {
const aToken = await deployer.deploy(cache, 'FakeToken', 'Neptune Mutual Token', 'NPM', helper.ether(100_000_000))

Expand Down
5 changes: 5 additions & 0 deletions test/specs/liquidity/engine/disable-strategy.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ describe('Liquidity Engine: disableStrategy', () => {
event.args.strategy.should.equal(aaveStrategy.address)
})

it('must throw while disabling an invalid strategy', async () => {
await liquidityEngine.disableStrategy(helper.randomAddress())
.should.be.rejectedWith('Invalid strategy')
})

it('correctly get disabled strategies', async () => {
await liquidityEngine.disableStrategy(aaveStrategy.address)
const disabledStrategies = await liquidityEngine.getDisabledStrategies()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ describe('Liquidity Engine: `setLendingPeriodsDefault` function', () => {

event.args.lendingPeriod.should.equal(lendingPeriod)
event.args.withdrawalWindow.should.equal(withdrawalWindow)

const result = await liquidityEngine.getLendingPeriods(key.toBytes32(''))
result[0].should.equal(lendingPeriod)
result[1].should.equal(withdrawalWindow)
})

it('reverts when protocol is paused', async () => {
Expand Down
10 changes: 10 additions & 0 deletions test/specs/liquidity/engine/set-lending-periods.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ describe('Liquidity Engine: `setLendingPeriods` function', () => {
await deployed.protocol.addContract(key.PROTOCOL.CNS.LIQUIDITY_ENGINE, liquidityEngine.address)
})

it('correct gets the lending period', async () => {
const result = await liquidityEngine.getLendingPeriods(coverkey)
result[0].should.equal('0')
result[1].should.equal('0')
})

it('correctly sets lending period', async () => {
const lendingPeriod = '10'
const withdrawalWindow = '10'
Expand All @@ -52,6 +58,10 @@ describe('Liquidity Engine: `setLendingPeriods` function', () => {

event.args.lendingPeriod.should.equal(lendingPeriod)
event.args.withdrawalWindow.should.equal(withdrawalWindow)

const result = await liquidityEngine.getLendingPeriods(coverkey)
result[0].should.equal(lendingPeriod)
result[1].should.equal(withdrawalWindow)
})

it('reverts when protocol is paused', async () => {
Expand Down
37 changes: 37 additions & 0 deletions test/specs/liquidity/strategy/aave/ctor.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* eslint-disable no-unused-expressions */
const BigNumber = require('bignumber.js')
const { deployer, key, helper } = require('../../../../../util')
const { deployDependencies } = require('../deps')
const cache = null

require('chai')
.use(require('chai-as-promised'))
.use(require('chai-bignumber')(BigNumber))
.should()

describe('Aave Strategy Constructor', () => {
let deployed, aaveLendingPool, aToken

beforeEach(async () => {
deployed = await deployDependencies()

aToken = await deployer.deploy(cache, 'FakeToken', 'aToken', 'aToken', helper.ether(100_000_000))
aaveLendingPool = await deployer.deploy(cache, 'FakeAaveLendingPool', aToken.address)
})

it('correctly deploys', async () => {
const aaveStrategy = await deployer.deployWithLibraries(cache, 'AaveStrategy', {
AccessControlLibV1: deployed.accessControlLibV1.address,
BaseLibV1: deployed.baseLibV1.address,
NTransferUtilV2: deployed.transferLib.address,
ProtoUtilV1: deployed.protoUtilV1.address,
RegistryLibV1: deployed.registryLibV1.address,
StoreKeyUtil: deployed.storeKeyUtil.address,
ValidationLibV1: deployed.validationLibV1.address
}, deployed.store.address, aaveLendingPool.address, aToken.address)

; (await aaveStrategy.getKey()).should.equal(ethers.utils.solidityKeccak256(['string', 'string', 'string', 'string'], ['lending', 'strategy', 'aave', 'v2']))
; (await aaveStrategy.version()).should.equal(key.toBytes32('v0.1'))
; (await aaveStrategy.getName()).should.equal(key.PROTOCOL.CNAME.STRATEGY_AAVE)
})
})
Loading

0 comments on commit 63a69a8

Please sign in to comment.