Merge branch 'master' into feat/data-helpers

This commit is contained in:
andyk 2020-10-27 16:01:18 +03:00
commit 3400d3b2a8
83 changed files with 2457 additions and 12163 deletions

View File

@ -11,7 +11,7 @@ usePlugin('buidler-typechain');
usePlugin('solidity-coverage');
usePlugin('@nomiclabs/buidler-waffle');
usePlugin('@nomiclabs/buidler-etherscan');
//usePlugin('buidler-gas-reporter');
usePlugin('buidler-gas-reporter');
const SKIP_LOAD = process.env.SKIP_LOAD === 'true';
const DEFAULT_BLOCK_GAS_LIMIT = 10000000;
@ -90,6 +90,16 @@ const buidlerConfig: any = {
balance,
})),
},
buidlerevm_docker: {
hardfork: 'istanbul',
blockGasLimit: 9500000,
gas: 9500000,
gasPrice: 8000000000,
chainId: BUIDLEREVM_CHAINID,
throwOnTransactionFailures: true,
throwOnCallFailures: true,
url: 'http://localhost:8545',
},
ganache: {
url: 'http://ganache:8545',
accounts: {

View File

@ -270,7 +270,7 @@ export const AaveConfig: IAaveConfiguration = {
MKR: '0x61e4CAE3DA7FD189e52a4879C7B8067D7C2Cc0FA',
LINK: '0xAD5ce863aE3E4E9394Ab43d4ba0D80f419F61789',
KNC: '0x3F80c39c0b96A0945f9F0E9f55d8A8891c5671A8',
WBTC: '0x3b92f58feD223E2cB1bCe4c286BD97e42f2A12EA',
WBTC: '0xD1B98B6607330172f1D991521145A22BCe793277',
MANA: '0x738Dc6380157429e957d223e6333Dc385c85Fec7',
ZRX: '0xD0d76886cF8D952ca26177EB7CfDf83bad08C00C',
SNX: '0x7FDb81B0b8a010dd4FFc57C3fecbf145BA8Bd947',

View File

@ -258,4 +258,11 @@ export const CommonsConfig: ICommonConfiguration = {
[eEthereumNetwork.ropsten]: '',
[eEthereumNetwork.main]: '',
},
ProxyPriceProvider: {
[eEthereumNetwork.coverage]: '',
[eEthereumNetwork.buidlerevm]: '',
[eEthereumNetwork.kovan]: '0x276C4793F2EE3D5Bf18C5b879529dD4270BA4814',
[eEthereumNetwork.ropsten]: '',
[eEthereumNetwork.main]: '',
},
};

View File

@ -1,10 +1,10 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {Ownable} from '../dependencies/openzeppelin/contracts/Ownable.sol';
import {
InitializableAdminUpgradeabilityProxy
} from '../libraries/openzeppelin-upgradeability/InitializableAdminUpgradeabilityProxy.sol';
InitializableImmutableAdminUpgradeabilityProxy
} from '../libraries/aave-upgradeability/InitializableImmutableAdminUpgradeabilityProxy.sol';
import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol';
@ -153,14 +153,14 @@ contract LendingPoolAddressesProvider is Ownable, ILendingPoolAddressesProvider
function _updateImpl(bytes32 id, address newAddress) internal {
address payable proxyAddress = payable(_addresses[id]);
InitializableAdminUpgradeabilityProxy proxy = InitializableAdminUpgradeabilityProxy(
proxyAddress
);
InitializableImmutableAdminUpgradeabilityProxy proxy
= InitializableImmutableAdminUpgradeabilityProxy(proxyAddress);
bytes memory params = abi.encodeWithSignature('initialize(address)', address(this));
if (proxyAddress == address(0)) {
proxy = new InitializableAdminUpgradeabilityProxy();
proxy.initialize(newAddress, address(this), params);
proxy = new InitializableImmutableAdminUpgradeabilityProxy(address(this));
proxy.initialize(newAddress, params);
_addresses[id] = address(proxy);
emit ProxyCreated(id, address(proxy));
} else {

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {Ownable} from '../dependencies/openzeppelin/contracts/Ownable.sol';
import {
ILendingPoolAddressesProviderRegistry
} from '../interfaces/ILendingPoolAddressesProviderRegistry.sol';
@ -36,13 +36,15 @@ contract LendingPoolAddressesProviderRegistry is Ownable, ILendingPoolAddressesP
* @return the list of addressesProviders
**/
function getAddressesProvidersList() external override view returns (address[] memory) {
uint256 maxLength = _addressesProvidersList.length;
address[] memory addressesProvidersList = _addressesProvidersList;
uint256 maxLength = addressesProvidersList.length;
address[] memory activeProviders = new address[](maxLength);
for (uint256 i = 0; i < _addressesProvidersList.length; i++) {
if (_addressesProviders[_addressesProvidersList[i]] > 0) {
activeProviders[i] = _addressesProvidersList[i];
for (uint256 i = 0; i < maxLength; i++) {
if (_addressesProviders[addressesProvidersList[i]] > 0) {
activeProviders[i] = addressesProvidersList[i];
}
}
@ -54,6 +56,8 @@ contract LendingPoolAddressesProviderRegistry is Ownable, ILendingPoolAddressesP
* @param provider the pool address to be registered
**/
function registerAddressesProvider(address provider, uint256 id) external override onlyOwner {
require(id != 0, Errors.INVALID_ADDRESSES_PROVIDER_ID);
_addressesProviders[provider] = id;
_addToAddressesProvidersList(provider);
emit AddressesProviderRegistered(provider);
@ -74,7 +78,9 @@ contract LendingPoolAddressesProviderRegistry is Ownable, ILendingPoolAddressesP
* @param provider the pool address to be added
**/
function _addToAddressesProvidersList(address provider) internal {
for (uint256 i = 0; i < _addressesProvidersList.length; i++) {
uint256 providersCount = _addressesProvidersList.length;
for (uint256 i = 0; i < providersCount; i++) {
if (_addressesProvidersList[i] == provider) {
return;
}

View File

@ -0,0 +1,344 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import './Context.sol';
import './IERC20.sol';
import './SafeMath.sol';
import './Address.sol';
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* We have followed general OpenZeppelin guidelines: functions revert instead
* of returning `false` on failure. This behavior is nonetheless conventional
* and does not conflict with the expectations of ERC20 applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/
contract ERC20 is Context, IERC20 {
using SafeMath for uint256;
using Address for address;
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
uint8 private _decimals;
/**
* @dev Sets the values for {name} and {symbol}, initializes {decimals} with
* a default value of 18.
*
* To select a different value for {decimals}, use {_setupDecimals}.
*
* All three of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name, string memory symbol) public {
_name = name;
_symbol = symbol;
_decimals = 18;
}
/**
* @dev Returns the name of the token.
*/
function name() public view returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5,05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
* called.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view returns (uint8) {
return _decimals;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public override view returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public override view returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `recipient` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
_transfer(_msgSender(), recipient, amount);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender)
public
virtual
override
view
returns (uint256)
{
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 amount) public virtual override returns (bool) {
_approve(_msgSender(), spender, amount);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20};
*
* Requirements:
* - `sender` and `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
* - the caller must have allowance for ``sender``'s tokens of at least
* `amount`.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) public virtual override returns (bool) {
_transfer(sender, recipient, amount);
_approve(
sender,
_msgSender(),
_allowances[sender][_msgSender()].sub(amount, 'ERC20: transfer amount exceeds allowance')
);
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
_approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue)
public
virtual
returns (bool)
{
_approve(
_msgSender(),
spender,
_allowances[_msgSender()][spender].sub(
subtractedValue,
'ERC20: decreased allowance below zero'
)
);
return true;
}
/**
* @dev Moves tokens `amount` from `sender` to `recipient`.
*
* This is internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `sender` cannot be the zero address.
* - `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
*/
function _transfer(
address sender,
address recipient,
uint256 amount
) internal virtual {
require(sender != address(0), 'ERC20: transfer from the zero address');
require(recipient != address(0), 'ERC20: transfer to the zero address');
_beforeTokenTransfer(sender, recipient, amount);
_balances[sender] = _balances[sender].sub(amount, 'ERC20: transfer amount exceeds balance');
_balances[recipient] = _balances[recipient].add(amount);
emit Transfer(sender, recipient, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements
*
* - `to` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), 'ERC20: mint to the zero address');
_beforeTokenTransfer(address(0), account, amount);
_totalSupply = _totalSupply.add(amount);
_balances[account] = _balances[account].add(amount);
emit Transfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), 'ERC20: burn from the zero address');
_beforeTokenTransfer(account, address(0), amount);
_balances[account] = _balances[account].sub(amount, 'ERC20: burn amount exceeds balance');
_totalSupply = _totalSupply.sub(amount);
emit Transfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
*
* This is internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(
address owner,
address spender,
uint256 amount
) internal virtual {
require(owner != address(0), 'ERC20: approve from the zero address');
require(spender != address(0), 'ERC20: approve to the zero address');
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Sets {decimals} to a value other than the default one of 18.
*
* WARNING: This function should only be called from the constructor. Most
* applications that interact with token contracts will not expect
* {decimals} to ever change, and may work incorrectly if it does.
*/
function _setupDecimals(uint8 decimals_) internal {
_decimals = decimals_;
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be to transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
}

View File

@ -0,0 +1,69 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import './Context.sol';
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() internal {
address msgSender = _msgSender();
_owner = msgSender;
emit OwnershipTransferred(address(0), msgSender);
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(_owner == _msgSender(), 'Ownable: caller is not the owner');
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), 'Ownable: new owner is the zero address');
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}

View File

@ -2,8 +2,8 @@
pragma solidity 0.6.8;
import {IERC20} from '../interfaces/IERC20.sol';
import {SafeMath} from '../libraries/math/SafeMath.sol';
import {IERC20} from './IERC20.sol';
import {SafeMath} from './SafeMath.sol';
import {Address} from './Address.sol';
/**

View File

@ -2,7 +2,7 @@
pragma solidity ^0.6.8;
import './Proxy.sol';
import '@openzeppelin/contracts/utils/Address.sol';
import '../contracts/Address.sol';
/**
* @title BaseUpgradeabilityProxy

View File

@ -1,12 +1,11 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {SafeMath} from '../../dependencies/openzeppelin/contracts/SafeMath.sol';
import {IERC20} from '../../dependencies/openzeppelin/contracts/IERC20.sol';
import {IFlashLoanReceiver} from '../interfaces/IFlashLoanReceiver.sol';
import {ILendingPoolAddressesProvider} from '../../interfaces/ILendingPoolAddressesProvider.sol';
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import '@nomiclabs/buidler/console.sol';
import {SafeERC20} from '../../dependencies/openzeppelin/contracts/SafeERC20.sol';
abstract contract FlashLoanReceiverBase is IFlashLoanReceiver {
using SafeERC20 for IERC20;

View File

@ -9,9 +9,9 @@ pragma solidity ^0.6.8;
**/
interface IFlashLoanReceiver {
function executeOperation(
address reserve,
uint256 amount,
uint256 fee,
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
bytes calldata params
) external returns (bool);
}

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
interface IExchangeAdapter {
event Exchange(

View File

@ -1,7 +1,6 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import {LendingPoolAddressesProvider} from '../configuration/LendingPoolAddressesProvider.sol';
import {ReserveConfiguration} from '../libraries/configuration/ReserveConfiguration.sol';
import {UserConfiguration} from '../libraries/configuration/UserConfiguration.sol';
import {ReserveLogic} from '../libraries/logic/ReserveLogic.sol';
@ -100,46 +99,20 @@ interface ILendingPool {
/**
* @dev emitted when a flashloan is executed
* @param target the address of the flashLoanReceiver
* @param reserve the address of the reserve
* @param amount the amount requested
* @param totalPremium the total fee on the amount
* @param assets the address of the assets being flashborrowed
* @param amounts the amount requested
* @param premiums the total fee on the amount
* @param referralCode the referral code of the caller
**/
event FlashLoan(
address indexed target,
address indexed reserve,
uint256 amount,
uint256 totalPremium,
uint256 mode,
address[] assets,
uint256[] amounts,
uint256[] premiums,
uint16 referralCode
);
/**
* @dev these events are not emitted directly by the LendingPool
* but they are declared here as the LendingPoolCollateralManager
* is executed using a delegateCall().
* This allows to have the events in the generated ABI for LendingPool.
**/
/**
* @dev emitted when a borrower is liquidated
* @param collateral the address of the collateral being liquidated
* @param reserve the address of the reserve
* @param user the address of the user being liquidated
* @param purchaseAmount the total amount liquidated
* @param liquidatedCollateralAmount the amount of collateral being liquidated
* @param accruedBorrowInterest the amount of interest accrued by the borrower since the last action
* @param liquidator the address of the liquidator
* @param receiveAToken true if the liquidator wants to receive aTokens, false otherwise
**/
event LiquidationCall(
address indexed collateral,
address indexed reserve,
address indexed user,
uint256 purchaseAmount,
uint256 liquidatedCollateralAmount,
uint256 accruedBorrowInterest,
address liquidator,
bool receiveAToken
);
/**
* @dev Emitted when the pause is triggered.
*/
@ -150,6 +123,51 @@ interface ILendingPool {
*/
event Unpaused();
/**
* @dev emitted when a borrower is liquidated. Thos evemt is emitted directly by the LendingPool
* but it's declared here as the LendingPoolCollateralManager
* is executed using a delegateCall().
* This allows to have the events in the generated ABI for LendingPool.
* @param collateral the address of the collateral being liquidated
* @param principal the address of the reserve
* @param user the address of the user being liquidated
* @param purchaseAmount the total amount liquidated
* @param liquidatedCollateralAmount the amount of collateral being liquidated
* @param liquidator the address of the liquidator
* @param receiveAToken true if the liquidator wants to receive aTokens, false otherwise
**/
event LiquidationCall(
address indexed collateral,
address indexed principal,
address indexed user,
uint256 purchaseAmount,
uint256 liquidatedCollateralAmount,
address liquidator,
bool receiveAToken
);
/**
* @dev Emitted when the state of a reserve is updated. NOTE: This event is actually declared
* in the ReserveLogic library and emitted in the updateInterestRates() function. Since the function is internal,
* the event will actually be fired by the LendingPool contract. The event is therefore replicated here so it
* gets added to the LendingPool ABI
* @param reserve the address of the reserve
* @param liquidityRate the new liquidity rate
* @param stableBorrowRate the new stable borrow rate
* @param variableBorrowRate the new variable borrow rate
* @param liquidityIndex the new liquidity index
* @param variableBorrowIndex the new variable borrow index
**/
event ReserveDataUpdated(
address indexed reserve,
uint256 liquidityRate,
uint256 stableBorrowRate,
uint256 averageStableBorrowRate,
uint256 variableBorrowRate,
uint256 liquidityIndex,
uint256 variableBorrowIndex
);
/**
* @dev deposits The underlying asset into the reserve. A corresponding amount of the overlying asset (aTokens)
* is minted.
@ -262,62 +280,26 @@ interface ILendingPool {
bool receiveAToken
) external;
/**
* @dev flashes the underlying collateral on an user to swap for the owed asset and repay
* - Both the owner of the position and other liquidators can execute it
* - The owner can repay with his collateral at any point, no matter the health factor
* - Other liquidators can only use this function below 1 HF. To liquidate 50% of the debt > HF 0.98 or the whole below
* @param collateral The address of the collateral asset
* @param principal The address of the owed asset
* @param user Address of the borrower
* @param principalAmount Amount of the debt to repay. type(uint256).max to repay the maximum possible
* @param receiver Address of the contract receiving the collateral to swap
* @param params Variadic bytes param to pass with extra information to the receiver
**/
function repayWithCollateral(
address collateral,
address principal,
address user,
uint256 principalAmount,
address receiver,
bytes calldata params
) external;
/**
* @dev allows smartcontracts to access the liquidity of the pool within one transaction,
* as long as the amount taken plus a fee is returned. NOTE There are security concerns for developers of flashloan receiver contracts
* that must be kept into consideration. For further details please visit https://developers.aave.com
* @param receiver The address of the contract receiving the funds. The receiver should implement the IFlashLoanReceiver interface.
* @param reserve the address of the principal reserve
* @param amount the amount requested for this flashloan
* @param assets the address of the principal reserve
* @param amounts the amount requested for this flashloan
* @param mode the flashloan mode
* @param params a bytes array to be sent to the flashloan executor
* @param referralCode the referral code of the caller
**/
function flashLoan(
address receiver,
address reserve,
uint256 amount,
uint256 debtType,
address[] calldata assets,
uint256[] calldata amounts,
uint256 mode,
bytes calldata params,
uint16 referralCode
) external;
/**
* @dev Allows an user to release one of his assets deposited in the protocol, even if it is used as collateral, to swap for another.
* - It's not possible to release one asset to swap for the same
* @param receiverAddress The address of the contract receiving the funds. The receiver should implement the ISwapAdapter interface
* @param fromAsset Asset to swap from
* @param toAsset Asset to swap to
* @param params a bytes array to be sent (if needed) to the receiver contract with extra data
**/
function swapLiquidity(
address receiverAddress,
address fromAsset,
address toAsset,
uint256 amountToSwap,
bytes calldata params
) external;
function getUserAccountData(address user)
external
view

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol';
import {SafeMath} from '../dependencies/openzeppelin/contracts/SafeMath.sol';
import {IReserveInterestRateStrategy} from '../interfaces/IReserveInterestRateStrategy.sol';
import {WadRayMath} from '../libraries/math/WadRayMath.sol';
import {PercentageMath} from '../libraries/math/PercentageMath.sol';
@ -162,7 +162,7 @@ contract DefaultReserveInterestRateStrategy is IReserveInterestRateStrategy {
_stableRateSlope1.rayMul(utilizationRate.rayDiv(OPTIMAL_UTILIZATION_RATE))
);
vars.currentVariableBorrowRate = _baseVariableBorrowRate.add(
utilizationRate.rayDiv(OPTIMAL_UTILIZATION_RATE).rayMul(_variableRateSlope1)
utilizationRate.rayMul(_variableRateSlope1).rayDiv(OPTIMAL_UTILIZATION_RATE)
);
}

View File

@ -2,11 +2,9 @@
pragma solidity ^0.6.8;
pragma experimental ABIEncoderV2;
import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {
VersionedInitializable
} from '../libraries/openzeppelin-upgradeability/VersionedInitializable.sol';
import {SafeMath} from '../dependencies/openzeppelin/contracts/SafeMath.sol';
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
import {VersionedInitializable} from '../libraries/aave-upgradeability/VersionedInitializable.sol';
import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol';
import {IAToken} from '../tokenization/interfaces/IAToken.sol';
import {Helpers} from '../libraries/helpers/Helpers.sol';
@ -25,7 +23,7 @@ import {IFlashLoanReceiver} from '../flashloan/interfaces/IFlashLoanReceiver.sol
import {ISwapAdapter} from '../interfaces/ISwapAdapter.sol';
import {LendingPoolCollateralManager} from './LendingPoolCollateralManager.sol';
import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol';
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import {SafeERC20} from '../dependencies/openzeppelin/contracts/SafeERC20.sol';
import {ILendingPool} from '../interfaces/ILendingPool.sol';
import {LendingPoolStorage} from './LendingPoolStorage.sol';
import {IReserveInterestRateStrategy} from '../interfaces/IReserveInterestRateStrategy.sol';
@ -106,13 +104,12 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
reserve.updateState();
reserve.updateInterestRates(asset, aToken, amount, 0);
bool isFirstDeposit = IAToken(aToken).balanceOf(onBehalfOf) == 0;
bool isFirstDeposit = IAToken(aToken).mint(onBehalfOf, amount, reserve.liquidityIndex);
if (isFirstDeposit) {
_usersConfig[onBehalfOf].setUsingAsCollateral(reserve.id, true);
}
IAToken(aToken).mint(onBehalfOf, amount, reserve.liquidityIndex);
//transfer to the aToken contract
IERC20(asset).safeTransferFrom(msg.sender, aToken, amount);
@ -160,7 +157,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
IAToken(aToken).burn(msg.sender, msg.sender, amountToWithdraw, reserve.liquidityIndex);
emit Withdraw(asset, msg.sender, amount);
emit Withdraw(asset, msg.sender, amountToWithdraw);
}
/**
@ -265,15 +262,6 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
ReserveLogic.InterestRateMode interestRateMode = ReserveLogic.InterestRateMode(rateMode);
//default to max amount
uint256 paybackAmount = interestRateMode == ReserveLogic.InterestRateMode.STABLE
? stableDebt
: variableDebt;
if (amount != type(uint256).max && amount < paybackAmount) {
paybackAmount = amount;
}
ValidationLogic.validateRepay(
reserve,
amount,
@ -283,6 +271,15 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
variableDebt
);
//default to max amount
uint256 paybackAmount = interestRateMode == ReserveLogic.InterestRateMode.STABLE
? stableDebt
: variableDebt;
if (amount < paybackAmount) {
paybackAmount = amount;
}
reserve.updateState();
//burns an equivalent amount of debt tokens
@ -359,9 +356,10 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
}
/**
* @dev rebalances the stable interest rate of a user if current liquidity rate > user stable rate.
* this is regulated by Aave to ensure that the protocol is not abused, and the user is paying a fair
* rate. Anyone can call this function.
* @dev rebalances the stable interest rate of a user. Users can be rebalanced if the following conditions are satisfied:
* 1. Usage ratio is above 95%
* 2. the current deposit APY is below REBALANCE_UP_THRESHOLD * maxVariableBorrowRate, which means that too much has been
* borrowed at a stable rate and depositors are not earning enough.
* @param asset the address of the reserve
* @param user the address of the user to be rebalanced
**/
@ -376,7 +374,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
uint256 stableBorrowBalance = IERC20(stableDebtToken).balanceOf(user);
//if the utilization rate is below 95%, no rebalances are needed
//if the usage ratio is below 95%, no rebalances are needed
uint256 totalBorrows = stableDebtToken
.totalSupply()
.add(variableDebtToken.totalSupply())
@ -420,7 +418,7 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
/**
* @dev allows depositors to enable or disable a specific deposit as collateral.
* @param asset the address of the reserve
* @param useAsCollateral true if the user wants to user the deposit as collateral, false otherwise.
* @param useAsCollateral true if the user wants to use the deposit as collateral, false otherwise.
**/
function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external override {
_whenNotPaused();
@ -485,61 +483,16 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
}
}
/**
* @dev flashes the underlying collateral on an user to swap for the owed asset and repay
* - Both the owner of the position and other liquidators can execute it
* - The owner can repay with his collateral at any point, no matter the health factor
* - Other liquidators can only use this function below 1 HF. To liquidate 50% of the debt > HF 0.98 or the whole below
* @param collateral The address of the collateral asset
* @param principal The address of the owed asset
* @param user Address of the borrower
* @param principalAmount Amount of the debt to repay. type(uint256).max to repay the maximum possible
* @param receiver Address of the contract receiving the collateral to swap
* @param params Variadic bytes param to pass with extra information to the receiver
**/
function repayWithCollateral(
address collateral,
address principal,
address user,
uint256 principalAmount,
address receiver,
bytes calldata params
) external override {
_whenNotPaused();
require(!_flashLiquidationLocked, Errors.REENTRANCY_NOT_ALLOWED);
_flashLiquidationLocked = true;
address collateralManager = _addressesProvider.getLendingPoolCollateralManager();
//solium-disable-next-line
(bool success, bytes memory result) = collateralManager.delegatecall(
abi.encodeWithSignature(
'repayWithCollateral(address,address,address,uint256,address,bytes)',
collateral,
principal,
user,
principalAmount,
receiver,
params
)
);
require(success, Errors.FAILED_REPAY_WITH_COLLATERAL);
(uint256 returnCode, string memory returnMessage) = abi.decode(result, (uint256, string));
if (returnCode != 0) {
revert(string(abi.encodePacked(returnMessage)));
}
_flashLiquidationLocked = false;
}
struct FlashLoanLocalVars {
uint256 premium;
uint256 amountPlusPremium;
IFlashLoanReceiver receiver;
address aTokenAddress;
address oracle;
ReserveLogic.InterestRateMode debtMode;
uint256 i;
address currentAsset;
address currentATokenAddress;
uint256 currentAmount;
uint256 currentPremium;
uint256 currentAmountPlusPremium;
}
/**
@ -547,106 +500,90 @@ contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage
* as long as the amount taken plus a fee is returned. NOTE There are security concerns for developers of flashloan receiver contracts
* that must be kept into consideration. For further details please visit https://developers.aave.com
* @param receiverAddress The address of the contract receiving the funds. The receiver should implement the IFlashLoanReceiver interface.
* @param asset The address of the principal reserve
* @param amount The amount requested for this flashloan
* @param assets The addresss of the assets being flashborrowed
* @param amounts The amounts requested for this flashloan for each asset
* @param mode Type of the debt to open if the flash loan is not returned. 0 -> Don't open any debt, just revert, 1 -> stable, 2 -> variable
* @param params Variadic packed params to pass to the receiver as extra information
* @param referralCode Referral code of the flash loan
**/
function flashLoan(
address receiverAddress,
address asset,
uint256 amount,
address[] calldata assets,
uint256[] calldata amounts,
uint256 mode,
bytes calldata params,
uint16 referralCode
) external override {
_whenNotPaused();
ReserveLogic.ReserveData storage reserve = _reserves[asset];
FlashLoanLocalVars memory vars;
vars.aTokenAddress = reserve.aTokenAddress;
ValidationLogic.validateFlashloan(assets, amounts, mode);
vars.premium = amount.mul(FLASHLOAN_PREMIUM_TOTAL).div(10000);
ValidationLogic.validateFlashloan(mode, vars.premium);
ReserveLogic.InterestRateMode debtMode = ReserveLogic.InterestRateMode(mode);
address[] memory aTokenAddresses = new address[](assets.length);
uint256[] memory premiums = new uint256[](assets.length);
vars.receiver = IFlashLoanReceiver(receiverAddress);
vars.debtMode = ReserveLogic.InterestRateMode(mode);
//transfer funds to the receiver
IAToken(vars.aTokenAddress).transferUnderlyingTo(receiverAddress, amount);
for (vars.i = 0; vars.i < assets.length; vars.i++) {
aTokenAddresses[vars.i] = _reserves[assets[vars.i]].aTokenAddress;
premiums[vars.i] = amounts[vars.i].mul(FLASHLOAN_PREMIUM_TOTAL).div(10000);
//transfer funds to the receiver
IAToken(aTokenAddresses[vars.i]).transferUnderlyingTo(receiverAddress, amounts[vars.i]);
}
//execute action of the receiver
require(
vars.receiver.executeOperation(asset, amount, vars.premium, params),
vars.receiver.executeOperation(assets, amounts, premiums, params),
Errors.INVALID_FLASH_LOAN_EXECUTOR_RETURN
);
vars.amountPlusPremium = amount.add(vars.premium);
for (vars.i = 0; vars.i < assets.length; vars.i++) {
vars.currentAsset = assets[vars.i];
vars.currentAmount = amounts[vars.i];
vars.currentPremium = premiums[vars.i];
vars.currentATokenAddress = aTokenAddresses[vars.i];
if (debtMode == ReserveLogic.InterestRateMode.NONE) {
IERC20(asset).safeTransferFrom(receiverAddress, vars.aTokenAddress, vars.amountPlusPremium);
vars.currentAmountPlusPremium = vars.currentAmount.add(vars.currentPremium);
reserve.updateState();
reserve.cumulateToLiquidityIndex(IERC20(vars.aTokenAddress).totalSupply(), vars.premium);
reserve.updateInterestRates(asset, vars.aTokenAddress, vars.premium, 0);
if (vars.debtMode == ReserveLogic.InterestRateMode.NONE) {
_reserves[vars.currentAsset].updateState();
_reserves[vars.currentAsset].cumulateToLiquidityIndex(
IERC20(vars.currentATokenAddress).totalSupply(),
vars.currentPremium
);
_reserves[vars.currentAsset].updateInterestRates(
vars.currentAsset,
vars.currentATokenAddress,
vars.currentPremium,
0
);
emit FlashLoan(receiverAddress, asset, amount, vars.premium, referralCode);
} else {
//if the user didn't choose to return the funds, the system checks if there
//is enough collateral and eventually open a position
_executeBorrow(
ExecuteBorrowParams(
asset,
msg.sender,
msg.sender,
vars.amountPlusPremium,
mode,
vars.aTokenAddress,
referralCode,
false
)
);
}
}
/**
* @dev Allows an user to release one of his assets deposited in the protocol, even if it is used as collateral, to swap for another.
* - It's not possible to release one asset to swap for the same
* @param receiverAddress The address of the contract receiving the funds. The receiver should implement the ISwapAdapter interface
* @param fromAsset Asset to swap from
* @param toAsset Asset to swap to
* @param params a bytes array to be sent (if needed) to the receiver contract with extra data
**/
function swapLiquidity(
address receiverAddress,
address fromAsset,
address toAsset,
uint256 amountToSwap,
bytes calldata params
) external override {
_whenNotPaused();
address collateralManager = _addressesProvider.getLendingPoolCollateralManager();
//solium-disable-next-line
(bool success, bytes memory result) = collateralManager.delegatecall(
abi.encodeWithSignature(
'swapLiquidity(address,address,address,uint256,bytes)',
receiverAddress,
fromAsset,
toAsset,
amountToSwap,
params
)
);
require(success, Errors.FAILED_COLLATERAL_SWAP);
(uint256 returnCode, string memory returnMessage) = abi.decode(result, (uint256, string));
if (returnCode != 0) {
revert(string(abi.encodePacked(returnMessage)));
IERC20(vars.currentAsset).safeTransferFrom(
receiverAddress,
vars.currentATokenAddress,
vars.currentAmountPlusPremium
);
} else {
//if the user didn't choose to return the funds, the system checks if there
//is enough collateral and eventually open a position
_executeBorrow(
ExecuteBorrowParams(
vars.currentAsset,
msg.sender,
msg.sender,
vars.currentAmount,
mode,
vars.currentATokenAddress,
referralCode,
false
)
);
}
emit FlashLoan(receiverAddress, mode, assets, amounts, premiums, referralCode);
}
}

View File

@ -1,15 +1,12 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {
VersionedInitializable
} from '../libraries/openzeppelin-upgradeability/VersionedInitializable.sol';
import {SafeMath} from '../dependencies/openzeppelin/contracts//SafeMath.sol';
import {IERC20} from '../dependencies/openzeppelin/contracts//IERC20.sol';
import {VersionedInitializable} from '../libraries/aave-upgradeability/VersionedInitializable.sol';
import {IAToken} from '../tokenization/interfaces/IAToken.sol';
import {IStableDebtToken} from '../tokenization/interfaces/IStableDebtToken.sol';
import {IVariableDebtToken} from '../tokenization/interfaces/IVariableDebtToken.sol';
import {DebtTokenBase} from '../tokenization/base/DebtTokenBase.sol';
import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol';
import {GenericLogic} from '../libraries/logic/GenericLogic.sol';
import {ReserveLogic} from '../libraries/logic/ReserveLogic.sol';
@ -17,7 +14,7 @@ import {UserConfiguration} from '../libraries/configuration/UserConfiguration.so
import {Helpers} from '../libraries/helpers/Helpers.sol';
import {WadRayMath} from '../libraries/math/WadRayMath.sol';
import {PercentageMath} from '../libraries/math/PercentageMath.sol';
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import {SafeERC20} from '../dependencies/openzeppelin/contracts/SafeERC20.sol';
import {ISwapAdapter} from '../interfaces/ISwapAdapter.sol';
import {Errors} from '../libraries/helpers/Errors.sol';
import {ValidationLogic} from '../libraries/logic/ValidationLogic.sol';
@ -61,24 +58,6 @@ contract LendingPoolCollateralManager is VersionedInitializable, LendingPoolStor
bool receiveAToken
);
/**
@dev emitted when a borrower/liquidator repays with the borrower's collateral
@param collateral the address of the collateral being swapped to repay
@param principal the address of the reserve of the debt
@param user the borrower's address
@param liquidator the address of the liquidator, same as the one of the borrower on self-repayment
@param principalAmount the amount of the debt finally covered
@param swappedCollateralAmount the amount of collateral finally swapped
*/
event RepaidWithCollateral(
address indexed collateral,
address indexed principal,
address indexed user,
address liquidator,
uint256 principalAmount,
uint256 swappedCollateralAmount
);
struct LiquidationCallLocalVars {
uint256 userCollateralBalance;
uint256 userStableDebt;
@ -99,16 +78,6 @@ contract LendingPoolCollateralManager is VersionedInitializable, LendingPoolStor
string errorMsg;
}
struct SwapLiquidityLocalVars {
uint256 healthFactor;
uint256 amountToReceive;
uint256 userBalanceBefore;
IAToken fromReserveAToken;
IAToken toReserveAToken;
uint256 errorCode;
string errorMsg;
}
struct AvailableCollateralToLiquidateLocalVars {
uint256 userCompoundedBorrowBalance;
uint256 liquidationBonus;
@ -192,7 +161,7 @@ contract LendingPoolCollateralManager is VersionedInitializable, LendingPoolStor
(
vars.maxCollateralToLiquidate,
vars.principalAmountNeeded
) = calculateAvailableCollateralToLiquidate(
) = _calculateAvailableCollateralToLiquidate(
collateralReserve,
principalReserve,
collateral,
@ -225,13 +194,6 @@ contract LendingPoolCollateralManager is VersionedInitializable, LendingPoolStor
//update the principal reserve
principalReserve.updateState();
principalReserve.updateInterestRates(
principal,
principalReserve.aTokenAddress,
vars.actualAmountToLiquidate,
0
);
if (vars.userVariableDebt >= vars.actualAmountToLiquidate) {
IVariableDebtToken(principalReserve.variableDebtTokenAddress).burn(
user,
@ -254,6 +216,13 @@ contract LendingPoolCollateralManager is VersionedInitializable, LendingPoolStor
);
}
principalReserve.updateInterestRates(
principal,
principalReserve.aTokenAddress,
vars.actualAmountToLiquidate,
0
);
//if liquidator reclaims the aToken, he receives the equivalent atoken amount
if (receiveAToken) {
vars.collateralAtoken.transferOnLiquidation(user, msg.sender, vars.maxCollateralToLiquidate);
@ -298,259 +267,6 @@ contract LendingPoolCollateralManager is VersionedInitializable, LendingPoolStor
return (uint256(Errors.CollateralManagerErrors.NO_ERROR), Errors.NO_ERRORS);
}
/**
* @dev flashes the underlying collateral on an user to swap for the owed asset and repay
* - Both the owner of the position and other liquidators can execute it.
* - The owner can repay with his collateral at any point, no matter the health factor.
* - Other liquidators can only use this function below 1 HF. To liquidate 50% of the debt > HF 0.98 or the whole below.
* @param collateral The address of the collateral asset.
* @param principal The address of the owed asset.
* @param user Address of the borrower.
* @param principalAmount Amount of the debt to repay.
* @param receiver Address of the contract receiving the collateral to swap.
* @param params Variadic bytes param to pass with extra information to the receiver
**/
function repayWithCollateral(
address collateral,
address principal,
address user,
uint256 principalAmount,
address receiver,
bytes calldata params
) external returns (uint256, string memory) {
ReserveLogic.ReserveData storage collateralReserve = _reserves[collateral];
ReserveLogic.ReserveData storage debtReserve = _reserves[principal];
UserConfiguration.Map storage userConfig = _usersConfig[user];
LiquidationCallLocalVars memory vars;
(, , , , vars.healthFactor) = GenericLogic.calculateUserAccountData(
user,
_reserves,
_usersConfig[user],
_reservesList,
_reservesCount,
_addressesProvider.getPriceOracle()
);
(vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(user, debtReserve);
(vars.errorCode, vars.errorMsg) = ValidationLogic.validateRepayWithCollateral(
collateralReserve,
debtReserve,
userConfig,
user,
vars.healthFactor,
vars.userStableDebt,
vars.userVariableDebt
);
if (Errors.CollateralManagerErrors(vars.errorCode) != Errors.CollateralManagerErrors.NO_ERROR) {
return (vars.errorCode, vars.errorMsg);
}
vars.maxPrincipalAmountToLiquidate = vars.userStableDebt.add(vars.userVariableDebt);
vars.actualAmountToLiquidate = principalAmount > vars.maxPrincipalAmountToLiquidate
? vars.maxPrincipalAmountToLiquidate
: principalAmount;
vars.collateralAtoken = IAToken(collateralReserve.aTokenAddress);
vars.userCollateralBalance = vars.collateralAtoken.balanceOf(user);
(
vars.maxCollateralToLiquidate,
vars.principalAmountNeeded
) = calculateAvailableCollateralToLiquidate(
collateralReserve,
debtReserve,
collateral,
principal,
vars.actualAmountToLiquidate,
vars.userCollateralBalance
);
//if principalAmountNeeded < vars.ActualAmountToLiquidate, there isn't enough
//of collateral to cover the actual amount that is being liquidated, hence we liquidate
//a smaller amount
if (vars.principalAmountNeeded < vars.actualAmountToLiquidate) {
vars.actualAmountToLiquidate = vars.principalAmountNeeded;
}
//updating collateral reserve indexes
collateralReserve.updateState();
//updating collateral reserve interest rates
collateralReserve.updateInterestRates(
collateral,
address(vars.collateralAtoken),
0,
vars.maxCollateralToLiquidate
);
vars.collateralAtoken.burn(
user,
receiver,
vars.maxCollateralToLiquidate,
collateralReserve.liquidityIndex
);
if (vars.userCollateralBalance == vars.maxCollateralToLiquidate) {
_usersConfig[user].setUsingAsCollateral(collateralReserve.id, false);
}
vars.principalAToken = debtReserve.aTokenAddress;
// Notifies the receiver to proceed, sending as param the underlying already transferred
ISwapAdapter(receiver).executeOperation(
collateral,
principal,
vars.maxCollateralToLiquidate,
address(this),
params
);
//updating debt reserve
debtReserve.updateState();
debtReserve.updateInterestRates(
principal,
vars.principalAToken,
vars.actualAmountToLiquidate,
0
);
IERC20(principal).safeTransferFrom(
receiver,
vars.principalAToken,
vars.actualAmountToLiquidate
);
if (vars.userVariableDebt >= vars.actualAmountToLiquidate) {
IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn(
user,
vars.actualAmountToLiquidate,
debtReserve.variableBorrowIndex
);
} else {
IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn(
user,
vars.userVariableDebt,
debtReserve.variableBorrowIndex
);
IStableDebtToken(debtReserve.stableDebtTokenAddress).burn(
user,
vars.actualAmountToLiquidate.sub(vars.userVariableDebt)
);
}
emit RepaidWithCollateral(
collateral,
principal,
user,
msg.sender,
vars.actualAmountToLiquidate,
vars.maxCollateralToLiquidate
);
return (uint256(Errors.CollateralManagerErrors.NO_ERROR), Errors.NO_ERRORS);
}
/**
* @dev Allows an user to release one of his assets deposited in the protocol, even if it is used as collateral, to swap for another.
* - It's not possible to release one asset to swap for the same
* @param receiverAddress The address of the contract receiving the funds. The receiver should implement the ISwapAdapter interface
* @param fromAsset Asset to swap from
* @param toAsset Asset to swap to
* @param params a bytes array to be sent (if needed) to the receiver contract with extra data
**/
function swapLiquidity(
address receiverAddress,
address fromAsset,
address toAsset,
uint256 amountToSwap,
bytes calldata params
) external returns (uint256, string memory) {
ReserveLogic.ReserveData storage fromReserve = _reserves[fromAsset];
ReserveLogic.ReserveData storage toReserve = _reserves[toAsset];
SwapLiquidityLocalVars memory vars;
(vars.errorCode, vars.errorMsg) = ValidationLogic.validateSwapLiquidity(
fromReserve,
toReserve,
fromAsset,
toAsset
);
if (Errors.CollateralManagerErrors(vars.errorCode) != Errors.CollateralManagerErrors.NO_ERROR) {
return (vars.errorCode, vars.errorMsg);
}
vars.fromReserveAToken = IAToken(fromReserve.aTokenAddress);
vars.toReserveAToken = IAToken(toReserve.aTokenAddress);
fromReserve.updateState();
toReserve.updateState();
if (vars.fromReserveAToken.balanceOf(msg.sender) == amountToSwap) {
_usersConfig[msg.sender].setUsingAsCollateral(fromReserve.id, false);
}
fromReserve.updateInterestRates(fromAsset, address(vars.fromReserveAToken), 0, amountToSwap);
vars.fromReserveAToken.burn(
msg.sender,
receiverAddress,
amountToSwap,
fromReserve.liquidityIndex
);
// Notifies the receiver to proceed, sending as param the underlying already transferred
ISwapAdapter(receiverAddress).executeOperation(
fromAsset,
toAsset,
amountToSwap,
address(this),
params
);
vars.amountToReceive = IERC20(toAsset).balanceOf(receiverAddress);
if (vars.amountToReceive != 0) {
IERC20(toAsset).safeTransferFrom(
receiverAddress,
address(vars.toReserveAToken),
vars.amountToReceive
);
if (vars.toReserveAToken.balanceOf(msg.sender) == 0) {
_usersConfig[msg.sender].setUsingAsCollateral(toReserve.id, true);
}
vars.toReserveAToken.mint(msg.sender, vars.amountToReceive, toReserve.liquidityIndex);
toReserve.updateInterestRates(
toAsset,
address(vars.toReserveAToken),
vars.amountToReceive,
0
);
}
(, , , , vars.healthFactor) = GenericLogic.calculateUserAccountData(
msg.sender,
_reserves,
_usersConfig[msg.sender],
_reservesList,
_reservesCount,
_addressesProvider.getPriceOracle()
);
if (vars.healthFactor < GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
return (
uint256(Errors.CollateralManagerErrors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD),
Errors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD
);
}
return (uint256(Errors.CollateralManagerErrors.NO_ERROR), Errors.NO_ERRORS);
}
/**
* @dev calculates how much of a specific collateral can be liquidated, given
* a certain amount of principal currency. This function needs to be called after
@ -562,7 +278,7 @@ contract LendingPoolCollateralManager is VersionedInitializable, LendingPoolStor
* @return collateralAmount the maximum amount that is possible to liquidated given all the liquidation constraints (user balance, close factor)
* @return principalAmountNeeded the purchase amount
**/
function calculateAvailableCollateralToLiquidate(
function _calculateAvailableCollateralToLiquidate(
ReserveLogic.ReserveData storage collateralReserve,
ReserveLogic.ReserveData storage principalReserve,
address collateralAddress,
@ -590,8 +306,8 @@ contract LendingPoolCollateralManager is VersionedInitializable, LendingPoolStor
.principalCurrencyPrice
.mul(purchaseAmount)
.mul(10**vars.collateralDecimals)
.div(vars.collateralPrice.mul(10**vars.principalDecimals))
.percentMul(vars.liquidationBonus);
.percentMul(vars.liquidationBonus)
.div(vars.collateralPrice.mul(10**vars.principalDecimals));
if (vars.maxAmountCollateralToLiquidate > userCollateralBalance) {
collateralAmount = userCollateralBalance;

View File

@ -2,17 +2,16 @@
pragma solidity ^0.6.8;
pragma experimental ABIEncoderV2;
import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol';
import {SafeMath} from '../dependencies/openzeppelin/contracts/SafeMath.sol';
import {VersionedInitializable} from '../libraries/aave-upgradeability/VersionedInitializable.sol';
import {
VersionedInitializable
} from '../libraries/openzeppelin-upgradeability/VersionedInitializable.sol';
import {
InitializableAdminUpgradeabilityProxy
} from '../libraries/openzeppelin-upgradeability/InitializableAdminUpgradeabilityProxy.sol';
InitializableImmutableAdminUpgradeabilityProxy
} from '../libraries/aave-upgradeability/InitializableImmutableAdminUpgradeabilityProxy.sol';
import {ReserveConfiguration} from '../libraries/configuration/ReserveConfiguration.sol';
import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol';
import {ILendingPool} from '../interfaces/ILendingPool.sol';
import {IERC20Detailed} from '../interfaces/IERC20Detailed.sol';
import {ITokenConfiguration} from '../tokenization/interfaces/ITokenConfiguration.sol';
import {IERC20Detailed} from '../dependencies/openzeppelin/contracts/IERC20Detailed.sol';
import {Errors} from '../libraries/helpers/Errors.sol';
import {ReserveLogic} from '../libraries/logic/ReserveLogic.sol';
@ -202,7 +201,6 @@ contract LendingPoolConfigurator is VersionedInitializable {
/**
* @dev initializes a reserve
* @param asset the address of the reserve to be initialized
* @param aTokenImpl the address of the aToken contract implementation
* @param stableDebtTokenImpl the address of the stable debt token contract
* @param variableDebtTokenImpl the address of the variable debt token contract
@ -210,13 +208,35 @@ contract LendingPoolConfigurator is VersionedInitializable {
* @param interestRateStrategyAddress the address of the interest rate strategy contract for this reserve
**/
function initReserve(
address asset,
address aTokenImpl,
address stableDebtTokenImpl,
address variableDebtTokenImpl,
uint8 underlyingAssetDecimals,
address interestRateStrategyAddress
) public onlyAaveAdmin {
address asset = ITokenConfiguration(aTokenImpl).UNDERLYING_ASSET_ADDRESS();
require(
address(pool) == ITokenConfiguration(aTokenImpl).POOL(),
Errors.INVALID_ATOKEN_POOL_ADDRESS
);
require(
address(pool) == ITokenConfiguration(stableDebtTokenImpl).POOL(),
Errors.INVALID_STABLE_DEBT_TOKEN_POOL_ADDRESS
);
require(
address(pool) == ITokenConfiguration(variableDebtTokenImpl).POOL(),
Errors.INVALID_VARIABLE_DEBT_TOKEN_POOL_ADDRESS
);
require(
asset == ITokenConfiguration(stableDebtTokenImpl).UNDERLYING_ASSET_ADDRESS(),
Errors.INVALID_STABLE_DEBT_TOKEN_UNDERLYING_ADDRESS
);
require(
asset == ITokenConfiguration(variableDebtTokenImpl).UNDERLYING_ASSET_ADDRESS(),
Errors.INVALID_VARIABLE_DEBT_TOKEN_UNDERLYING_ADDRESS
);
address aTokenProxyAddress = _initTokenWithProxy(aTokenImpl, underlyingAssetDecimals);
address stableDebtTokenProxyAddress = _initTokenWithProxy(
@ -551,7 +571,9 @@ contract LendingPoolConfigurator is VersionedInitializable {
* @param decimals the decimals of the token
**/
function _initTokenWithProxy(address implementation, uint8 decimals) internal returns (address) {
InitializableAdminUpgradeabilityProxy proxy = new InitializableAdminUpgradeabilityProxy();
InitializableImmutableAdminUpgradeabilityProxy proxy
= new InitializableImmutableAdminUpgradeabilityProxy(address(this));
bytes memory params = abi.encodeWithSignature(
'initialize(uint8,string,string)',
@ -560,7 +582,7 @@ contract LendingPoolConfigurator is VersionedInitializable {
IERC20Detailed(implementation).symbol()
);
proxy.initialize(implementation, address(this), params);
proxy.initialize(implementation, params);
return address(proxy);
}
@ -570,9 +592,9 @@ contract LendingPoolConfigurator is VersionedInitializable {
address proxyAddress,
address implementation
) internal {
InitializableAdminUpgradeabilityProxy proxy = InitializableAdminUpgradeabilityProxy(
payable(proxyAddress)
);
InitializableImmutableAdminUpgradeabilityProxy proxy
= InitializableImmutableAdminUpgradeabilityProxy(payable(proxyAddress));
ReserveConfiguration.Map memory configuration = pool.getConfiguration(asset);

View File

@ -0,0 +1,80 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import '../../dependencies/openzeppelin/upgradeability/BaseUpgradeabilityProxy.sol';
/**
* @title BaseImmutableAdminUpgradeabilityProxy
* @author Aave, inspired by the OpenZeppelin upgradeability proxy pattern
* @dev This contract combines an upgradeability proxy with an authorization
* mechanism for administrative tasks. The admin role is stored in an immutable, which
* helps saving transactions costs
* All external functions in this contract must be guarded by the
* `ifAdmin` modifier. See ethereum/solidity#3864 for a Solidity
* feature proposal that would enable this to be done automatically.
*/
contract BaseImmutableAdminUpgradeabilityProxy is BaseUpgradeabilityProxy {
address immutable ADMIN;
constructor(address admin) public {
ADMIN = admin;
}
modifier ifAdmin() {
if (msg.sender == ADMIN) {
_;
} else {
_fallback();
}
}
/**
* @return The address of the proxy admin.
*/
function admin() external ifAdmin returns (address) {
return ADMIN;
}
/**
* @return The address of the implementation.
*/
function implementation() external ifAdmin returns (address) {
return _implementation();
}
/**
* @dev Upgrade the backing implementation of the proxy.
* Only the admin can call this function.
* @param newImplementation Address of the new implementation.
*/
function upgradeTo(address newImplementation) external ifAdmin {
_upgradeTo(newImplementation);
}
/**
* @dev Upgrade the backing implementation of the proxy and call a function
* on the new implementation.
* This is useful to initialize the proxied contract.
* @param newImplementation Address of the new implementation.
* @param data Data to send as msg.data in the low level call.
* It should include the signature and the parameters of the function to be called, as described in
* https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
*/
function upgradeToAndCall(address newImplementation, bytes calldata data)
external
payable
ifAdmin
{
_upgradeTo(newImplementation);
(bool success, ) = newImplementation.delegatecall(data);
require(success);
}
/**
* @dev Only fall back when the sender is not the admin.
*/
function _willFallback() internal virtual override {
require(msg.sender != ADMIN, 'Cannot call fallback function from the proxy admin');
super._willFallback();
}
}

View File

@ -0,0 +1,24 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import './BaseImmutableAdminUpgradeabilityProxy.sol';
import '../../dependencies/openzeppelin/upgradeability/InitializableUpgradeabilityProxy.sol';
/**
* @title InitializableAdminUpgradeabilityProxy
* @dev Extends from BaseAdminUpgradeabilityProxy with an initializer for
* initializing the implementation, admin, and init data.
*/
contract InitializableImmutableAdminUpgradeabilityProxy is
BaseImmutableAdminUpgradeabilityProxy,
InitializableUpgradeabilityProxy
{
constructor(address admin) public BaseImmutableAdminUpgradeabilityProxy(admin) {}
/**
* @dev Only fall back when the sender is not the admin.
*/
function _willFallback() internal override(BaseImmutableAdminUpgradeabilityProxy, Proxy) {
BaseImmutableAdminUpgradeabilityProxy._willFallback();
}
}

View File

@ -1,12 +1,6 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {ReserveLogic} from '../logic/ReserveLogic.sol';
import {WadRayMath} from '../math/WadRayMath.sol';
import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol';
/**
* @title ReserveConfiguration library
* @author Aave

View File

@ -1,11 +1,6 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {WadRayMath} from '../math/WadRayMath.sol';
import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol';
/**
* @title UserConfiguration library
* @author Aave

View File

@ -45,13 +45,14 @@ library Errors {
string public constant INVALID_EQUAL_ASSETS_TO_SWAP = '56';
string public constant NO_MORE_RESERVES_ALLOWED = '59';
string public constant INVALID_FLASH_LOAN_EXECUTOR_RETURN = '60';
string public constant INCONSISTENT_FLASHLOAN_PARAMS = '69';
// require error messages - aToken - DebtTokens
string public constant CALLER_MUST_BE_LENDING_POOL = '28'; // 'The caller of this function must be a lending pool'
string public constant CANNOT_GIVE_ALLOWANCE_TO_HIMSELF = '30'; // 'User cannot give allowance to himself'
string public constant TRANSFER_AMOUNT_NOT_GT_0 = '31'; // 'Transferred amount needs to be greater than zero'
string public constant INVALID_MINT_AMOUNT = '53'; //invalid amount to mint
string public constant INVALID_BURN_AMOUNT = '54'; //invalid amount to burn
string public constant INVALID_MINT_AMOUNT = '61'; //invalid amount to mint
string public constant INVALID_BURN_AMOUNT = '62'; //invalid amount to burn
// require error messages - ReserveLogic
string public constant RESERVE_ALREADY_INITIALIZED = '34'; // 'Reserve has already been initialized'
@ -64,9 +65,15 @@ library Errors {
//require error messages - LendingPoolConfiguration
string public constant CALLER_NOT_AAVE_ADMIN = '35'; // 'The caller must be the aave admin'
string public constant RESERVE_LIQUIDITY_NOT_0 = '36'; // 'The liquidity of the reserve needs to be 0'
string public constant INVALID_ATOKEN_POOL_ADDRESS = '63'; // the lending pool in the aToken implementation is not configured correctly
string public constant INVALID_STABLE_DEBT_TOKEN_POOL_ADDRESS = '64'; // the lending pool in the stable debt token implementation is not configured correctly
string public constant INVALID_VARIABLE_DEBT_TOKEN_POOL_ADDRESS = '65'; // the lending pool in the variable debt token implementation is not configured correctly
string public constant INVALID_STABLE_DEBT_TOKEN_UNDERLYING_ADDRESS = '66'; // the underlying asset in the stable debt token implementation is not configured correctly
string public constant INVALID_VARIABLE_DEBT_TOKEN_UNDERLYING_ADDRESS = '67'; // the underlying asset in the variable debt token implementation is not configured correctly
//require error messages - LendingPoolAddressesProviderRegistry
string public constant PROVIDER_NOT_REGISTERED = '37'; // 'Provider is not registered'
string public constant INVALID_ADDRESSES_PROVIDER_ID = '68'; // the addresses provider id needs to be greater than 0
//return error messages - LendingPoolCollateralManager
string public constant HEALTH_FACTOR_NOT_BELOW_THRESHOLD = '38'; // 'Health factor is not below the threshold'

View File

@ -2,8 +2,8 @@
pragma solidity ^0.6.8;
pragma experimental ABIEncoderV2;
import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {SafeMath} from '../../dependencies/openzeppelin/contracts/SafeMath.sol';
import {IERC20} from '../../dependencies/openzeppelin/contracts/IERC20.sol';
import {ReserveLogic} from './ReserveLogic.sol';
import {ReserveConfiguration} from '../configuration/ReserveConfiguration.sol';
import {UserConfiguration} from '../configuration/UserConfiguration.sol';
@ -25,7 +25,6 @@ library GenericLogic {
using UserConfiguration for UserConfiguration.Map;
uint256 public constant HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1 ether;
uint256 public constant HEALTH_FACTOR_CRITICAL_THRESHOLD = 0.98 ether;
struct balanceDecreaseAllowedLocalVars {
uint256 decimals;
@ -68,7 +67,9 @@ library GenericLogic {
balanceDecreaseAllowedLocalVars memory vars;
(, vars.liquidationThreshold, , vars.decimals, ) = reservesData[asset].configuration.getParams();
(, vars.liquidationThreshold, , vars.decimals, ) = reservesData[asset]
.configuration
.getParams();
if (vars.liquidationThreshold == 0) {
return true; //if reserve is not used as collateral, no reasons to block the transfer

View File

@ -1,11 +1,10 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {SafeMath} from '../../dependencies/openzeppelin/contracts/SafeMath.sol';
import {IERC20} from '../../dependencies/openzeppelin/contracts/IERC20.sol';
import {MathUtils} from '../math/MathUtils.sol';
import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol';
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import {SafeERC20} from '../../dependencies/openzeppelin/contracts/SafeERC20.sol';
import {IAToken} from '../../tokenization/interfaces/IAToken.sol';
import {IStableDebtToken} from '../../tokenization/interfaces/IStableDebtToken.sol';
import {IVariableDebtToken} from '../../tokenization/interfaces/IVariableDebtToken.sol';
@ -147,21 +146,22 @@ library ReserveLogic {
* a formal specification.
* @param reserve the reserve object
**/
function updateState(ReserveData storage reserve) external {
address variableDebtToken = reserve.variableDebtTokenAddress;
function updateState(ReserveData storage reserve) internal {
uint256 scaledVariableDebt = IVariableDebtToken(reserve.variableDebtTokenAddress)
.scaledTotalSupply();
uint256 previousVariableBorrowIndex = reserve.variableBorrowIndex;
uint256 previousLiquidityIndex = reserve.liquidityIndex;
(uint256 newLiquidityIndex, uint256 newVariableBorrowIndex) = _updateIndexes(
reserve,
variableDebtToken,
scaledVariableDebt,
previousLiquidityIndex,
previousVariableBorrowIndex
);
_mintToTreasury(
reserve,
variableDebtToken,
scaledVariableDebt,
previousVariableBorrowIndex,
newLiquidityIndex,
newVariableBorrowIndex
@ -179,7 +179,7 @@ library ReserveLogic {
ReserveData storage reserve,
uint256 totalLiquidity,
uint256 amount
) external {
) internal {
uint256 amountToLiquidityRatio = amount.wadToRay().rayDiv(totalLiquidity.wadToRay());
uint256 result = amountToLiquidityRatio.add(WadRayMath.ray());
@ -227,6 +227,7 @@ library ReserveLogic {
uint256 newStableRate;
uint256 newVariableRate;
uint256 avgStableRate;
uint256 totalVariableDebt;
}
/**
@ -242,7 +243,7 @@ library ReserveLogic {
address aTokenAddress,
uint256 liquidityAdded,
uint256 liquidityTaken
) external {
) internal {
UpdateInterestRatesLocalVars memory vars;
vars.stableDebtTokenAddress = reserve.stableDebtTokenAddress;
@ -250,6 +251,13 @@ library ReserveLogic {
(vars.totalStableDebt, vars.avgStableRate) = IStableDebtToken(vars.stableDebtTokenAddress)
.getTotalSupplyAndAvgRate();
//calculates the total variable debt locally using the scaled total supply instead
//of totalSupply(), as it's noticeably cheaper. Also, the index has been
//updated by the previous updateState() call
vars.totalVariableDebt = IVariableDebtToken(reserve.variableDebtTokenAddress)
.scaledTotalSupply()
.rayMul(reserve.variableBorrowIndex);
vars.availableLiquidity = IERC20(reserveAddress).balanceOf(aTokenAddress);
(
@ -260,7 +268,7 @@ library ReserveLogic {
reserveAddress,
vars.availableLiquidity.add(liquidityAdded).sub(liquidityTaken),
vars.totalStableDebt,
IERC20(reserve.variableDebtTokenAddress).totalSupply(),
vars.totalVariableDebt,
vars.avgStableRate,
reserve.configuration.getReserveFactor()
);
@ -287,7 +295,6 @@ library ReserveLogic {
uint256 principalStableDebt;
uint256 previousStableDebt;
uint256 currentVariableDebt;
uint256 scaledVariableDebt;
uint256 previousVariableDebt;
uint256 avgStableRate;
uint256 cumulatedStableInterest;
@ -301,14 +308,14 @@ library ReserveLogic {
* @dev mints part of the repaid interest to the reserve treasury, depending on the reserveFactor for the
* specific asset.
* @param reserve the reserve reserve to be updated
* @param variableDebtToken the debt token address
* @param scaledVariableDebt the current scaled total variable debt
* @param previousVariableBorrowIndex the variable borrow index before the last accumulation of the interest
* @param newLiquidityIndex the new liquidity index
* @param newVariableBorrowIndex the variable borrow index after the last accumulation of the interest
**/
function _mintToTreasury(
ReserveData storage reserve,
address variableDebtToken,
uint256 scaledVariableDebt,
uint256 previousVariableBorrowIndex,
uint256 newLiquidityIndex,
uint256 newVariableBorrowIndex
@ -321,9 +328,6 @@ library ReserveLogic {
return;
}
//fetching the last scaled total variable debt
vars.scaledVariableDebt = IVariableDebtToken(variableDebtToken).scaledTotalSupply();
//fetching the principal, total stable debt and the avg stable rate
(
vars.principalStableDebt,
@ -333,10 +337,10 @@ library ReserveLogic {
) = IStableDebtToken(reserve.stableDebtTokenAddress).getSupplyData();
//calculate the last principal variable debt
vars.previousVariableDebt = vars.scaledVariableDebt.rayMul(previousVariableBorrowIndex);
vars.previousVariableDebt = scaledVariableDebt.rayMul(previousVariableBorrowIndex);
//calculate the new total supply after accumulation of the index
vars.currentVariableDebt = vars.scaledVariableDebt.rayMul(newVariableBorrowIndex);
vars.currentVariableDebt = scaledVariableDebt.rayMul(newVariableBorrowIndex);
//calculate the stable debt until the last timestamp update
vars.cumulatedStableInterest = MathUtils.calculateCompoundedInterest(
@ -355,19 +359,21 @@ library ReserveLogic {
vars.amountToMint = vars.totalDebtAccrued.percentMul(vars.reserveFactor);
IAToken(reserve.aTokenAddress).mintToTreasury(vars.amountToMint, newLiquidityIndex);
if (vars.amountToMint != 0) {
IAToken(reserve.aTokenAddress).mintToTreasury(vars.amountToMint, newLiquidityIndex);
}
}
/**
* @dev updates the reserve indexes and the timestamp of the update
* @param reserve the reserve reserve to be updated
* @param variableDebtToken the debt token address
* @param scaledVariableDebt the scaled variable debt
* @param liquidityIndex the last stored liquidity index
* @param variableBorrowIndex the last stored variable borrow index
**/
function _updateIndexes(
ReserveData storage reserve,
address variableDebtToken,
uint256 scaledVariableDebt,
uint256 liquidityIndex,
uint256 variableBorrowIndex
) internal returns (uint256, uint256) {
@ -391,7 +397,7 @@ library ReserveLogic {
//as the liquidity rate might come only from stable rate loans, we need to ensure
//that there is actual variable debt before accumulating
if (IERC20(variableDebtToken).totalSupply() > 0) {
if (scaledVariableDebt != 0) {
uint256 cumulatedVariableBorrowInterest = MathUtils.calculateCompoundedInterest(
reserve.currentVariableBorrowRate,
timestamp

View File

@ -2,16 +2,15 @@
pragma solidity ^0.6.8;
pragma experimental ABIEncoderV2;
import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {SafeMath} from '../../dependencies/openzeppelin/contracts/SafeMath.sol';
import {IERC20} from '../../dependencies/openzeppelin/contracts/IERC20.sol';
import {ReserveLogic} from './ReserveLogic.sol';
import {GenericLogic} from './GenericLogic.sol';
import {WadRayMath} from '../math/WadRayMath.sol';
import {PercentageMath} from '../math/PercentageMath.sol';
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import {SafeERC20} from '../../dependencies/openzeppelin/contracts/SafeERC20.sol';
import {ReserveConfiguration} from '../configuration/ReserveConfiguration.sol';
import {UserConfiguration} from '../configuration/UserConfiguration.sol';
import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol';
import {Errors} from '../helpers/Errors.sol';
import {Helpers} from '../helpers/Helpers.sol';
@ -34,7 +33,7 @@ library ValidationLogic {
* @param reserve the reserve state on which the user is depositing
* @param amount the amount to be deposited
*/
function validateDeposit(ReserveLogic.ReserveData storage reserve, uint256 amount) external view {
function validateDeposit(ReserveLogic.ReserveData storage reserve, uint256 amount) internal view {
(bool isActive, bool isFreezed, , ) = reserve.configuration.getFlags();
require(amount > 0, Errors.AMOUNT_NOT_GREATER_THAN_0);
@ -57,7 +56,7 @@ library ValidationLogic {
mapping(uint256 => address) storage reserves,
uint256 reservesCount,
address oracle
) external view {
) internal view {
require(amount > 0, Errors.AMOUNT_NOT_GREATER_THAN_0);
require(amount <= userBalance, Errors.NOT_ENOUGH_AVAILABLE_USER_BALANCE);
@ -66,7 +65,7 @@ library ValidationLogic {
GenericLogic.balanceDecreaseAllowed(
reserveAddress,
msg.sender,
userBalance,
amount,
reservesData,
userConfig,
reserves,
@ -328,11 +327,16 @@ library ValidationLogic {
/**
* @dev validates a flashloan action
* @param mode the flashloan mode (0 = classic flashloan, 1 = open a stable rate loan, 2 = open a variable rate loan)
* @param premium the premium paid on the flashloan
* @param assets the assets being flashborrowed
* @param amounts the amounts for each asset being borrowed
**/
function validateFlashloan(uint256 mode, uint256 premium) internal pure {
require(premium > 0, Errors.REQUESTED_AMOUNT_TOO_SMALL);
function validateFlashloan(
address[] memory assets,
uint256[] memory amounts,
uint256 mode
) internal pure {
require(mode <= uint256(ReserveLogic.InterestRateMode.VARIABLE), Errors.INVALID_FLASHLOAN_MODE);
require(assets.length == amounts.length, Errors.INCONSISTENT_FLASHLOAN_PARAMS);
}
/**
@ -385,95 +389,4 @@ library ValidationLogic {
return (uint256(Errors.CollateralManagerErrors.NO_ERROR), Errors.NO_ERRORS);
}
/**
* @dev Validates the repayWithCollateral() action
* @param collateralReserve The reserve data of the collateral
* @param principalReserve The reserve data of the principal
* @param userConfig The user configuration
* @param user The address of the user
* @param userHealthFactor The user's health factor
* @param userStableDebt Total stable debt balance of the user
* @param userVariableDebt Total variable debt balance of the user
**/
function validateRepayWithCollateral(
ReserveLogic.ReserveData storage collateralReserve,
ReserveLogic.ReserveData storage principalReserve,
UserConfiguration.Map storage userConfig,
address user,
uint256 userHealthFactor,
uint256 userStableDebt,
uint256 userVariableDebt
) internal view returns (uint256, string memory) {
if (
!collateralReserve.configuration.getActive() || !principalReserve.configuration.getActive()
) {
return (uint256(Errors.CollateralManagerErrors.NO_ACTIVE_RESERVE), Errors.NO_ACTIVE_RESERVE);
}
if (
msg.sender != user && userHealthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD
) {
return (
uint256(Errors.CollateralManagerErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD
);
}
if (msg.sender != user) {
bool isCollateralEnabled = collateralReserve.configuration.getLiquidationThreshold() > 0 &&
userConfig.isUsingAsCollateral(collateralReserve.id);
//if collateral isn't enabled as collateral by user, it cannot be liquidated
if (!isCollateralEnabled) {
return (
uint256(Errors.CollateralManagerErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
Errors.COLLATERAL_CANNOT_BE_LIQUIDATED
);
}
}
if (userStableDebt == 0 && userVariableDebt == 0) {
return (
uint256(Errors.CollateralManagerErrors.CURRRENCY_NOT_BORROWED),
Errors.SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
);
}
return (uint256(Errors.CollateralManagerErrors.NO_ERROR), Errors.NO_ERRORS);
}
/**
* @dev Validates the swapLiquidity() action
* @param fromReserve The reserve data of the asset to swap from
* @param toReserve The reserve data of the asset to swap to
* @param fromAsset Address of the asset to swap from
* @param toAsset Address of the asset to swap to
**/
function validateSwapLiquidity(
ReserveLogic.ReserveData storage fromReserve,
ReserveLogic.ReserveData storage toReserve,
address fromAsset,
address toAsset
) internal view returns (uint256, string memory) {
if (fromAsset == toAsset) {
return (
uint256(Errors.CollateralManagerErrors.INVALID_EQUAL_ASSETS_TO_SWAP),
Errors.INVALID_EQUAL_ASSETS_TO_SWAP
);
}
(bool isToActive, bool isToFreezed, , ) = toReserve.configuration.getFlags();
if (!fromReserve.configuration.getActive() || !isToActive) {
return (uint256(Errors.CollateralManagerErrors.NO_ACTIVE_RESERVE), Errors.NO_ACTIVE_RESERVE);
}
if (isToFreezed) {
return (
uint256(Errors.CollateralManagerErrors.NO_UNFREEZED_RESERVE),
Errors.NO_UNFREEZED_RESERVE
);
}
return (uint256(Errors.CollateralManagerErrors.NO_ERROR), Errors.NO_ERRORS);
}
}

View File

@ -1,13 +1,14 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol';
import {SafeMath} from '../../dependencies/openzeppelin/contracts/SafeMath.sol';
import {WadRayMath} from './WadRayMath.sol';
library MathUtils {
using SafeMath for uint256;
using WadRayMath for uint256;
/// @dev Ignoring leap years
uint256 internal constant SECONDS_PER_YEAR = 365 days;
/**
@ -25,9 +26,7 @@ library MathUtils {
//solium-disable-next-line
uint256 timeDifference = block.timestamp.sub(uint256(lastUpdateTimestamp));
uint256 timeDelta = timeDifference.wadToRay().rayDiv(SECONDS_PER_YEAR.wadToRay());
return rate.rayMul(timeDelta).add(WadRayMath.ray());
return (rate.mul(timeDifference) / SECONDS_PER_YEAR).add(WadRayMath.ray());
}
/**

View File

@ -58,13 +58,9 @@ library WadRayMath {
return 0;
}
uint256 result = a * b;
uint256 result = a * b + halfWAD;
require(result / a == b, Errors.MULTIPLICATION_OVERFLOW);
result += halfWAD;
require(result >= halfWAD, Errors.ADDITION_OVERFLOW);
require(result >= halfWAD && (result - halfWAD) / a == b, Errors.MULTIPLICATION_OVERFLOW);
return result / WAD;
}
@ -80,13 +76,9 @@ library WadRayMath {
uint256 halfB = b / 2;
uint256 result = a * WAD;
uint256 result = a * WAD + halfB;
require(result / WAD == a, Errors.MULTIPLICATION_OVERFLOW);
result += halfB;
require(result >= halfB, Errors.ADDITION_OVERFLOW);
require(result >= halfB && (result - halfB) / WAD == a, Errors.MULTIPLICATION_OVERFLOW);
return result / b;
}
@ -102,13 +94,9 @@ library WadRayMath {
return 0;
}
uint256 result = a * b;
uint256 result = a * b + halfRAY;
require(result / a == b, Errors.MULTIPLICATION_OVERFLOW);
result += halfRAY;
require(result >= halfRAY, Errors.ADDITION_OVERFLOW);
require(result >= halfRAY && (result - halfRAY) / a == b, Errors.MULTIPLICATION_OVERFLOW);
return result / RAY;
}
@ -124,13 +112,9 @@ library WadRayMath {
uint256 halfB = b / 2;
uint256 result = a * RAY;
uint256 result = a * RAY + halfB;
require(result / RAY == a, Errors.MULTIPLICATION_OVERFLOW);
result += halfB;
require(result >= halfB, Errors.ADDITION_OVERFLOW);
require(result >= halfB && (result - halfB) / RAY == a, Errors.MULTIPLICATION_OVERFLOW);
return result / b;
}

View File

@ -3,7 +3,7 @@ pragma solidity ^0.6.8;
pragma experimental ABIEncoderV2;
import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol';
import {IERC20Detailed} from '../interfaces/IERC20Detailed.sol';
import {IERC20Detailed} from '../dependencies/openzeppelin/contracts/IERC20Detailed.sol';
import {ILendingPool} from '../interfaces/ILendingPool.sol';
import {ReserveLogic} from '../libraries/logic/ReserveLogic.sol';
import {ReserveConfiguration} from '../libraries/configuration/ReserveConfiguration.sol';

View File

@ -1,12 +1,12 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {Ownable} from '../dependencies/openzeppelin/contracts/Ownable.sol';
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol';
import {IChainlinkAggregator} from '../interfaces/IChainlinkAggregator.sol';
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import {SafeERC20} from '../dependencies/openzeppelin/contracts/SafeERC20.sol';
/// @title ChainlinkProxyPriceProvider
/// @author Aave

View File

@ -3,12 +3,12 @@ pragma solidity ^0.6.8;
pragma experimental ABIEncoderV2;
import {Address} from '@openzeppelin/contracts/utils/Address.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {Address} from '../dependencies/openzeppelin/contracts/Address.sol';
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
import {LendingPoolAddressesProvider} from '../configuration/LendingPoolAddressesProvider.sol';
import {ILendingPool} from '../interfaces/ILendingPool.sol';
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import {SafeERC20} from '../dependencies/openzeppelin/contracts/SafeERC20.sol';
import {ReserveConfiguration} from '../libraries/configuration/ReserveConfiguration.sol';
/**

View File

@ -1,22 +1,21 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {SafeMath} from '../../dependencies/openzeppelin/contracts/SafeMath.sol';
import {IERC20} from '../../dependencies/openzeppelin/contracts/IERC20.sol';
import {FlashLoanReceiverBase} from '../../flashloan/base/FlashLoanReceiverBase.sol';
import {MintableERC20} from '../tokens/MintableERC20.sol';
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
import {SafeERC20} from '../../dependencies/openzeppelin/contracts/SafeERC20.sol';
import {ILendingPoolAddressesProvider} from '../../interfaces/ILendingPoolAddressesProvider.sol';
contract MockFlashLoanReceiver is FlashLoanReceiverBase {
using SafeMath for uint256;
using SafeERC20 for IERC20;
ILendingPoolAddressesProvider internal _provider;
event ExecutedWithFail(address _reserve, uint256 _amount, uint256 _fee);
event ExecutedWithSuccess(address _reserve, uint256 _amount, uint256 _fee);
event ExecutedWithFail(address[] _assets, uint256[] _amounts, uint256[] _premiums);
event ExecutedWithSuccess(address[] _assets, uint256[] _amounts, uint256[] _premiums);
bool _failExecution;
uint256 _amountToApprove;
@ -45,33 +44,40 @@ contract MockFlashLoanReceiver is FlashLoanReceiverBase {
}
function executeOperation(
address reserve,
uint256 amount,
uint256 fee,
address[] memory assets,
uint256[] memory amounts,
uint256[] memory premiums,
bytes memory params
) public override returns (bool) {
params;
//mint to this contract the specific amount
MintableERC20 token = MintableERC20(reserve);
//check the contract has the specified balance
require(amount <= IERC20(reserve).balanceOf(address(this)), 'Invalid balance for the contract');
uint256 amountToReturn = (_amountToApprove != 0) ? _amountToApprove : amount.add(fee);
if (_failExecution) {
emit ExecutedWithFail(reserve, amount, fee);
emit ExecutedWithFail(assets, amounts, premiums);
return !_simulateEOA;
}
//execution does not fail - mint tokens and return them to the _destination
//note: if the reserve is eth, the mock contract must receive at least _fee ETH before calling executeOperation
for (uint256 i = 0; i < assets.length; i++) {
//mint to this contract the specific amount
MintableERC20 token = MintableERC20(assets[i]);
token.mint(fee);
//check the contract has the specified balance
require(
amounts[i] <= IERC20(assets[i]).balanceOf(address(this)),
'Invalid balance for the contract'
);
IERC20(reserve).approve(_addressesProvider.getLendingPool(), amountToReturn);
uint256 amountToReturn = (_amountToApprove != 0)
? _amountToApprove
: amounts[i].add(premiums[i]);
//execution does not fail - mint tokens and return them to the _destination
//note: if the reserve is eth, the mock contract must receive at least _fee ETH before calling executeOperation
emit ExecutedWithSuccess(reserve, amount, fee);
token.mint(premiums[i]);
IERC20(assets[i]).approve(_addressesProvider.getLendingPool(), amountToReturn);
}
emit ExecutedWithSuccess(assets, amounts, premiums);
return true;
}

View File

@ -1,59 +0,0 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import {MintableERC20} from '../tokens/MintableERC20.sol';
import {ILendingPoolAddressesProvider} from '../../interfaces/ILendingPoolAddressesProvider.sol';
import {ISwapAdapter} from '../../interfaces/ISwapAdapter.sol';
import {ILendingPool} from '../../interfaces/ILendingPool.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
contract MockSwapAdapter is ISwapAdapter {
uint256 internal _amountToReturn;
bool internal _tryReentrancy;
ILendingPoolAddressesProvider public addressesProvider;
event Swapped(address fromAsset, address toAsset, uint256 fromAmount, uint256 receivedAmount);
constructor(ILendingPoolAddressesProvider provider) public {
addressesProvider = provider;
}
function setAmountToReturn(uint256 amount) public {
_amountToReturn = amount;
}
function setTryReentrancy(bool tryReentrancy) public {
_tryReentrancy = tryReentrancy;
}
function executeOperation(
address assetToSwapFrom,
address assetToSwapTo,
uint256 amountToSwap,
address fundsDestination,
bytes calldata params
) external override {
params;
IERC20(assetToSwapFrom).transfer(address(1), amountToSwap); // We don't want to keep funds here
MintableERC20(assetToSwapTo).mint(_amountToReturn);
IERC20(assetToSwapTo).approve(fundsDestination, _amountToReturn);
if (_tryReentrancy) {
ILendingPool(fundsDestination).repayWithCollateral(
assetToSwapFrom,
assetToSwapTo,
address(1), // Doesn't matter, we just want to test the reentrancy
1 ether, // Same
address(1), // Same
'0x'
);
}
emit Swapped(assetToSwapFrom, assetToSwapTo, amountToSwap, _amountToReturn);
}
function burnAsset(IERC20 asset, uint256 amount) public {
uint256 amountToBurn = (amount == type(uint256).max) ? asset.balanceOf(address(this)) : amount;
asset.transfer(address(0), amountToBurn);
}
}

View File

@ -14,4 +14,13 @@ contract MockAggregator {
function latestAnswer() external view returns (int256) {
return _latestAnswer;
}
function getTokenType() external view returns (uint256) {
return 1;
}
// function getSubTokens() external view returns (address[] memory) {
// TODO: implement mock for when multiple subtokens. Maybe we need to create diff mock contract
// to call it from the migration for this case??
// }
}

View File

@ -2,7 +2,7 @@
pragma solidity ^0.6.8;
import {ILendingRateOracle} from '../../interfaces/ILendingRateOracle.sol';
import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {Ownable} from '../../dependencies/openzeppelin/contracts/Ownable.sol';
contract LendingRateOracle is ILendingRateOracle, Ownable {
mapping(address => uint256) borrowRates;

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import {ERC20} from '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import {ERC20} from '../../dependencies/openzeppelin/contracts/ERC20.sol';
/**
* @title ERC20Mintable

View File

@ -2,7 +2,6 @@
pragma solidity ^0.6.8;
import {StableDebtToken} from '../../tokenization/StableDebtToken.sol';
import {LendingPool} from '../../lendingpool/LendingPool.sol';
contract MockStableDebtToken is StableDebtToken {
constructor(

View File

@ -2,7 +2,6 @@
pragma solidity ^0.6.8;
import {VariableDebtToken} from '../../tokenization/VariableDebtToken.sol';
import {LendingPool} from '../../lendingpool/LendingPool.sol';
contract MockVariableDebtToken is VariableDebtToken {
constructor(

View File

@ -2,15 +2,13 @@
pragma solidity ^0.6.8;
import {IncentivizedERC20} from './IncentivizedERC20.sol';
import {LendingPool} from '../lendingpool/LendingPool.sol';
import {ILendingPool} from '../interfaces/ILendingPool.sol';
import {WadRayMath} from '../libraries/math/WadRayMath.sol';
import {Errors} from '../libraries/helpers/Errors.sol';
import {
VersionedInitializable
} from '../libraries/openzeppelin-upgradeability/VersionedInitializable.sol';
import {VersionedInitializable} from '../libraries/aave-upgradeability/VersionedInitializable.sol';
import {IAToken} from './interfaces/IAToken.sol';
import {IERC20} from '../interfaces/IERC20.sol';
import {SafeERC20} from '../misc/SafeERC20.sol';
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
import {SafeERC20} from '../dependencies/openzeppelin/contracts/SafeERC20.sol';
/**
* @title Aave ERC20 AToken
@ -34,7 +32,7 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken {
uint256 public constant ATOKEN_REVISION = 0x1;
address public immutable UNDERLYING_ASSET_ADDRESS;
address public immutable RESERVE_TREASURY_ADDRESS;
LendingPool public immutable POOL;
ILendingPool public immutable POOL;
/// @dev owner => next valid nonce to submit with permit()
mapping(address => uint256) public _nonces;
@ -47,7 +45,7 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken {
}
constructor(
LendingPool pool,
ILendingPool pool,
address underlyingAssetAddress,
address reserveTreasuryAddress,
string memory tokenName,
@ -119,12 +117,15 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken {
* @param user the address receiving the minted tokens
* @param amount the amount of tokens to mint
* @param index the the last index of the reserve
* @return true if the the previous balance of the user is 0
*/
function mint(
address user,
uint256 amount,
uint256 index
) external override onlyLendingPool {
) external override onlyLendingPool returns (bool) {
uint256 previousBalance = super.balanceOf(user);
uint256 amountScaled = amount.rayDiv(index);
require(amountScaled != 0, Errors.INVALID_MINT_AMOUNT);
_mint(user, amountScaled);
@ -132,6 +133,8 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken {
//transfer event to track balances
emit Transfer(address(0), user, amount);
emit Mint(user, amount, index);
return previousBalance == 0;
}
/**

View File

@ -1,10 +1,10 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.6.8;
import {Context} from '../misc/Context.sol';
import {IERC20} from '../interfaces/IERC20.sol';
import {IERC20Detailed} from '../interfaces/IERC20Detailed.sol';
import {SafeMath} from '../libraries/math/SafeMath.sol';
import {Context} from '../dependencies/openzeppelin/contracts/Context.sol';
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
import {IERC20Detailed} from '../dependencies/openzeppelin/contracts/IERC20Detailed.sol';
import {SafeMath} from '../dependencies/openzeppelin/contracts/SafeMath.sol';
import {IAaveIncentivesController} from '../interfaces/IAaveIncentivesController.sol';
/**
@ -181,10 +181,10 @@ contract IncentivizedERC20 is Context, IERC20, IERC20Detailed {
_balances[recipient] = _balances[recipient].add(amount);
if (address(_incentivesController) != address(0)) {
uint256 totalSupply = _totalSupply;
_incentivesController.handleAction(sender, totalSupply, oldSenderBalance);
uint256 currentTotalSupply = _totalSupply;
_incentivesController.handleAction(sender, currentTotalSupply, oldSenderBalance);
if (sender != recipient) {
_incentivesController.handleAction(recipient, totalSupply, oldRecipientBalance);
_incentivesController.handleAction(recipient, currentTotalSupply, oldRecipientBalance);
}
}
}

View File

@ -1,9 +1,6 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import {Context} from '@openzeppelin/contracts/GSN/Context.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol';
import {DebtTokenBase} from './base/DebtTokenBase.sol';
import {MathUtils} from '../libraries/math/MathUtils.sol';
import {WadRayMath} from '../libraries/math/WadRayMath.sol';
@ -131,7 +128,7 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase {
_totalSupplyTimestamp = _timestamps[user] = uint40(block.timestamp);
//calculates the updated average stable rate
_avgStableRate = vars
vars.currentAvgStableRate = _avgStableRate = vars
.currentAvgStableRate
.rayMul(vars.previousSupply.wadToRay())
.add(rate.rayMul(vars.amountInRay))
@ -142,7 +139,15 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase {
// transfer event to track balances
emit Transfer(address(0), user, amount);
emit Mint(user, amount, previousBalance, currentBalance, balanceIncrease, vars.newStableRate);
emit Mint(
user,
amount,
previousBalance,
currentBalance,
balanceIncrease,
vars.newStableRate,
vars.currentAvgStableRate
);
}
/**
@ -158,17 +163,18 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase {
) = _calculateBalanceIncrease(user);
uint256 previousSupply = totalSupply();
uint256 newStableRate = 0;
//since the total supply and each single user debt accrue separately,
//there might be accumulation errors so that the last borrower repaying
//might actually try to repay more than the available debt supply.
//in this case we simply set the total supply and the avg stable rate to 0
if (previousSupply <= amount) {
_avgStableRate = 0;
newStableRate = _avgStableRate = 0;
_totalSupply = 0;
} else {
uint256 nextSupply = _totalSupply = previousSupply.sub(amount);
_avgStableRate = _avgStableRate
newStableRate = _avgStableRate = _avgStableRate
.rayMul(previousSupply.wadToRay())
.sub(_usersData[user].rayMul(amount.wadToRay()))
.rayDiv(nextSupply.wadToRay());
@ -193,14 +199,13 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase {
// transfer event to track balances
emit Transfer(user, address(0), amount);
emit Burn(user, amount, previousBalance, currentBalance, balanceIncrease);
emit Burn(user, amount, previousBalance, currentBalance, balanceIncrease, newStableRate);
}
/**
* @dev Calculates the increase in balance since the last user interaction
* @param user The address of the user for which the interest is being accumulated
* @return The previous principal balance, the new principal balance, the balance increase
* and the new user index
* @return The previous principal balance, the new principal balance and the balance increase
**/
function _calculateBalanceIncrease(address user)
internal

View File

@ -1,9 +1,6 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import {Context} from '@openzeppelin/contracts/GSN/Context.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol';
import {DebtTokenBase} from './base/DebtTokenBase.sol';
import {WadRayMath} from '../libraries/math/WadRayMath.sol';
import {IVariableDebtToken} from './interfaces/IVariableDebtToken.sol';
@ -46,7 +43,7 @@ contract VariableDebtToken is DebtTokenBase, IVariableDebtToken {
return 0;
}
return scaledBalance.rayMul(POOL.getReserveNormalizedVariableDebt(UNDERLYING_ASSET));
return scaledBalance.rayMul(POOL.getReserveNormalizedVariableDebt(UNDERLYING_ASSET_ADDRESS));
}
/**
@ -54,12 +51,14 @@ contract VariableDebtToken is DebtTokenBase, IVariableDebtToken {
* @param user the user receiving the debt
* @param amount the amount of debt being minted
* @param index the variable debt index of the reserve
* @return true if the the previous balance of the user is 0
**/
function mint(
address user,
uint256 amount,
uint256 index
) external override onlyLendingPool {
) external override onlyLendingPool returns (bool) {
uint256 previousBalance = super.balanceOf(user);
uint256 amountScaled = amount.rayDiv(index);
require(amountScaled != 0, Errors.INVALID_MINT_AMOUNT);
@ -67,6 +66,8 @@ contract VariableDebtToken is DebtTokenBase, IVariableDebtToken {
emit Transfer(address(0), user, amount);
emit Mint(user, amount, index);
return previousBalance == 0;
}
/**
@ -101,7 +102,8 @@ contract VariableDebtToken is DebtTokenBase, IVariableDebtToken {
* @return the total supply
**/
function totalSupply() public virtual override view returns (uint256) {
return super.totalSupply().rayMul(POOL.getReserveNormalizedVariableDebt(UNDERLYING_ASSET));
return
super.totalSupply().rayMul(POOL.getReserveNormalizedVariableDebt(UNDERLYING_ASSET_ADDRESS));
}
/**

View File

@ -1,13 +1,10 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import {Context} from '@openzeppelin/contracts/GSN/Context.sol';
import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol';
import {ILendingPoolAddressesProvider} from '../../interfaces/ILendingPoolAddressesProvider.sol';
import {ILendingPool} from '../../interfaces/ILendingPool.sol';
import {
VersionedInitializable
} from '../../libraries/openzeppelin-upgradeability/VersionedInitializable.sol';
} from '../../libraries/aave-upgradeability/VersionedInitializable.sol';
import {IncentivizedERC20} from '../IncentivizedERC20.sol';
import {Errors} from '../../libraries/helpers/Errors.sol';
@ -18,8 +15,8 @@ import {Errors} from '../../libraries/helpers/Errors.sol';
*/
abstract contract DebtTokenBase is IncentivizedERC20, VersionedInitializable {
address internal immutable UNDERLYING_ASSET;
ILendingPool internal immutable POOL;
address public immutable UNDERLYING_ASSET_ADDRESS;
ILendingPool public immutable POOL;
mapping(address => uint256) internal _usersData;
/**
@ -42,7 +39,7 @@ abstract contract DebtTokenBase is IncentivizedERC20, VersionedInitializable {
address incentivesController
) public IncentivizedERC20(name, symbol, 18, incentivesController) {
POOL = ILendingPool(pool);
UNDERLYING_ASSET = underlyingAssetAddress;
UNDERLYING_ASSET_ADDRESS = underlyingAssetAddress;
}
/**
@ -61,10 +58,6 @@ abstract contract DebtTokenBase is IncentivizedERC20, VersionedInitializable {
_setDecimals(decimals);
}
function underlyingAssetAddress() public view returns (address) {
return UNDERLYING_ASSET;
}
/**
* @dev Being non transferrable, the debt token does not implement any of the
* standard ERC20 functions for transfer and allowance.

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.6.8;
import {IERC20} from '../../interfaces/IERC20.sol';
import {IERC20} from '../../dependencies/openzeppelin/contracts/IERC20.sol';
import {IScaledBalanceToken} from './IScaledBalanceToken.sol';
interface IAToken is IERC20, IScaledBalanceToken {

View File

@ -21,7 +21,7 @@ interface IScaledBalanceToken {
address user,
uint256 amount,
uint256 index
) external;
) external returns (bool);
/**
* @dev returns the principal balance of the user. The principal balance is the last

View File

@ -21,6 +21,7 @@ interface IStableDebtToken {
* @param currentBalance the current balance of the user
* @param balanceIncrease the debt increase since the last update
* @param newRate the rate of the debt after the minting
* @param avgStableRate the new average stable rate after the minting
**/
event Mint(
address indexed user,
@ -28,7 +29,8 @@ interface IStableDebtToken {
uint256 previousBalance,
uint256 currentBalance,
uint256 balanceIncrease,
uint256 newRate
uint256 newRate,
uint256 avgStableRate
);
/**
@ -38,13 +40,15 @@ interface IStableDebtToken {
* @param previousBalance the previous balance of the user
* @param currentBalance the current balance of the user
* @param balanceIncrease the debt increase since the last update
* @param avgStableRate the new average stable rate after the minting
**/
event Burn(
address indexed user,
uint256 amount,
uint256 previousBalance,
uint256 currentBalance,
uint256 balanceIncrease
uint256 balanceIncrease,
uint256 avgStableRate
);
/**

View File

@ -0,0 +1,13 @@
pragma solidity ^0.6;
/**
* @title ITokenConfiguration
* @author Aave
* @dev common interface between aTokens and debt tokens to fetch the
* token configuration
**/
interface ITokenConfiguration {
function UNDERLYING_ASSET_ADDRESS() external view returns (address);
function POOL() external view returns (address);
}

View File

@ -5,7 +5,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x58F132FBB86E21545A4Bace3C19f1C05d86d7A22",
"address": "0xDFbeeed692AA81E7f86E72F7ACbEA2A1C4d63544",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -19,7 +19,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0xa4bcDF64Cdd5451b6ac3743B414124A6299B65FF",
"address": "0x5191aA68c7dB195181Dd2441dBE23A48EA24b040",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -27,7 +27,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"kovan": {
"address": "0x20e080B395341B3b617E893c281c7E999C942276",
"address": "0xF9a2E6D57c691f3aa5269858178a13Ef06378579",
"deployer": "0x85e4A467343c0dc4aDAB74Af84448D9c45D8ae6F"
}
},
@ -37,7 +37,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x5A0773Ff307Bf7C71a832dBB5312237fD3437f9F",
"address": "0xa89E20284Bd638F31b0011D0fC754Fc9d2fa73e3",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -45,7 +45,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"kovan": {
"address": "0x00219a2958f758122106Bb8A801AFc1B70897663",
"address": "0xf3266d89e6742fAE2C35D05eD549cd4e117300a7",
"deployer": "0x85e4A467343c0dc4aDAB74Af84448D9c45D8ae6F"
}
},
@ -73,10 +73,10 @@
"address": "0x6642B57e4265BAD868C17Fc1d1F4F88DBBA04Aa8"
},
"localhost": {
"address": "0x65e0Cd5B8904A02f2e00BC6f58bf881998D54BDe"
"address": "0x9Ec55627757348b322c8dD0865D704649bFa0c7b"
},
"kovan": {
"address": "0x50C9d3aD9399c1EEf6DDeadF8e57fF69994F552e"
"address": "0x1aae278bCcdb95817c7A546d752fC662F09b6DBa"
}
},
"LendingPoolDataProvider": {
@ -89,10 +89,10 @@
"address": "0xD9273d497eDBC967F39d419461CfcF382a0A822e"
},
"localhost": {
"address": "0x5d12dDe3286D94E0d85F9D3B01B7099cfA0aBCf1"
"address": "0x3EE716e38f21e5FC16BFDB773db24D63C637A5d8"
},
"kovan": {
"address": "0x6d1e69bB0578699dd955Eefbf23aAC65c0DA5cE7"
"address": "0x8E05A3054cb736258FaF4638D07058cE6e294d2C"
}
},
"PriceOracle": {
@ -101,7 +101,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x1750499D05Ed1674d822430FB960d5F6731fDf64",
"address": "0x5889354f21A1C8D8D2f82669d778f6Dab778B519",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -115,7 +115,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0xEC1C93A9f6a9e18E97784c76aC52053587FcDB89",
"address": "0xC452C5244F701108B4e8E8BCe693160046b30332",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -129,7 +129,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x7B6C3e5486D9e6959441ab554A889099eed76290",
"address": "0x0B63c002cb44B2e5e580C3B3560a27F4101D95c0",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -147,7 +147,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0xD83D2773a7873ae2b5f8Fb92097e20a8C64F691E",
"address": "0xCeB290A2C6614BF23B2faa0f0B8067F29C48DB0F",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -165,12 +165,16 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x626FdE749F9d499d3777320CAf29484B624ab84a",
"address": "0x7C95b1ad025F0C9aB14192f87bF2aD53889bE4F7",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
"address": "0x626FdE749F9d499d3777320CAf29484B624ab84a",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"kovan": {
"address": "0x47341CE48FfE1cbD91991578B880a18c45cdB5CA",
"deployer": "0x85e4A467343c0dc4aDAB74Af84448D9c45D8ae6F"
}
},
"LendingPoolLiquidationManager": {
@ -223,7 +227,7 @@
"address": "0xBEF0d4b9c089a5883741fC14cbA352055f35DDA2"
},
"localhost": {
"address": "0x2B681757d757fbB80cc51c6094cEF5eE75bF55aA"
"address": "0x9c91aEaD98b1354C7B0EAfb8ff539d0796c79894"
},
"coverage": {
"address": "0x2B681757d757fbB80cc51c6094cEF5eE75bF55aA"
@ -231,11 +235,11 @@
},
"WalletBalanceProvider": {
"buidlerevm": {
"address": "0x2cfcA5785261fbC88EFFDd46fCFc04c22525F9e4",
"address": "0xDf73fC454FA018051D4a1509e63D11530A59DE10",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0xDf73fC454FA018051D4a1509e63D11530A59DE10",
"address": "0x145b7B6368Df63e7F3497b0A948B30fC1A4d5E55",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -249,7 +253,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x7c2C195CD6D34B8F845992d380aADB2730bB9C6F",
"address": "0x010e948B9B7D30771E23346C0B17a4D5Ff04e300",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -263,7 +267,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x8858eeB3DfffA017D4BCE9801D340D36Cf895CCf",
"address": "0x79094eDB848047e87a4B8a64ab5Ee2f527791bC0",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -277,7 +281,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x0078371BDeDE8aAc7DeBfFf451B74c5EDB385Af7",
"address": "0xEE0A69d0Bb1312685870Dd7E20AcAD66b6f6264F",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -291,7 +295,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0xf4e77E5Da47AC3125140c470c71cBca77B5c638c",
"address": "0x631B367fBE1dbB934bC039aAA0C9eC2EE5943fd5",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -305,7 +309,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x3619DbE27d7c1e7E91aA738697Ae7Bc5FC3eACA5",
"address": "0xf55Af78B3f3059fACF166Aa338FFe059A14e75F6",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -319,7 +323,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x038B86d9d8FAFdd0a02ebd1A476432877b0107C8",
"address": "0xD5A0587aAEB195028909E98930B391dFB3f9F589",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -333,7 +337,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x1A1FEe7EeD918BD762173e4dc5EfDB8a78C924A8",
"address": "0xaD3AdbC18E4AD090034A6C74Eda61f4310dce313",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -347,7 +351,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x500D1d6A4c7D8Ae28240b47c8FCde034D827fD5e",
"address": "0x25a88BbA9c8D2a46e3Ff4bFe98712DF7A1044fB6",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -361,7 +365,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0xc4905364b78a742ccce7B890A89514061E47068D",
"address": "0x16d1802cd7cfcb67955BBBa26bAae1cE559B5F5B",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -375,7 +379,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0xD6C850aeBFDC46D7F4c207e445cC0d6B0919BDBe",
"address": "0xE58d8c88f5A670f16BE8F7864707170F43e943A6",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -389,7 +393,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x8B5B7a6055E54a36fF574bbE40cf2eA68d5554b3",
"address": "0xfdAF4f6e47e854c05bE158993d32872e784F0502",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -403,7 +407,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0xEcc0a6dbC0bb4D51E4F84A315a9e5B0438cAD4f0",
"address": "0x92edC13A10036A3C50396f2B63148a3e9a8D589e",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -417,7 +421,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x20Ce94F404343aD2752A2D01b43fa407db9E0D00",
"address": "0xE5C277cDb7E10372918Ac54Ce54022910A24FE88",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -431,7 +435,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x1d80315fac6aBd3EfeEbE97dEc44461ba7556160",
"address": "0xF5742a599a0F4520089cbf2EBBa66Bb4F471B85F",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -445,7 +449,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x2D8553F9ddA85A9B3259F6Bf26911364B85556F5",
"address": "0x380EF388e13D8cAdeACef6eF682C7B7D85865076",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -459,7 +463,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x52d3b94181f8654db2530b0fEe1B19173f519C52",
"address": "0xC89577DED8441e52C17C13D527b85b225C5c8311",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -473,7 +477,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0xd15468525c35BDBC1eD8F2e09A00F8a173437f2f",
"address": "0xD4b06774A717Ff5A7c20c8712e31c6BbfFcb1F01",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -487,7 +491,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x7e35Eaf7e8FBd7887ad538D4A38Df5BbD073814a",
"address": "0xbe66dC9DFEe580ED968403e35dF7b5159f873df8",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -501,7 +505,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x5bcb88A0d20426e451332eE6C4324b0e663c50E0",
"address": "0x93AfC6Df4bB8F62F2493B19e577f8382c0BA9EBC",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -515,7 +519,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x3521eF8AaB0323004A6dD8b03CE890F4Ea3A13f5",
"address": "0x75Ded61646B5945BdDd0CD9a9Db7c8288DA6F810",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -529,7 +533,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x53369fd4680FfE3DfF39Fc6DDa9CfbfD43daeA2E",
"address": "0xdE7c40e675bF1aA45c18cCbaEb9662B16b0Ddf7E",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -543,7 +547,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0xB00cC45B4a7d3e1FEE684cFc4417998A1c183e6d",
"address": "0xEcb928A3c079a1696Aa5244779eEc3dE1717fACd",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -557,58 +561,81 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x58F132FBB86E21545A4Bace3C19f1C05d86d7A22",
"address": "0xDFbeeed692AA81E7f86E72F7ACbEA2A1C4d63544",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
}
},
"LendingPoolAddressesProviderRegistry": {
"buidlerevm": {
"address": "0x5A0773Ff307Bf7C71a832dBB5312237fD3437f9F",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
}
},
"LendingPool": {
"buidlerevm": {
"address": "0xe7536f450378748E1BD4645D3c77ec38e0F3ba28"
},
"localhost": {
"address": "0x987223924D2DD6c6efB601756850f3886ECbceF6"
},
"coverage": {
"address": "0x2cfcA5785261fbC88EFFDd46fCFc04c22525F9e4"
},
"kovan": {
"address": "0xfF28b837352d9531bAb6dFF3650D7831192117F7",
"deployer": "0x85e4A467343c0dc4aDAB74Af84448D9c45D8ae6F"
}
},
"LendingPoolConfigurator": {
"buidlerevm": {
"address": "0x830bceA96E56DBC1F8578f75fBaC0AF16B32A07d",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0xaca5aCeB6f44845d07Fd339a51F0bd52Bb3D8D1A",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
"address": "0x58F132FBB86E21545A4Bace3C19f1C05d86d7A22",
"address": "0xB660Fdd109a95718cB9d20E3A89EE6cE342aDcB6",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"kovan": {
"address": "0x0EDc241FdA0dF39EB1B9eB1236217BBe72Ab911D",
"deployer": "0x85e4A467343c0dc4aDAB74Af84448D9c45D8ae6F"
}
},
"PriceOracle": {
"buidlerevm": {
"address": "0x1750499D05Ed1674d822430FB960d5F6731fDf64",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x9bD0Bec44106D8Ea8fFb6296d7A84742a290E064",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
}
},
"ChainlinkProxyPriceProvider": {
"buidlerevm": {
"address": "0x7B6C3e5486D9e6959441ab554A889099eed76290",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"kovan": {
"address": "0x293f5BcC66762c28a5d3Bd8512a799D457F5296D",
"deployer": "0x85e4A467343c0dc4aDAB74Af84448D9c45D8ae6F"
}
},
"AToken": {
"localhost": {
"address": "0x00f126cCA2266bFb634Ed6DB17c4C74fb8cA5177",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
}
},
"AaveProtocolTestHelpers": {
"buidlerevm": {
"address": "0xe7536f450378748E1BD4645D3c77ec38e0F3ba28"
},
"localhost": {
"address": "0x2cfcA5785261fbC88EFFDd46fCFc04c22525F9e4"
},
"coverage": {
"address": "0x2cfcA5785261fbC88EFFDd46fCFc04c22525F9e4"
"address": "0x689d0586F88cDEa1E1003F838DD943415E4EB1e5"
}
},
"StableDebtToken": {
"buidlerevm": {
"address": "0x830bceA96E56DBC1F8578f75fBaC0AF16B32A07d",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0xB660Fdd109a95718cB9d20E3A89EE6cE342aDcB6",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
"address": "0xB660Fdd109a95718cB9d20E3A89EE6cE342aDcB6",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
}
},
"VariableDebtToken": {
"buidlerevm": {
"address": "0xA0AB1cB92A4AF81f84dCd258155B5c25D247b54E",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x830bceA96E56DBC1F8578f75fBaC0AF16B32A07d",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
"address": "0x830bceA96E56DBC1F8578f75fBaC0AF16B32A07d",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
}
},
"AToken": {
"localhost": {
"address": "0xA0AB1cB92A4AF81f84dCd258155B5c25D247b54E",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"DefaultReserveInterestRateStrategy": {
"buidlerevm": {
"address": "0x5f7134cd38C826a7649f9Cc47dda24d834DD2967",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
@ -616,15 +643,19 @@
"coverage": {
"address": "0xA0AB1cB92A4AF81f84dCd258155B5c25D247b54E",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"kovan": {
"address": "0xf303Ae6F24C29D94E367fdb5C7aE04D32BEbF13E",
"deployer": "0x85e4A467343c0dc4aDAB74Af84448D9c45D8ae6F"
}
},
"MockAToken": {
"StableDebtToken": {
"buidlerevm": {
"address": "0x392E5355a0e88Bd394F717227c752670fb3a8020",
"address": "0x3bDA11B584dDff7F66E0cFe1da1562c92B45db60",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0xa89E20284Bd638F31b0011D0fC754Fc9d2fa73e3",
"address": "0xbF538F34cb100bAeEE55aa1F036D33F03b03d900",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -632,13 +663,13 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
}
},
"WETH": {
"VariableDebtToken": {
"buidlerevm": {
"address": "0xf784709d2317D872237C4bC22f867d1BAe2913AB",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0xf784709d2317D872237C4bC22f867d1BAe2913AB",
"address": "0xff1B1B810F5DCe853a9b1819DE220D532D1CFeF2",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -646,13 +677,13 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
}
},
"MockStableDebtToken": {
"AToken": {
"buidlerevm": {
"address": "0x3b050AFb4ac4ACE646b31fF3639C1CD43aC31460",
"address": "0x392E5355a0e88Bd394F717227c752670fb3a8020",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0xaA935993065F2dDB1d13623B1941C7AEE3A60F23",
"address": "0x7436d6adaA697413F00cb63E1A2A854bF2Aec5A1",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -660,13 +691,13 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
}
},
"MockVariableDebtToken": {
"MockFlashLoanReceiver": {
"buidlerevm": {
"address": "0xEBAB67ee3ef604D5c250A53b4b8fcbBC6ec3007C",
"address": "0x3b050AFb4ac4ACE646b31fF3639C1CD43aC31460",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"localhost": {
"address": "0x35A2624888e207e4B3434E9a9E250bF6Ee68FeA3",
"address": "0x2A7BE996B8801ED21f2f45148791D402811A2106",
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
},
"coverage": {
@ -674,7 +705,7 @@
"deployer": "0xc783df8a850f42e7F7e57013759C285caa701eB6"
}
},
"MockSwapAdapter": {
"WalletBalanceProvider": {
"buidlerevm": {
"address": "0xDf73fC454FA018051D4a1509e63D11530A59DE10"
},
@ -682,7 +713,17 @@
"address": "0xBEF0d4b9c089a5883741fC14cbA352055f35DDA2"
},
"localhost": {
"address": "0xBEF0d4b9c089a5883741fC14cbA352055f35DDA2"
"address": "0x48FAde2E719B770E1783d03466dAEe98b5183538"
}
},
"MockFlashRepayAdapter": {
"buidlerevm": {
"address": "0xDf73fC454FA018051D4a1509e63D11530A59DE10"
}
},
"MockFlashLiquiditySwapAdapter": {
"buidlerevm": {
"address": "0x2cfcA5785261fbC88EFFDd46fCFc04c22525F9e4"
}
}
}

View File

@ -2,6 +2,8 @@ version: '3.5'
services:
contracts-env:
env_file:
- .env
build:
context: ./
working_dir: /src

View File

@ -1,6 +1,6 @@
import {Contract, Signer, utils, ethers} from 'ethers';
import {CommonsConfig} from '../config/commons';
import {getDb, BRE} from './misc-utils';
import {getDb, BRE, waitForTx} from './misc-utils';
import {
tEthereumAddress,
eContractid,
@ -47,7 +47,6 @@ const {
export type MockTokenMap = {[symbol: string]: MintableERC20};
import {ZERO_ADDRESS} from './constants';
import {MockSwapAdapter} from '../types/MockSwapAdapter';
import {signTypedData_v4, TypedData} from 'eth-sig-util';
import {fromRpcSig, ECDSASignature} from 'ethereumjs-util';
import {SignerWithAddress} from '../test/helpers/make-suite';
@ -101,7 +100,7 @@ export const deployContract = async <ContractType extends Contract>(
const contract = (await (await BRE.ethers.getContractFactory(contractName)).deploy(
...args
)) as ContractType;
await waitForTx(contract.deployTransaction);
await registerContractInJsonDb(<eContractid>contractName, contract);
return contract;
};
@ -205,6 +204,7 @@ export const deployLendingPool = async (verify?: boolean) => {
);
const factory = await linkLibrariesToArtifact(lendingPoolArtifact);
const lendingPool = await factory.deploy();
await waitForTx(lendingPool.deployTransaction);
const instance = (await lendingPool.deployed()) as LendingPool;
if (verify) {
await verifyContract(eContractid.LendingPool, instance.address, []);
@ -321,8 +321,6 @@ export const deployWalletBalancerProvider = async (
}
return instance;
};
export const deployMockSwapAdapter = async (addressesProvider: tEthereumAddress) =>
await deployContract<MockSwapAdapter>(eContractid.MockSwapAdapter, [addressesProvider]);
export const deployAaveProtocolTestHelpers = async (
addressesProvider: tEthereumAddress,
@ -548,14 +546,6 @@ export const getMockFlashLoanReceiver = async (address?: tEthereumAddress) => {
);
};
export const getMockSwapAdapter = async (address?: tEthereumAddress) => {
return await getContract<MockSwapAdapter>(
eContractid.MockSwapAdapter,
address ||
(await getDb().get(`${eContractid.MockSwapAdapter}.${BRE.network.name}`).value()).address
);
};
export const getLendingRateOracle = async (address?: tEthereumAddress) => {
return await getContract<LendingRateOracle>(
eContractid.LendingRateOracle,
@ -798,7 +788,7 @@ export const initReserves = async (
stableRateSlope2,
},
] = (Object.entries(reservesParams) as [string, IReserveParams][])[reserveParamIndex];
console.log('deploy def reserve');
console.log('deploy the interest rate strategy for ', assetSymbol);
const rateStrategyContract = await deployDefaultReserveInterestRateStrategy(
[
lendingPoolAddressesProvider.address,
@ -811,7 +801,7 @@ export const initReserves = async (
verify
);
console.log('deploy stable deb totken ', assetSymbol);
console.log('deploy the stable debt totken for ', assetSymbol);
const stableDebtToken = await deployStableDebtToken(
[
`Aave stable debt bearing ${assetSymbol === 'WETH' ? 'ETH' : assetSymbol}`,
@ -823,7 +813,7 @@ export const initReserves = async (
verify
);
console.log('deploy var deb totken ', assetSymbol);
console.log('deploy the variable debt totken for ', assetSymbol);
const variableDebtToken = await deployVariableDebtToken(
[
`Aave variable debt bearing ${assetSymbol === 'WETH' ? 'ETH' : assetSymbol}`,
@ -835,7 +825,7 @@ export const initReserves = async (
verify
);
console.log('deploy a token ', assetSymbol);
console.log('deploy the aToken for ', assetSymbol);
const aToken = await deployGenericAToken(
[
lendingPool.address,
@ -855,9 +845,8 @@ export const initReserves = async (
}
}
console.log('init reserve currency ', assetSymbol);
console.log('initialize the reserve ', assetSymbol);
await lendingPoolConfigurator.initReserve(
tokenAddress,
aToken.address,
stableDebtToken.address,
variableDebtToken.address,

View File

@ -1,13 +1,20 @@
import {exit} from 'process';
import fs from 'fs';
import globby from 'globby';
import {file} from 'tmp-promise';
import {BRE} from './misc-utils';
const listSolidityFiles = (dir: string) => globby(`${dir}/**/*.sol`);
const fatalErrors = [
`The address provided as argument contains a contract, but its bytecode`,
`Daily limit of 100 source code submissions reached`,
];
export const SUPPORTED_ETHERSCAN_NETWORKS = ['main', 'ropsten', 'kovan'];
export const getEtherscanPath = async (contractName: string) => {
const compilerInput = await BRE.run('compile:get-compiler-input');
const paths = Object.keys(compilerInput.sources);
const paths = await listSolidityFiles(BRE.config.paths.sources);
const path = paths.find((p) => p.includes(contractName));
if (!path) {
throw new Error(
@ -79,12 +86,22 @@ export const runTaskWithRetry = async (
cleanup();
} else {
cleanup();
console.error('[ERROR] Errors after all the retries, check the logs for more information.');
console.error(
'[ETHERSCAN][ERROR] Errors after all the retries, check the logs for more information.'
);
}
} catch (error) {
counter--;
console.info(`[INFO] Retrying attemps: ${counter}.`);
console.error('[ERROR]', error.message);
console.info(`[ETHERSCAN][[INFO] Retrying attemps: ${counter}.`);
console.error('[ETHERSCAN][[ERROR]', error.message);
if (fatalErrors.some((fatalError) => error.message.includes(fatalError))) {
console.error(
'[ETHERSCAN][[ERROR] Fatal error detected, skip retries and resume deployment.'
);
return;
}
await runTaskWithRetry(task, params, counter, msDelay, cleanup);
}
};

View File

@ -2,6 +2,7 @@ import {iMultiPoolsAssets, IReserveParams, tEthereumAddress} from './types';
import {LendingPool} from '../types/LendingPool';
import {LendingPoolConfigurator} from '../types/LendingPoolConfigurator';
import {AaveProtocolTestHelpers} from '../types/AaveProtocolTestHelpers';
import {waitForTx} from './misc-utils';
export const enableReservesToBorrow = async (
reservesParams: iMultiPoolsAssets<IReserveParams>,
@ -29,7 +30,14 @@ export const enableReservesToBorrow = async (
continue;
}
await lendingPoolConfigurator.enableBorrowingOnReserve(tokenAddress, stableBorrowRateEnabled);
console.log('Enabling borrowing on reserve ', assetSymbol);
await waitForTx(
await lendingPoolConfigurator.enableBorrowingOnReserve(
tokenAddress,
stableBorrowRateEnabled
)
);
} catch (e) {
console.log(
`Enabling reserve for borrowings for ${assetSymbol} failed with error ${e}. Skipped.`
@ -66,11 +74,15 @@ export const enableReservesAsCollateral = async (
}
try {
await lendingPoolConfigurator.enableReserveAsCollateral(
tokenAddress,
baseLTVAsCollateral,
liquidationThreshold,
liquidationBonus
console.log(`Enabling reserve ${assetSymbol} as collateral`);
await waitForTx(
await lendingPoolConfigurator.enableReserveAsCollateral(
tokenAddress,
baseLTVAsCollateral,
liquidationThreshold,
liquidationBonus
)
);
} catch (e) {
console.log(

View File

@ -41,7 +41,7 @@ export const increaseTime = async (secondsToIncrease: number) => {
await BRE.ethers.provider.send('evm_mine', []);
};
export const waitForTx = async (tx: ContractTransaction) => await tx.wait();
export const waitForTx = async (tx: ContractTransaction) => await tx.wait(1);
export const filterMapBy = (raw: {[key: string]: any}, fn: (key: string) => boolean) =>
Object.keys(raw)

View File

@ -30,7 +30,7 @@ export const setInitialMarketRatesInRatesOracle = async (
const [, assetAddress] = (Object.entries(assetsAddresses) as [string, string][])[
assetAddressIndex
];
await lendingRateOracleInstance.setMarketBorrowRate(assetAddress, borrowRate);
await waitForTx(await lendingRateOracleInstance.setMarketBorrowRate(assetAddress, borrowRate));
console.log('added Market Borrow Rate for: ', assetSymbol);
}
};

View File

@ -44,7 +44,6 @@ export enum eContractid {
LendingPoolCollateralManager = 'LendingPoolCollateralManager',
InitializableAdminUpgradeabilityProxy = 'InitializableAdminUpgradeabilityProxy',
MockFlashLoanReceiver = 'MockFlashLoanReceiver',
MockSwapAdapter = 'MockSwapAdapter',
WalletBalanceProvider = 'WalletBalanceProvider',
AToken = 'AToken',
MockAToken = 'MockAToken',
@ -106,6 +105,7 @@ export enum ProtocolErrors {
//require error messages - LendingPoolAddressesProviderRegistry
PROVIDER_NOT_REGISTERED = '37', // 'Provider is not registered'
INVALID_ADDRESSES_PROVIDER_ID = '68',
//return error messages - LendingPoolCollateralManager
HEALTH_FACTOR_NOT_BELOW_THRESHOLD = '38', // 'Health factor is not below the threshold'
@ -344,6 +344,7 @@ export interface ICommonConfiguration {
ReserveAssets: iParamsPerNetwork<SymbolMap<tEthereumAddress>>;
ReservesConfig: iMultiPoolsAssets<IReserveParams>;
ATokenDomainSeparator: iParamsPerNetwork<string>;
ProxyPriceProvider: iParamsPerNetwork<tEthereumAddress>;
}
export interface IAaveConfiguration extends ICommonConfiguration {

7321
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@
"buidler:kovan": "buidler --network kovan",
"buidler:ropsten": "buidler--network ropsten",
"buidler:main": "buidler --network main",
"buidler:docker": "buidler --network buidlerevm_docker",
"buidler help": "buidler help",
"compile": "SKIP_LOAD=true buidler compile",
"types-gen": "typechain --target ethers-v5 --outDir ./types './artifacts/*.json'",
@ -15,6 +16,8 @@
"test-scenarios": "buidler test test/__setup.spec.ts test/scenario.spec.ts",
"aave:evm:dev:migration": "buidler aave:dev",
"aave:evm:full:migration": "buidler aave:full",
"aave:docker:dev:migration": "npm run buidler:docker -- aave:dev",
"aave:docker:full:migration": "npm run buidler:docker -- aave:full",
"aave:kovan:dev:migration": "npm run buidler:kovan -- aave:dev --verify",
"aave:kovan:full:migration": "npm run buidler:kovan -- aave:full --verify",
"aave:ropsten:dev:migration": "npm run buidler:ropsten -- aave:dev --verify",
@ -36,6 +39,7 @@
"test-liquidate": "buidler test test/__setup.spec.ts test/liquidation-atoken.spec.ts",
"test-pausable": "buidler test test/__setup.spec.ts test/pausable-functions.spec.ts",
"test-permit": "buidler test test/__setup.spec.ts test/atoken-permit.spec.ts",
"test-subgraph:scenarios": "buidler --network buidlerevm_docker test test/__setup.spec.ts test/subgraph-scenarios.spec.ts",
"dev:coverage": "buidler coverage --network coverage",
"dev:deployment": "buidler dev-deployment",
"dev:deployExample": "buidler deploy-Example",
@ -59,7 +63,7 @@
"@types/mocha": "7.0.2",
"@types/node": "14.0.5",
"bignumber.js": "9.0.0",
"buidler-gas-reporter": "^0.1.3",
"buidler-gas-reporter": "^0.1.4",
"buidler-typechain": "0.1.1",
"chai": "4.2.0",
"chai-bignumber": "3.0.0",
@ -68,6 +72,7 @@
"ethereum-waffle": "3.0.2",
"ethereumjs-util": "7.0.2",
"ethers": "5.0.8",
"globby": "^11.0.1",
"husky": "^4.2.5",
"lowdb": "1.0.0",
"prettier": "^2.0.5",

View File

@ -37,4 +37,11 @@ task(
ProviderId
)
);
//register the proxy price provider on the addressesProvider
const proxyProvider = getParamPerNetwork(poolConfig.ProxyPriceProvider, network);
if (proxyProvider && proxyProvider !== '') {
await waitForTx(await addressesProvider.setPriceOracle(proxyProvider));
}
});

View File

@ -24,10 +24,7 @@ task('aave:full', 'Deploy development enviroment')
console.log('2. Deploy lending pool');
await localBRE.run('full:deploy-lending-pool', {verify});
console.log('3. Deploy oracles');
await localBRE.run('full:deploy-oracles', {verify, pool: POOL_NAME});
console.log('4. Initialize lending pool');
console.log('3. Initialize lending pool');
await localBRE.run('full:initialize-lending-pool', {verify, pool: POOL_NAME});
console.log('\nFinished migrations');

3077
test.log

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,6 @@ import {
registerContractInJsonDb,
getPairsTokenAggregator,
initReserves,
deployMockSwapAdapter,
deployLendingRateOracle,
} from '../helpers/contracts-helpers';
import {Signer} from 'ethers';
@ -239,9 +238,6 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => {
const mockFlashLoanReceiver = await deployMockFlashLoanReceiver(addressesProvider.address);
await insertContractAddressInDb(eContractid.MockFlashLoanReceiver, mockFlashLoanReceiver.address);
const mockSwapAdapter = await deployMockSwapAdapter(addressesProvider.address);
await insertContractAddressInDb(eContractid.MockSwapAdapter, mockSwapAdapter.address);
await deployWalletBalancerProvider(addressesProvider.address);
console.timeEnd('setup');

View File

@ -18,6 +18,15 @@ makeSuite('AddressesProviderRegistry', (testEnv: TestEnv) => {
);
});
it('tries to register an addresses provider with id 0', async () => {
const {users, registry} = testEnv;
const {INVALID_ADDRESSES_PROVIDER_ID} = ProtocolErrors;
await expect(registry.registerAddressesProvider(users[2].address, '0')).to.be.revertedWith(
INVALID_ADDRESSES_PROVIDER_ID
);
});
it('Registers a new mock addresses provider', async () => {
const {users, registry} = testEnv;

View File

@ -1,258 +0,0 @@
import {makeSuite, TestEnv} from './helpers/make-suite';
import {MockSwapAdapter} from '../types/MockSwapAdapter';
import {getMockSwapAdapter} from '../helpers/contracts-helpers';
import {ProtocolErrors} from '../helpers/types';
import {ethers} from 'ethers';
import {APPROVAL_AMOUNT_LENDING_POOL} from '../helpers/constants';
import {getContractsData, getTxCostAndTimestamp} from './helpers/actions';
import {calcExpectedATokenBalance} from './helpers/utils/calculations';
import {waitForTx} from '../helpers/misc-utils';
import {advanceBlock, timeLatest} from '../helpers/misc-utils';
const {expect} = require('chai');
makeSuite('LendingPool SwapDeposit function', (testEnv: TestEnv) => {
let _mockSwapAdapter = {} as MockSwapAdapter;
const {
HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD,
NO_UNFREEZED_RESERVE,
NO_ACTIVE_RESERVE,
INVALID_EQUAL_ASSETS_TO_SWAP,
} = ProtocolErrors;
before(async () => {
_mockSwapAdapter = await getMockSwapAdapter();
});
it('Should not allow to swap if from equal to', async () => {
const {pool, weth} = testEnv;
await expect(
pool.swapLiquidity(
_mockSwapAdapter.address,
weth.address,
weth.address,
'1'.toString(),
'0x10'
)
).to.be.revertedWith(INVALID_EQUAL_ASSETS_TO_SWAP);
});
it('Should not allow to swap if from or to reserves are not active', async () => {
const {pool, weth, dai, configurator} = testEnv;
await configurator.deactivateReserve(weth.address);
await expect(
pool.swapLiquidity(
_mockSwapAdapter.address,
weth.address,
dai.address,
'1'.toString(),
'0x10'
)
).to.be.revertedWith(NO_ACTIVE_RESERVE);
await configurator.activateReserve(weth.address);
await configurator.deactivateReserve(dai.address);
await expect(
pool.swapLiquidity(
_mockSwapAdapter.address,
weth.address,
dai.address,
'1'.toString(),
'0x10'
)
).to.be.revertedWith(NO_ACTIVE_RESERVE);
//cleanup state
await configurator.activateReserve(dai.address);
});
it('Deposits WETH into the reserve', async () => {
const {pool, weth, users} = testEnv;
const amountToDeposit = ethers.utils.parseEther('1');
for (const signer of [weth.signer, users[2].signer]) {
const connectedWETH = weth.connect(signer);
await connectedWETH.mint(amountToDeposit);
await connectedWETH.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await pool
.connect(signer)
.deposit(weth.address, amountToDeposit, await signer.getAddress(), '0');
}
});
it('User tries to swap more then he can, revert expected', async () => {
const {pool, weth, dai} = testEnv;
await expect(
pool.swapLiquidity(
_mockSwapAdapter.address,
weth.address,
dai.address,
ethers.utils.parseEther('1.1'),
'0x10'
)
).to.be.revertedWith('55');
});
it('User tries to swap more then available on the reserve', async () => {
const {pool, weth, dai, users, aEth, deployer} = testEnv;
await pool.borrow(weth.address, ethers.utils.parseEther('0.1'), 1, 0, deployer.address);
await pool.connect(users[2].signer).withdraw(weth.address, ethers.utils.parseEther('1'));
await expect(
pool.swapLiquidity(
_mockSwapAdapter.address,
weth.address,
dai.address,
ethers.utils.parseEther('1'),
'0x10'
)
).to.be.revertedWith('55');
});
it('User tries to swap correct amount', async () => {
const {pool, weth, dai, aEth, aDai, helpersContract} = testEnv;
const userAddress = await pool.signer.getAddress();
const amountToSwap = ethers.utils.parseEther('0.25');
const amountToReturn = ethers.utils.parseEther('0.5');
await _mockSwapAdapter.setAmountToReturn(amountToReturn);
const {
reserveData: wethReserveDataBefore,
userData: wethUserDataBefore,
} = await getContractsData(weth.address, userAddress, testEnv);
const {reserveData: daiReserveDataBefore, userData: daiUserDataBefore} = await getContractsData(
dai.address,
userAddress,
testEnv
);
const reserveBalanceWETHBefore = await weth.balanceOf(aEth.address);
const reserveBalanceDAIBefore = await dai.balanceOf(aDai.address);
const txReceipt = await waitForTx(
await pool.swapLiquidity(
_mockSwapAdapter.address,
weth.address,
dai.address,
amountToSwap,
'0x10'
)
);
const {txTimestamp} = await getTxCostAndTimestamp(txReceipt);
const userATokenBalanceWETHAfter = await aEth.balanceOf(userAddress);
const userATokenBalanceDAIAfter = await aDai.balanceOf(userAddress);
const reserveBalanceWETHAfter = await weth.balanceOf(aEth.address);
const reserveBalanceDAIAfter = await dai.balanceOf(aDai.address);
expect(userATokenBalanceWETHAfter.toString()).to.be.equal(
calcExpectedATokenBalance(wethReserveDataBefore, wethUserDataBefore, txTimestamp)
.minus(amountToSwap.toString())
.toString(),
'was burned incorrect amount of user funds'
);
expect(userATokenBalanceDAIAfter.toString()).to.be.equal(
calcExpectedATokenBalance(daiReserveDataBefore, daiUserDataBefore, txTimestamp)
.plus(amountToReturn.toString())
.toString(),
'was minted incorrect amount of user funds'
);
expect(reserveBalanceWETHAfter.toString()).to.be.equal(
reserveBalanceWETHBefore.sub(amountToSwap).toString(),
'was sent incorrect amount if reserve funds'
);
expect(reserveBalanceDAIAfter.toString()).to.be.equal(
reserveBalanceDAIBefore.add(amountToReturn).toString(),
'was received incorrect amount if reserve funds'
);
expect(
(await helpersContract.getUserReserveData(dai.address, userAddress)).usageAsCollateralEnabled
).to.be.equal(true, 'usage as collateral was not enabled on destination reserve for the user');
});
it('User tries to drop HF below one', async () => {
const {pool, weth, dai, deployer} = testEnv;
const amountToSwap = ethers.utils.parseEther('0.3');
const amountToReturn = ethers.utils.parseEther('0.5');
await _mockSwapAdapter.setAmountToReturn(amountToReturn);
await pool.borrow(weth.address, ethers.utils.parseEther('0.3'), 1, 0, deployer.address);
await expect(
pool.swapLiquidity(_mockSwapAdapter.address, weth.address, dai.address, amountToSwap, '0x10')
).to.be.revertedWith(HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD);
});
it('Should set usage as collateral to false if no leftovers after swap', async () => {
const {pool, weth, dai, users} = testEnv;
const userAddress = await pool.signer.getAddress();
// add more liquidity to allow user 0 to swap everything he has
await weth.connect(users[2].signer).mint(ethers.utils.parseEther('1'));
await pool
.connect(users[2].signer)
.deposit(weth.address, ethers.utils.parseEther('1'), users[2].address, '0');
// cleanup borrowings, to be abe to swap whole weth
const amountToRepay = ethers.utils.parseEther('0.5');
await weth.mint(amountToRepay);
await pool.repay(weth.address, amountToRepay, '1', userAddress);
const txTimestamp = (await timeLatest()).plus(100);
const {
reserveData: wethReserveDataBefore,
userData: wethUserDataBefore,
} = await getContractsData(weth.address, userAddress, testEnv);
const amountToSwap = calcExpectedATokenBalance(
wethReserveDataBefore,
wethUserDataBefore,
txTimestamp.plus('1')
);
await advanceBlock(txTimestamp.toNumber());
await pool.swapLiquidity(
_mockSwapAdapter.address,
weth.address,
dai.address,
amountToSwap.toString(),
'0x10'
);
const {userData: wethUserDataAfter} = await getContractsData(
weth.address,
userAddress,
testEnv
);
expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.equal(
false,
'usageAsCollateralEnabled are not set to false'
);
});
it('Should not allow to swap if to reserve are freezed', async () => {
const {pool, weth, dai, configurator} = testEnv;
await configurator.freezeReserve(dai.address);
await expect(
pool.swapLiquidity(
_mockSwapAdapter.address,
weth.address,
dai.address,
'1'.toString(),
'0x10'
)
).to.be.revertedWith(NO_UNFREEZED_RESERVE);
//cleanup state
await configurator.unfreezeReserve(dai.address);
});
});

View File

@ -1,932 +0,0 @@
import {TestEnv, makeSuite} from './helpers/make-suite';
import {APPROVAL_AMOUNT_LENDING_POOL, oneEther} from '../helpers/constants';
import {ethers} from 'ethers';
import BigNumber from 'bignumber.js';
import {
calcExpectedVariableDebtTokenBalance,
calcExpectedStableDebtTokenBalance,
} from './helpers/utils/calculations';
import {getContractsData} from './helpers/actions';
import {timeLatest, BRE, increaseTime, waitForTx} from '../helpers/misc-utils';
import {ProtocolErrors} from '../helpers/types';
import {convertToCurrencyDecimals} from '../helpers/contracts-helpers';
import {expectRepayWithCollateralEvent} from './repay-with-collateral.spec';
const {expect} = require('chai');
const {parseUnits, parseEther} = ethers.utils;
makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEnv) => {
const {INVALID_HF, COLLATERAL_CANNOT_BE_LIQUIDATED, IS_PAUSED} = ProtocolErrors;
it('User 1 provides some liquidity for others to borrow', async () => {
const {pool, weth, dai, usdc, deployer} = testEnv;
await weth.mint(parseEther('200'));
await weth.approve(pool.address, parseEther('200'));
await pool.deposit(weth.address, parseEther('200'), deployer.address, 0);
await dai.mint(parseEther('20000'));
await dai.approve(pool.address, parseEther('20000'));
await pool.deposit(dai.address, parseEther('20000'), deployer.address, 0);
await usdc.mint(parseEther('20000'));
await usdc.approve(pool.address, parseEther('20000'));
await pool.deposit(usdc.address, parseEther('20000'), deployer.address, 0);
});
it('User 5 liquidate User 3 collateral, all his variable debt and part of the stable', async () => {
const {pool, weth, usdc, users, mockSwapAdapter, oracle, helpersContract} = testEnv;
const user = users[2];
const liquidator = users[4];
const amountToDeposit = parseEther('20');
const amountToBorrow = parseUnits('40', 6);
await weth.connect(user.signer).mint(amountToDeposit);
await weth.connect(user.signer).approve(pool.address, amountToDeposit);
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
const usdcPrice = await oracle.getAssetPrice(usdc.address);
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0, user.address);
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 1, 0, user.address);
const {userData: wethUserDataBefore} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {
reserveData: usdcReserveDataBefore,
userData: usdcUserDataBefore,
} = await getContractsData(usdc.address, user.address, testEnv);
// Set HF below 1
await oracle.setAssetPrice(
usdc.address,
new BigNumber(usdcPrice.toString()).multipliedBy(60).toFixed(0)
);
const userGlobalDataPrior = await pool.getUserAccountData(user.address);
expect(userGlobalDataPrior.healthFactor.toString()).to.be.bignumber.lt(oneEther, INVALID_HF);
const amountToRepay = parseUnits('80', 6);
await mockSwapAdapter.setAmountToReturn(amountToRepay);
const txReceipt = await waitForTx(
await pool
.connect(liquidator.signer)
.repayWithCollateral(
weth.address,
usdc.address,
user.address,
amountToRepay,
mockSwapAdapter.address,
'0x'
)
);
const repayWithCollateralTimestamp = await timeLatest();
const {userData: wethUserDataAfter} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {userData: usdcUserDataAfter} = await getContractsData(
usdc.address,
user.address,
testEnv
);
const collateralPrice = await oracle.getAssetPrice(weth.address);
const principalPrice = await oracle.getAssetPrice(usdc.address);
const collateralDecimals = (
await helpersContract.getReserveConfigurationData(weth.address)
).decimals.toString();
const principalDecimals = (
await helpersContract.getReserveConfigurationData(usdc.address)
).decimals.toString();
const expectedCollateralLiquidated = new BigNumber(principalPrice.toString())
.times(new BigNumber(amountToRepay.toString()).times(105))
.times(new BigNumber(10).pow(collateralDecimals))
.div(
new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals))
)
.div(100)
.decimalPlaces(0, BigNumber.ROUND_DOWN);
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
usdcReserveDataBefore,
usdcUserDataBefore,
new BigNumber(repayWithCollateralTimestamp)
).minus(usdcUserDataBefore.currentVariableDebt);
const expectedStableDebtIncrease = calcExpectedStableDebtTokenBalance(
usdcUserDataBefore.principalStableDebt,
usdcUserDataBefore.stableBorrowRate,
usdcUserDataBefore.stableRateLastUpdated,
new BigNumber(repayWithCollateralTimestamp)
).minus(usdcUserDataBefore.currentStableDebt);
expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.equal(
new BigNumber(usdcUserDataBefore.currentVariableDebt)
.minus(amountToRepay.toString())
.plus(expectedVariableDebtIncrease)
.gte(0)
? new BigNumber(usdcUserDataBefore.currentVariableDebt)
.minus(amountToRepay.toString())
.plus(expectedVariableDebtIncrease)
.toString()
: '0',
'INVALID_VARIABLE_DEBT_POSITION'
);
const stableDebtRepaid = new BigNumber(usdcUserDataBefore.currentVariableDebt)
.minus(amountToRepay.toString())
.plus(expectedVariableDebtIncrease)
.abs();
expect(usdcUserDataAfter.currentStableDebt).to.be.bignumber.equal(
new BigNumber(usdcUserDataBefore.currentStableDebt)
.minus(stableDebtRepaid)
.plus(expectedStableDebtIncrease)
.gte(0)
? new BigNumber(usdcUserDataBefore.currentStableDebt)
.minus(stableDebtRepaid)
.plus(expectedStableDebtIncrease)
.toString()
: '0',
'INVALID_STABLE_DEBT_POSITION'
);
expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(
new BigNumber(wethUserDataBefore.currentATokenBalance).minus(
expectedCollateralLiquidated.toString()
),
'INVALID_COLLATERAL_POSITION'
);
const eventsEmitted = txReceipt.events || [];
expectRepayWithCollateralEvent(
eventsEmitted,
pool.address,
weth.address,
usdc.address,
user.address
);
// Resets USDC Price
await oracle.setAssetPrice(usdc.address, usdcPrice);
});
it('User 3 deposits WETH and borrows USDC at Variable', async () => {
const {pool, weth, usdc, users, oracle} = testEnv;
const user = users[2];
const amountToDeposit = parseEther('10');
await weth.connect(user.signer).mint(amountToDeposit);
await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
const userGlobalData = await pool.getUserAccountData(user.address);
const usdcPrice = await oracle.getAssetPrice(usdc.address);
const amountUSDCToBorrow = await convertToCurrencyDecimals(
usdc.address,
new BigNumber(userGlobalData.availableBorrowsETH.toString())
.div(usdcPrice.toString())
.multipliedBy(0.95)
.toFixed(0)
);
await pool.connect(user.signer).borrow(usdc.address, amountUSDCToBorrow, 2, 0, user.address);
});
it('User 5 liquidates half the USDC loan of User 3 by swapping his WETH collateral', async () => {
const {pool, weth, usdc, users, mockSwapAdapter, oracle, helpersContract} = testEnv;
const user = users[2];
const liquidator = users[4];
// Sets USDC Price higher to decrease health factor below 1
const usdcPrice = await oracle.getAssetPrice(usdc.address);
await oracle.setAssetPrice(
usdc.address,
new BigNumber(usdcPrice.toString()).multipliedBy(1.15).toFixed(0)
);
const userGlobalData = await pool.getUserAccountData(user.address);
expect(userGlobalData.healthFactor.toString()).to.be.bignumber.lt(oneEther, INVALID_HF);
const {userData: wethUserDataBefore} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {
reserveData: usdcReserveDataBefore,
userData: usdcUserDataBefore,
} = await getContractsData(usdc.address, user.address, testEnv);
const amountToRepay = usdcReserveDataBefore.totalVariableDebt.dividedBy(2).toFixed(0);
await mockSwapAdapter.setAmountToReturn(amountToRepay);
await waitForTx(
await pool
.connect(liquidator.signer)
.repayWithCollateral(
weth.address,
usdc.address,
user.address,
amountToRepay,
mockSwapAdapter.address,
'0x'
)
);
const repayWithCollateralTimestamp = await timeLatest();
const {userData: wethUserDataAfter} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {userData: usdcUserDataAfter} = await getContractsData(
usdc.address,
user.address,
testEnv
);
const collateralPrice = await oracle.getAssetPrice(weth.address);
const principalPrice = await oracle.getAssetPrice(usdc.address);
const collateralDecimals = (
await helpersContract.getReserveConfigurationData(weth.address)
).decimals.toString();
const principalDecimals = (
await helpersContract.getReserveConfigurationData(usdc.address)
).decimals.toString();
const expectedCollateralLiquidated = new BigNumber(principalPrice.toString())
.times(new BigNumber(amountToRepay.toString()).times(105))
.times(new BigNumber(10).pow(collateralDecimals))
.div(
new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals))
)
.div(100)
.decimalPlaces(0, BigNumber.ROUND_DOWN);
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
usdcReserveDataBefore,
usdcUserDataBefore,
new BigNumber(repayWithCollateralTimestamp)
).minus(usdcUserDataBefore.currentVariableDebt);
expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual(
new BigNumber(usdcUserDataBefore.currentVariableDebt)
.minus(amountToRepay.toString())
.plus(expectedVariableDebtIncrease)
.toString(),
'INVALID_DEBT_POSITION'
);
expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.almostEqual(
new BigNumber(wethUserDataBefore.currentATokenBalance).minus(
expectedCollateralLiquidated.toString()
),
'INVALID_COLLATERAL_POSITION'
);
expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.true;
// Resets USDC Price
await oracle.setAssetPrice(usdc.address, usdcPrice);
});
it('Revert expected. User 5 tries to liquidate an User 3 collateral a currency he havent borrow', async () => {
const {pool, weth, dai, users, oracle, mockSwapAdapter, usdc} = testEnv;
const user = users[2];
const liquidator = users[4];
const amountToRepay = parseUnits('10', 6);
// Sets USDC Price higher to decrease health factor below 1
const usdcPrice = await oracle.getAssetPrice(usdc.address);
await oracle.setAssetPrice(
usdc.address,
new BigNumber(usdcPrice.toString()).multipliedBy(6.4).toFixed(0)
);
const userGlobalData = await pool.getUserAccountData(user.address);
expect(userGlobalData.healthFactor.toString()).to.be.bignumber.lt(oneEther, INVALID_HF);
await expect(
pool
.connect(liquidator.signer)
.repayWithCollateral(
weth.address,
dai.address,
user.address,
amountToRepay,
mockSwapAdapter.address,
'0x'
)
).to.be.revertedWith('40');
await oracle.setAssetPrice(usdc.address, usdcPrice);
});
it('User 5 liquidates all the USDC loan of User 3 by swapping his WETH collateral', async () => {
const {pool, weth, usdc, users, mockSwapAdapter, oracle, helpersContract} = testEnv;
const user = users[2];
const liquidator = users[4];
// Sets USDC Price higher to decrease health factor below 1
const usdcPrice = await oracle.getAssetPrice(usdc.address);
await oracle.setAssetPrice(
usdc.address,
new BigNumber(usdcPrice.toString()).multipliedBy(1.35).toFixed(0)
);
const userGlobalData = await pool.getUserAccountData(user.address);
expect(userGlobalData.healthFactor.toString()).to.be.bignumber.lt(oneEther, INVALID_HF);
const {userData: wethUserDataBefore} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {
reserveData: usdcReserveDataBefore,
userData: usdcUserDataBefore,
} = await getContractsData(usdc.address, user.address, testEnv);
const amountToRepay = usdcReserveDataBefore.totalVariableDebt.toFixed(0);
await mockSwapAdapter.setAmountToReturn(amountToRepay);
await waitForTx(
await pool
.connect(liquidator.signer)
.repayWithCollateral(
weth.address,
usdc.address,
user.address,
amountToRepay,
mockSwapAdapter.address,
'0x'
)
);
const repayWithCollateralTimestamp = await timeLatest();
const {userData: wethUserDataAfter} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {userData: usdcUserDataAfter} = await getContractsData(
usdc.address,
user.address,
testEnv
);
const collateralPrice = await oracle.getAssetPrice(weth.address);
const principalPrice = await oracle.getAssetPrice(usdc.address);
const collateralDecimals = (
await helpersContract.getReserveConfigurationData(weth.address)
).decimals.toString();
const principalDecimals = (
await helpersContract.getReserveConfigurationData(usdc.address)
).decimals.toString();
const expectedCollateralLiquidated = new BigNumber(principalPrice.toString())
.times(new BigNumber(amountToRepay.toString()).times(105))
.times(new BigNumber(10).pow(collateralDecimals))
.div(
new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals))
)
.div(100)
.decimalPlaces(0, BigNumber.ROUND_DOWN);
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
usdcReserveDataBefore,
usdcUserDataBefore,
new BigNumber(repayWithCollateralTimestamp)
).minus(usdcUserDataBefore.currentVariableDebt);
expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual(
new BigNumber(usdcUserDataBefore.currentVariableDebt)
.minus(amountToRepay.toString())
.plus(expectedVariableDebtIncrease)
.toString(),
'INVALID_DEBT_POSITION'
);
expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(
new BigNumber(wethUserDataBefore.currentATokenBalance).minus(
expectedCollateralLiquidated.toString()
),
'INVALID_COLLATERAL_POSITION'
);
// Resets USDC Price
await oracle.setAssetPrice(usdc.address, usdcPrice);
});
it('User 2 deposit WETH and borrows DAI at Variable', async () => {
const {pool, weth, dai, users, oracle} = testEnv;
const user = users[1];
const amountToDeposit = ethers.utils.parseEther('2');
await weth.connect(user.signer).mint(amountToDeposit);
await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
const userGlobalData = await pool.getUserAccountData(user.address);
const daiPrice = await oracle.getAssetPrice(dai.address);
const amountDAIToBorrow = await convertToCurrencyDecimals(
dai.address,
new BigNumber(userGlobalData.availableBorrowsETH.toString())
.div(daiPrice.toString())
.multipliedBy(0.9)
.toFixed(0)
);
await pool.connect(user.signer).borrow(dai.address, amountDAIToBorrow, 2, 0, user.address);
});
it('It is not possible to do reentrancy on repayWithCollateral()', async () => {
const {pool, weth, dai, users, mockSwapAdapter, oracle} = testEnv;
const user = users[1];
const liquidator = users[4];
// Sets DAI Price higher to decrease health factor below 1
const daiPrice = await oracle.getAssetPrice(dai.address);
await oracle.setAssetPrice(
dai.address,
new BigNumber(daiPrice.toString()).multipliedBy(1.4).toFixed(0)
);
const {reserveData: daiReserveDataBefore} = await getContractsData(
dai.address,
user.address,
testEnv
);
const amountToRepay = daiReserveDataBefore.totalVariableDebt.toString();
await waitForTx(await mockSwapAdapter.setTryReentrancy(true));
await mockSwapAdapter.setAmountToReturn(amountToRepay);
await expect(
pool
.connect(liquidator.signer)
.repayWithCollateral(
weth.address,
dai.address,
user.address,
amountToRepay,
mockSwapAdapter.address,
'0x'
)
).to.be.revertedWith('53');
// Resets DAI Price
await oracle.setAssetPrice(dai.address, daiPrice);
// Resets mock
await waitForTx(await mockSwapAdapter.setTryReentrancy(false));
});
it('User 5 tries to liquidate User 2 DAI Variable loan using his WETH collateral, with good HF', async () => {
const {pool, weth, dai, users, mockSwapAdapter} = testEnv;
const user = users[1];
const liquidator = users[4];
const {reserveData: daiReserveDataBefore} = await getContractsData(
dai.address,
user.address,
testEnv
);
// First half
const amountToRepay = daiReserveDataBefore.totalVariableDebt.dividedBy(2).toString();
await mockSwapAdapter.setAmountToReturn(amountToRepay);
await expect(
pool
.connect(liquidator.signer)
.repayWithCollateral(
weth.address,
dai.address,
user.address,
amountToRepay,
mockSwapAdapter.address,
'0x'
)
).to.be.revertedWith('38');
});
it('User 5 liquidates User 2 DAI Variable loan using his WETH collateral, half the amount', async () => {
const {pool, weth, dai, users, mockSwapAdapter, oracle, helpersContract} = testEnv;
const user = users[1];
const liquidator = users[4];
// Sets DAI Price higher to decrease health factor below 1
const daiPrice = await oracle.getAssetPrice(dai.address);
await oracle.setAssetPrice(
dai.address,
new BigNumber(daiPrice.toString()).multipliedBy(1.4).toFixed(0)
);
const userGlobalData = await pool.getUserAccountData(user.address);
expect(userGlobalData.healthFactor.toString()).to.be.bignumber.lt(oneEther, INVALID_HF);
const {userData: wethUserDataBefore} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {reserveData: daiReserveDataBefore, userData: daiUserDataBefore} = await getContractsData(
dai.address,
user.address,
testEnv
);
// First half
const amountToRepay = daiReserveDataBefore.totalVariableDebt
.multipliedBy(0.6)
.toFixed(0)
.toString();
await mockSwapAdapter.setAmountToReturn(amountToRepay);
await waitForTx(
await pool
.connect(liquidator.signer)
.repayWithCollateral(
weth.address,
dai.address,
user.address,
amountToRepay,
mockSwapAdapter.address,
'0x'
)
);
const repayWithCollateralTimestamp = await timeLatest();
const {userData: wethUserDataAfter} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {userData: daiUserDataAfter} = await getContractsData(dai.address, user.address, testEnv);
const collateralPrice = await oracle.getAssetPrice(weth.address);
const principalPrice = await oracle.getAssetPrice(dai.address);
const collateralDecimals = (
await helpersContract.getReserveConfigurationData(weth.address)
).decimals.toString();
const principalDecimals = (
await helpersContract.getReserveConfigurationData(dai.address)
).decimals.toString();
const expectedCollateralLiquidated = new BigNumber(principalPrice.toString())
.times(new BigNumber(amountToRepay.toString()).times(105))
.times(new BigNumber(10).pow(collateralDecimals))
.div(
new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals))
)
.div(100)
.decimalPlaces(0, BigNumber.ROUND_DOWN);
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
daiReserveDataBefore,
daiUserDataBefore,
new BigNumber(repayWithCollateralTimestamp)
).minus(daiUserDataBefore.currentVariableDebt);
expect(daiUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual(
new BigNumber(daiUserDataBefore.currentVariableDebt)
.minus(amountToRepay.toString())
.plus(expectedVariableDebtIncrease)
.toString()
);
expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(
new BigNumber(wethUserDataBefore.currentATokenBalance).minus(
expectedCollateralLiquidated.toString()
)
);
expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.true;
// Resets DAI price
await oracle.setAssetPrice(dai.address, daiPrice);
});
it('User 2 tries to repay remaining DAI Variable loan using his WETH collateral', async () => {
const {pool, weth, dai, users, mockSwapAdapter, oracle, helpersContract} = testEnv;
const user = users[1];
const {userData: wethUserDataBefore} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {reserveData: daiReserveDataBefore, userData: daiUserDataBefore} = await getContractsData(
dai.address,
user.address,
testEnv
);
await increaseTime(1000);
// Repay the remaining DAI
const amountToRepay = daiReserveDataBefore.totalVariableDebt.toString();
await mockSwapAdapter.setAmountToReturn(amountToRepay);
const receipt = await waitForTx(
await pool
.connect(user.signer)
.repayWithCollateral(
weth.address,
dai.address,
user.address,
amountToRepay,
mockSwapAdapter.address,
'0x'
)
);
const repayWithCollateralTimestamp = (await BRE.ethers.provider.getBlock(receipt.blockNumber))
.timestamp;
const {userData: wethUserDataAfter} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {userData: daiUserDataAfter} = await getContractsData(dai.address, user.address, testEnv);
const collateralPrice = await oracle.getAssetPrice(weth.address);
const principalPrice = await oracle.getAssetPrice(dai.address);
const collateralDecimals = (
await helpersContract.getReserveConfigurationData(weth.address)
).decimals.toString();
const principalDecimals = (
await helpersContract.getReserveConfigurationData(dai.address)
).decimals.toString();
const expectedCollateralLiquidated = new BigNumber(principalPrice.toString())
.times(new BigNumber(amountToRepay.toString()).times(105))
.times(new BigNumber(10).pow(collateralDecimals))
.div(
new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals))
)
.div(100)
.decimalPlaces(0, BigNumber.ROUND_DOWN);
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
daiReserveDataBefore,
daiUserDataBefore,
new BigNumber(repayWithCollateralTimestamp)
).minus(daiUserDataBefore.currentVariableDebt);
expect(daiUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual(
new BigNumber(daiUserDataBefore.currentVariableDebt)
.minus(amountToRepay.toString())
.plus(expectedVariableDebtIncrease)
.toString()
);
expect(
new BigNumber(wethUserDataBefore.currentATokenBalance).minus(
expectedCollateralLiquidated.toString()
)
).to.be.bignumber.equal(wethUserDataAfter.currentATokenBalance);
});
it('Liquidator tries to repay 4 user a bigger amount that what can be swapped of a particular collateral, repaying only the maximum allowed by that collateral', async () => {
const {pool, weth, dai, usdc, users, mockSwapAdapter, oracle, helpersContract} = testEnv;
const user = users[3];
const liquidator = users[5];
const amountToDepositWeth = parseEther('0.1');
const amountToDepositDAI = parseEther('500');
const amountToBorrowVariable = parseUnits('80', '6');
await weth.connect(user.signer).mint(amountToDepositWeth);
await dai.connect(user.signer).mint(amountToDepositDAI);
await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await dai.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await pool.connect(user.signer).deposit(weth.address, amountToDepositWeth, user.address, '0');
await pool.connect(user.signer).deposit(dai.address, amountToDepositDAI, user.address, '0');
await pool
.connect(user.signer)
.borrow(usdc.address, amountToBorrowVariable, 2, 0, user.address);
const amountToRepay = amountToBorrowVariable;
const {userData: wethUserDataBefore} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {
reserveData: usdcReserveDataBefore,
userData: usdcUserDataBefore,
} = await getContractsData(usdc.address, user.address, testEnv);
// Set HF below 1
const daiPrice = await oracle.getAssetPrice(dai.address);
await oracle.setAssetPrice(
dai.address,
new BigNumber(daiPrice.toString()).multipliedBy(0.1).toFixed(0)
);
const userGlobalDataPrior = await pool.getUserAccountData(user.address);
expect(userGlobalDataPrior.healthFactor.toString()).to.be.bignumber.lt(oneEther, INVALID_HF);
// Execute liquidation
await mockSwapAdapter.setAmountToReturn(amountToRepay);
await waitForTx(
await pool
.connect(liquidator.signer)
.repayWithCollateral(
weth.address,
usdc.address,
user.address,
amountToRepay,
mockSwapAdapter.address,
'0x'
)
);
const repayWithCollateralTimestamp = await timeLatest();
const {userData: wethUserDataAfter} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {userData: usdcUserDataAfter} = await getContractsData(
usdc.address,
user.address,
testEnv
);
const collateralPrice = await oracle.getAssetPrice(weth.address);
const principalPrice = await oracle.getAssetPrice(usdc.address);
const collateralConfig = await helpersContract.getReserveConfigurationData(weth.address);
const collateralDecimals = collateralConfig.decimals.toString();
const principalDecimals = (
await helpersContract.getReserveConfigurationData(usdc.address)
).decimals.toString();
const collateralLiquidationBonus = collateralConfig.liquidationBonus.toString();
const expectedDebtCovered = new BigNumber(collateralPrice.toString())
.times(new BigNumber(wethUserDataBefore.currentATokenBalance.toString()))
.times(new BigNumber(10).pow(principalDecimals))
.div(
new BigNumber(principalPrice.toString()).times(new BigNumber(10).pow(collateralDecimals))
)
.div(new BigNumber(collateralLiquidationBonus).div(10000).toString())
.decimalPlaces(0, BigNumber.ROUND_DOWN);
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
usdcReserveDataBefore,
usdcUserDataBefore,
new BigNumber(repayWithCollateralTimestamp)
).minus(usdcUserDataBefore.currentVariableDebt);
expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual(
new BigNumber(usdcUserDataBefore.currentVariableDebt)
.minus(expectedDebtCovered.toString())
.plus(expectedVariableDebtIncrease),
'INVALID_VARIABLE_DEBT_POSITION'
);
expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.false;
expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(0);
// Resets DAI Price
await oracle.setAssetPrice(dai.address, daiPrice);
});
it('User 4 deposits WETH, LEND and DAI, then borrows USDC at Variable, then disables WETH as collateral', async () => {
const {pool, weth, dai, usdc, users} = testEnv;
const user = users[4];
const amountWETHToDeposit = parseEther('10');
const amountDAIToDeposit = parseEther('100');
const amountToBorrow = parseUnits('75', 6);
await weth.connect(user.signer).mint(amountWETHToDeposit);
await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await pool.connect(user.signer).deposit(weth.address, amountWETHToDeposit, user.address, '0');
await dai.connect(user.signer).mint(amountDAIToDeposit);
await dai.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await pool.connect(user.signer).deposit(dai.address, amountDAIToDeposit, user.address, '0');
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0, user.address);
});
it('Liquidator tries to liquidate User 5 USDC loan by swapping his WETH collateral, should revert due WETH collateral disabled', async () => {
const {pool, weth, dai, usdc, users, mockSwapAdapter, oracle} = testEnv;
const user = users[4];
const liquidator = users[5];
const amountToRepay = parseUnits('65', 6);
// User 5 Disable WETH as collateral
await pool.connect(user.signer).setUserUseReserveAsCollateral(weth.address, false);
const {userData: wethUserDataBefore} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {
reserveData: usdcReserveDataBefore,
userData: usdcUserDataBefore,
} = await getContractsData(usdc.address, user.address, testEnv);
expect(wethUserDataBefore.usageAsCollateralEnabled).to.be.false;
//drop the price to set the HF below 1
const daiPrice = await oracle.getAssetPrice(dai.address);
await oracle.setAssetPrice(
dai.address,
new BigNumber(daiPrice.toString()).multipliedBy(0.9).toFixed(0)
);
// Liquidator should NOT be able to liquidate himself with WETH, even if is disabled
await mockSwapAdapter.setAmountToReturn(amountToRepay);
await expect(
pool
.connect(liquidator.signer)
.repayWithCollateral(
weth.address,
usdc.address,
user.address,
amountToRepay,
mockSwapAdapter.address,
'0x'
)
).to.be.revertedWith(COLLATERAL_CANNOT_BE_LIQUIDATED);
const repayWithCollateralTimestamp = await timeLatest();
const {userData: wethUserDataAfter} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {userData: usdcUserDataAfter} = await getContractsData(
usdc.address,
user.address,
testEnv
);
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
usdcReserveDataBefore,
usdcUserDataBefore,
new BigNumber(repayWithCollateralTimestamp)
).minus(usdcUserDataBefore.currentVariableDebt);
expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual(
new BigNumber(usdcUserDataBefore.currentVariableDebt)
.plus(expectedVariableDebtIncrease)
.toString(),
'INVALID_DEBT_POSITION'
);
expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.false;
});
});

View File

@ -48,8 +48,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await pool.flashLoan(
_mockFlashLoanReceiver.address,
weth.address,
ethers.utils.parseEther('0.8'),
[weth.address],
[ethers.utils.parseEther('0.8')],
0,
'0x10',
'0'
@ -77,8 +77,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
const reserveDataBefore = await helpersContract.getReserveData(weth.address);
const txResult = await pool.flashLoan(
_mockFlashLoanReceiver.address,
weth.address,
'1000720000000000000',
[weth.address],
['1000720000000000000'],
0,
'0x10',
'0'
@ -108,13 +108,13 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
.connect(caller.signer)
.flashLoan(
_mockFlashLoanReceiver.address,
weth.address,
ethers.utils.parseEther('0.8'),
[weth.address],
[ethers.utils.parseEther('0.8')],
0,
'0x10',
'0'
)
).to.be.revertedWith(TRANSFER_AMOUNT_EXCEEDS_BALANCE);
).to.be.revertedWith(SAFEERC20_LOWLEVEL_CALL);
});
it('Takes WETH flashloan, simulating a receiver as EOA (revert expected)', async () => {
@ -128,8 +128,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
.connect(caller.signer)
.flashLoan(
_mockFlashLoanReceiver.address,
weth.address,
ethers.utils.parseEther('0.8'),
[weth.address],
[ethers.utils.parseEther('0.8')],
0,
'0x10',
'0'
@ -148,8 +148,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
.connect(caller.signer)
.flashLoan(
_mockFlashLoanReceiver.address,
weth.address,
ethers.utils.parseEther('0.8'),
[weth.address],
[ethers.utils.parseEther('0.8')],
4,
'0x10',
'0'
@ -176,8 +176,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
.connect(caller.signer)
.flashLoan(
_mockFlashLoanReceiver.address,
weth.address,
ethers.utils.parseEther('0.8'),
[weth.address],
[ethers.utils.parseEther('0.8')],
2,
'0x10',
'0'
@ -193,22 +193,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
const callerDebt = await wethDebtToken.balanceOf(caller.address);
expect(callerDebt.toString()).to.be.equal('800720000000000000', 'Invalid user debt');
});
it('tries to take a very small flashloan, which would result in 0 fees (revert expected)', async () => {
const {pool, weth} = testEnv;
await expect(
pool.flashLoan(
_mockFlashLoanReceiver.address,
weth.address,
'1', //1 wei loan
2,
'0x10',
'0'
)
).to.be.revertedWith(REQUESTED_AMOUNT_TOO_SMALL);
expect(callerDebt.toString()).to.be.equal('800000000000000000', 'Invalid user debt');
});
it('tries to take a flashloan that is bigger than the available liquidity (revert expected)', async () => {
@ -217,8 +202,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await expect(
pool.flashLoan(
_mockFlashLoanReceiver.address,
weth.address,
'1004415000000000000', //slightly higher than the available liquidity
[weth.address],
['1004415000000000000'], //slightly higher than the available liquidity
2,
'0x10',
'0'
@ -231,7 +216,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
const {pool, deployer, weth} = testEnv;
await expect(
pool.flashLoan(deployer.address, weth.address, '1000000000000000000', 2, '0x10', '0')
pool.flashLoan(deployer.address, [weth.address], ['1000000000000000000'], 2, '0x10', '0')
).to.be.reverted;
});
@ -257,8 +242,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await pool.flashLoan(
_mockFlashLoanReceiver.address,
usdc.address,
flashloanAmount,
[usdc.address],
[flashloanAmount],
0,
'0x10',
'0'
@ -297,7 +282,14 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await expect(
pool
.connect(caller.signer)
.flashLoan(_mockFlashLoanReceiver.address, usdc.address, flashloanAmount, 2, '0x10', '0')
.flashLoan(
_mockFlashLoanReceiver.address,
[usdc.address],
[flashloanAmount],
2,
'0x10',
'0'
)
).to.be.revertedWith(COLLATERAL_BALANCE_IS_0);
});
@ -320,7 +312,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await pool
.connect(caller.signer)
.flashLoan(_mockFlashLoanReceiver.address, usdc.address, flashloanAmount, 2, '0x10', '0');
.flashLoan(_mockFlashLoanReceiver.address, [usdc.address], [flashloanAmount], 2, '0x10', '0');
const {variableDebtTokenAddress} = await helpersContract.getReserveTokensAddresses(
usdc.address
);
@ -332,7 +324,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
const callerDebt = await usdcDebtToken.balanceOf(caller.address);
expect(callerDebt.toString()).to.be.equal('500450000', 'Invalid user debt');
expect(callerDebt.toString()).to.be.equal('500000000', 'Invalid user debt');
});
it('Caller deposits 1000 DAI as collateral, Takes a WETH flashloan with mode = 0, does not approve the transfer of the funds', async () => {
@ -355,8 +347,8 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await expect(
pool
.connect(caller.signer)
.flashLoan(_mockFlashLoanReceiver.address, weth.address, flashAmount, 0, '0x10', '0')
).to.be.revertedWith('ERC20: transfer amount exceeds allowance');
.flashLoan(_mockFlashLoanReceiver.address, [weth.address], [flashAmount], 0, '0x10', '0')
).to.be.revertedWith(SAFEERC20_LOWLEVEL_CALL);
});
it('Caller takes a WETH flashloan with mode = 1', async () => {
@ -370,7 +362,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
await pool
.connect(caller.signer)
.flashLoan(_mockFlashLoanReceiver.address, weth.address, flashAmount, 1, '0x10', '0');
.flashLoan(_mockFlashLoanReceiver.address, [weth.address], [flashAmount], 1, '0x10', '0');
const {stableDebtTokenAddress} = await helpersContract.getReserveTokensAddresses(weth.address);
@ -381,6 +373,6 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => {
const callerDebt = await wethDebtToken.balanceOf(caller.address);
expect(callerDebt.toString()).to.be.equal('800720000000000000', 'Invalid user debt');
expect(callerDebt.toString()).to.be.equal('800000000000000000', 'Invalid user debt');
});
});

View File

@ -9,7 +9,6 @@ import {
getMintableErc20,
getLendingPoolConfiguratorProxy,
getPriceOracle,
getMockSwapAdapter,
getLendingPoolAddressesProviderRegistry,
} from '../../helpers/contracts-helpers';
import {tEthereumAddress} from '../../helpers/types';
@ -25,7 +24,6 @@ import bignumberChai from 'chai-bignumber';
import {almostEqual} from './almost-equal';
import {PriceOracle} from '../../types/PriceOracle';
import {LendingPoolAddressesProvider} from '../../types/LendingPoolAddressesProvider';
import {MockSwapAdapter} from '../../types/MockSwapAdapter';
import {LendingPoolAddressesProviderRegistry} from '../../types/LendingPoolAddressesProviderRegistry';
chai.use(bignumberChai());
chai.use(almostEqual());
@ -48,7 +46,6 @@ export interface TestEnv {
usdc: MintableERC20;
lend: MintableERC20;
addressesProvider: LendingPoolAddressesProvider;
mockSwapAdapter: MockSwapAdapter;
registry: LendingPoolAddressesProviderRegistry;
}
@ -73,7 +70,6 @@ const testEnv: TestEnv = {
usdc: {} as MintableERC20,
lend: {} as MintableERC20,
addressesProvider: {} as LendingPoolAddressesProvider,
mockSwapAdapter: {} as MockSwapAdapter,
registry: {} as LendingPoolAddressesProviderRegistry,
} as TestEnv;
@ -134,8 +130,6 @@ export async function initializeMakeSuite() {
testEnv.usdc = await getMintableErc20(usdcAddress);
testEnv.lend = await getMintableErc20(lendAddress);
testEnv.weth = await getMintableErc20(wethAddress);
testEnv.mockSwapAdapter = await getMockSwapAdapter();
}
export function makeSuite(name: string, tests: (testEnv: TestEnv) => void) {

View File

@ -342,6 +342,20 @@
"expected": "success"
}
]
},
{
"description": "Users 1 tries to withdraw 0.05 WETH, which does not bring the HF below 1",
"actions": [
{
"name": "withdraw",
"args": {
"reserve": "WETH",
"amount": "0.05",
"user": "1"
},
"expected": "success"
}
]
}
]
}

View File

@ -1153,11 +1153,12 @@ const calcLinearInterest = (
currentTimestamp: BigNumber,
lastUpdateTimestamp: BigNumber
) => {
const timeDifference = currentTimestamp.minus(lastUpdateTimestamp).wadToRay();
const timeDifference = currentTimestamp.minus(lastUpdateTimestamp);
const timeDelta = timeDifference.rayDiv(new BigNumber(ONE_YEAR).wadToRay());
const cumulatedInterest = rate.rayMul(timeDelta).plus(RAY);
const cumulatedInterest = rate
.multipliedBy(timeDifference)
.dividedBy(new BigNumber(ONE_YEAR))
.plus(RAY);
return cumulatedInterest;
};

View File

@ -155,22 +155,6 @@ makeSuite('Pausable Pool', (testEnv: TestEnv) => {
await configurator.setPoolPause(false);
});
it('Swap liquidity', async () => {
const {pool, dai, weth, users, configurator} = testEnv;
const user = users[1];
// Pause the pool
await configurator.setPoolPause(true);
// Try to execute liquidation
await expect(
pool.connect(user.signer).swapLiquidity(user.address, dai.address, weth.address, '1', '0x')
).revertedWith(IS_PAUSED);
// Unpause the pool
await configurator.setPoolPause(false);
});
it('Repay', async () => {
const {pool, dai, users, configurator} = testEnv;
@ -187,32 +171,6 @@ makeSuite('Pausable Pool', (testEnv: TestEnv) => {
await configurator.setPoolPause(false);
});
it('Repay with collateral', async () => {
const {pool, weth, dai, usdc, users, mockSwapAdapter, oracle, configurator} = testEnv;
const user = users[6];
const liquidator = users[5];
// Pause the pool
await configurator.setPoolPause(true);
// Try to execute liquidation
await expect(
pool
.connect(liquidator.signer)
.repayWithCollateral(
weth.address,
usdc.address,
user.address,
'1',
mockSwapAdapter.address,
'0x'
)
).revertedWith(IS_PAUSED);
// Unpause the pool
await configurator.setPoolPause(false);
});
it('Flash loan', async () => {
const {dai, pool, weth, users, configurator} = testEnv;
@ -228,7 +186,7 @@ makeSuite('Pausable Pool', (testEnv: TestEnv) => {
await expect(
pool
.connect(caller.signer)
.flashLoan(_mockFlashLoanReceiver.address, weth.address, flashAmount, 1, '0x10', '0')
.flashLoan(_mockFlashLoanReceiver.address, [weth.address], [flashAmount], 1, '0x10', '0')
).revertedWith(IS_PAUSED);
// Unpause pool
@ -317,7 +275,7 @@ makeSuite('Pausable Pool', (testEnv: TestEnv) => {
});
it('SwapBorrowRateMode', async () => {
const {pool, weth, dai, usdc, users, configurator, mockSwapAdapter} = testEnv;
const {pool, weth, dai, usdc, users, configurator} = testEnv;
const user = users[1];
const amountWETHToDeposit = parseEther('10');
const amountDAIToDeposit = parseEther('120');

View File

@ -1,682 +0,0 @@
import {TestEnv, makeSuite} from './helpers/make-suite';
import {APPROVAL_AMOUNT_LENDING_POOL} from '../helpers/constants';
import {ethers} from 'ethers';
import BigNumber from 'bignumber.js';
import {
calcExpectedVariableDebtTokenBalance,
calcExpectedStableDebtTokenBalance,
} from './helpers/utils/calculations';
import {getContractsData} from './helpers/actions';
import {timeLatest, waitForTx} from '../helpers/misc-utils';
import {ProtocolErrors, tEthereumAddress} from '../helpers/types';
const {expect} = require('chai');
const {parseUnits, parseEther} = ethers.utils;
export const expectRepayWithCollateralEvent = (
events: ethers.Event[],
pool: tEthereumAddress,
collateral: tEthereumAddress,
borrowing: tEthereumAddress,
user: tEthereumAddress
) => {
if (!events || events.length < 16) {
expect(false, 'INVALID_EVENTS_LENGTH_ON_REPAY_COLLATERAL');
}
const repayWithCollateralEvent = events[15];
expect(repayWithCollateralEvent.address).to.be.equal(pool);
expect(`0x${repayWithCollateralEvent.topics[1].slice(26)}`.toLowerCase()).to.be.equal(
collateral.toLowerCase()
);
expect(`0x${repayWithCollateralEvent.topics[2].slice(26)}`).to.be.equal(borrowing.toLowerCase());
expect(`0x${repayWithCollateralEvent.topics[3].slice(26)}`.toLowerCase()).to.be.equal(
user.toLowerCase()
);
};
makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
const {IS_PAUSED} = ProtocolErrors;
it("It's not possible to repayWithCollateral() on a non-active collateral or a non active principal", async () => {
const {configurator, weth, pool, users, dai, mockSwapAdapter} = testEnv;
const user = users[1];
await configurator.deactivateReserve(weth.address);
await expect(
pool
.connect(user.signer)
.repayWithCollateral(
weth.address,
dai.address,
user.address,
parseEther('100'),
mockSwapAdapter.address,
'0x'
)
).to.be.revertedWith('2');
await configurator.activateReserve(weth.address);
await configurator.deactivateReserve(dai.address);
await expect(
pool
.connect(user.signer)
.repayWithCollateral(
weth.address,
dai.address,
user.address,
parseEther('100'),
mockSwapAdapter.address,
'0x'
)
).to.be.revertedWith('2');
await configurator.activateReserve(dai.address);
});
it('User 1 provides some liquidity for others to borrow', async () => {
const {pool, weth, dai, usdc, deployer} = testEnv;
await weth.mint(parseEther('200'));
await weth.approve(pool.address, parseEther('200'));
await pool.deposit(weth.address, parseEther('200'), deployer.address, 0);
await dai.mint(parseEther('20000'));
await dai.approve(pool.address, parseEther('20000'));
await pool.deposit(dai.address, parseEther('20000'), deployer.address, 0);
await usdc.mint(parseEther('20000'));
await usdc.approve(pool.address, parseEther('20000'));
await pool.deposit(usdc.address, parseEther('20000'), deployer.address, 0);
});
it('User 2 deposit WETH and borrows DAI at Variable', async () => {
const {pool, weth, dai, users} = testEnv;
const user = users[1];
const amountToDeposit = ethers.utils.parseEther('1');
const amountToBorrow = ethers.utils.parseEther('20');
await weth.connect(user.signer).mint(amountToDeposit);
await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
await pool.connect(user.signer).borrow(dai.address, amountToBorrow, 2, 0, user.address);
});
it('It is not possible to do reentrancy on repayWithCollateral()', async () => {
const {pool, weth, dai, users, mockSwapAdapter, oracle} = testEnv;
const user = users[1];
const amountToRepay = parseEther('10');
await waitForTx(await mockSwapAdapter.setTryReentrancy(true));
await mockSwapAdapter.setAmountToReturn(amountToRepay);
await expect(
pool
.connect(user.signer)
.repayWithCollateral(
weth.address,
dai.address,
user.address,
amountToRepay,
mockSwapAdapter.address,
'0x'
)
).to.be.revertedWith('53');
});
it('User 2 tries to repay his DAI Variable loan using his WETH collateral. First half the amount, after that, the rest', async () => {
const {pool, weth, dai, users, mockSwapAdapter, oracle, helpersContract} = testEnv;
const user = users[1];
const amountToRepay = parseEther('10');
await waitForTx(await mockSwapAdapter.setTryReentrancy(false));
const {userData: wethUserDataBefore} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {reserveData: daiReserveDataBefore, userData: daiUserDataBefore} = await getContractsData(
dai.address,
user.address,
testEnv
);
await mockSwapAdapter.setAmountToReturn(amountToRepay);
await waitForTx(
await pool
.connect(user.signer)
.repayWithCollateral(
weth.address,
dai.address,
user.address,
amountToRepay,
mockSwapAdapter.address,
'0x'
)
);
const repayWithCollateralTimestamp = await timeLatest();
const {userData: wethUserDataAfter} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {userData: daiUserDataAfter} = await getContractsData(dai.address, user.address, testEnv);
const collateralPrice = await oracle.getAssetPrice(weth.address);
const principalPrice = await oracle.getAssetPrice(dai.address);
const collateralDecimals = (
await helpersContract.getReserveConfigurationData(weth.address)
).decimals.toString();
const principalDecimals = (
await helpersContract.getReserveConfigurationData(dai.address)
).decimals.toString();
const expectedCollateralLiquidated = new BigNumber(principalPrice.toString())
.times(new BigNumber(amountToRepay.toString()).times(105))
.times(new BigNumber(10).pow(collateralDecimals))
.div(
new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals))
)
.div(100)
.decimalPlaces(0, BigNumber.ROUND_DOWN);
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
daiReserveDataBefore,
daiUserDataBefore,
new BigNumber(repayWithCollateralTimestamp)
).minus(daiUserDataBefore.currentVariableDebt);
expect(daiUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual(
new BigNumber(daiUserDataBefore.currentVariableDebt)
.minus(amountToRepay.toString())
.plus(expectedVariableDebtIncrease)
.toString()
);
expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(
new BigNumber(wethUserDataBefore.currentATokenBalance).minus(
expectedCollateralLiquidated.toString()
)
);
expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.true;
});
it('User 3 deposits WETH and borrows USDC at Variable', async () => {
const {pool, weth, usdc, users} = testEnv;
const user = users[2];
const amountToDeposit = parseEther('10');
const amountToBorrow = parseUnits('40', 6);
await weth.connect(user.signer).mint(amountToDeposit);
await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0, user.address);
});
it('User 3 repays completely his USDC loan by swapping his WETH collateral', async () => {
const {pool, weth, usdc, users, mockSwapAdapter, oracle, helpersContract} = testEnv;
const user = users[2];
const amountToRepay = parseUnits('10', 6);
const {userData: wethUserDataBefore} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {
reserveData: usdcReserveDataBefore,
userData: usdcUserDataBefore,
} = await getContractsData(usdc.address, user.address, testEnv);
await mockSwapAdapter.setAmountToReturn(amountToRepay);
await waitForTx(
await pool
.connect(user.signer)
.repayWithCollateral(
weth.address,
usdc.address,
user.address,
amountToRepay,
mockSwapAdapter.address,
'0x'
)
);
const repayWithCollateralTimestamp = await timeLatest();
const {userData: wethUserDataAfter} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {userData: usdcUserDataAfter} = await getContractsData(
usdc.address,
user.address,
testEnv
);
const collateralPrice = await oracle.getAssetPrice(weth.address);
const principalPrice = await oracle.getAssetPrice(usdc.address);
const collateralDecimals = (
await helpersContract.getReserveConfigurationData(weth.address)
).decimals.toString();
const principalDecimals = (
await helpersContract.getReserveConfigurationData(usdc.address)
).decimals.toString();
const expectedCollateralLiquidated = new BigNumber(principalPrice.toString())
.times(new BigNumber(amountToRepay.toString()).times(105))
.times(new BigNumber(10).pow(collateralDecimals))
.div(
new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals))
)
.div(100)
.decimalPlaces(0, BigNumber.ROUND_DOWN);
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
usdcReserveDataBefore,
usdcUserDataBefore,
new BigNumber(repayWithCollateralTimestamp)
).minus(usdcUserDataBefore.currentVariableDebt);
expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual(
new BigNumber(usdcUserDataBefore.currentVariableDebt)
.minus(amountToRepay.toString())
.plus(expectedVariableDebtIncrease)
.toString(),
'INVALID_DEBT_POSITION'
);
expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(
new BigNumber(wethUserDataBefore.currentATokenBalance).minus(
expectedCollateralLiquidated.toString()
),
'INVALID_COLLATERAL_POSITION'
);
expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.true;
});
it('Revert expected. User 3 tries to repay with his collateral a currency he havent borrow', async () => {
const {pool, weth, dai, users, mockSwapAdapter} = testEnv;
const user = users[2];
const amountToRepay = parseUnits('10', 6);
await expect(
pool
.connect(user.signer)
.repayWithCollateral(
weth.address,
dai.address,
user.address,
amountToRepay,
mockSwapAdapter.address,
'0x'
)
).to.be.revertedWith('40');
});
it('User 3 tries to repay with his collateral all his variable debt and part of the stable', async () => {
const {pool, weth, usdc, users, mockSwapAdapter, oracle, helpersContract} = testEnv;
const user = users[2];
const amountToDeposit = parseEther('20');
const amountToBorrowStable = parseUnits('40', 6);
const amountToBorrowVariable = parseUnits('40', 6);
await weth.connect(user.signer).mint(amountToDeposit);
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
await pool
.connect(user.signer)
.borrow(usdc.address, amountToBorrowVariable, 2, 0, user.address);
await pool.connect(user.signer).borrow(usdc.address, amountToBorrowStable, 1, 0, user.address);
const amountToRepay = parseUnits('80', 6);
const {userData: wethUserDataBefore} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {
reserveData: usdcReserveDataBefore,
userData: usdcUserDataBefore,
} = await getContractsData(usdc.address, user.address, testEnv);
await mockSwapAdapter.setAmountToReturn(amountToRepay);
const txReceipt = await waitForTx(
await pool
.connect(user.signer)
.repayWithCollateral(
weth.address,
usdc.address,
user.address,
amountToRepay,
mockSwapAdapter.address,
'0x'
)
);
const repayWithCollateralTimestamp = await timeLatest();
const {userData: wethUserDataAfter} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {userData: usdcUserDataAfter} = await getContractsData(
usdc.address,
user.address,
testEnv
);
const collateralPrice = await oracle.getAssetPrice(weth.address);
const principalPrice = await oracle.getAssetPrice(usdc.address);
const collateralDecimals = (
await helpersContract.getReserveConfigurationData(weth.address)
).decimals.toString();
const principalDecimals = (
await helpersContract.getReserveConfigurationData(usdc.address)
).decimals.toString();
const expectedCollateralLiquidated = new BigNumber(principalPrice.toString())
.times(new BigNumber(amountToRepay.toString()).times(105))
.times(new BigNumber(10).pow(collateralDecimals))
.div(
new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals))
)
.div(100)
.decimalPlaces(0, BigNumber.ROUND_DOWN);
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
usdcReserveDataBefore,
usdcUserDataBefore,
new BigNumber(repayWithCollateralTimestamp)
).minus(usdcUserDataBefore.currentVariableDebt);
const expectedStableDebtIncrease = calcExpectedStableDebtTokenBalance(
usdcUserDataBefore.principalStableDebt,
usdcUserDataBefore.stableBorrowRate,
usdcUserDataBefore.stableRateLastUpdated,
new BigNumber(repayWithCollateralTimestamp)
).minus(usdcUserDataBefore.currentStableDebt);
expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.equal(
new BigNumber(usdcUserDataBefore.currentVariableDebt)
.minus(amountToRepay.toString())
.plus(expectedVariableDebtIncrease)
.gte(0)
? new BigNumber(usdcUserDataBefore.currentVariableDebt)
.minus(amountToRepay.toString())
.plus(expectedVariableDebtIncrease)
.toString()
: '0',
'INVALID_VARIABLE_DEBT_POSITION'
);
const stableDebtRepaid = new BigNumber(usdcUserDataBefore.currentVariableDebt)
.minus(amountToRepay.toString())
.plus(expectedVariableDebtIncrease)
.abs();
expect(usdcUserDataAfter.currentStableDebt).to.be.bignumber.equal(
new BigNumber(usdcUserDataBefore.currentStableDebt)
.minus(stableDebtRepaid)
.plus(expectedStableDebtIncrease)
.gte(0)
? new BigNumber(usdcUserDataBefore.currentStableDebt)
.minus(stableDebtRepaid)
.plus(expectedStableDebtIncrease)
.toString()
: '0',
'INVALID_STABLE_DEBT_POSITION'
);
expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(
new BigNumber(wethUserDataBefore.currentATokenBalance).minus(
expectedCollateralLiquidated.toString()
),
'INVALID_COLLATERAL_POSITION'
);
const eventsEmitted = txReceipt.events || [];
expectRepayWithCollateralEvent(
eventsEmitted,
pool.address,
weth.address,
usdc.address,
user.address
);
expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.true;
});
it('User 4 tries to repay a bigger amount that what can be swapped of a particular collateral, repaying only the maximum allowed by that collateral', async () => {
const {pool, weth, dai, users, mockSwapAdapter, oracle, helpersContract} = testEnv;
const user = users[3];
const amountToDepositWeth = parseEther('0.1');
const amountToDepositDAI = parseEther('500');
const amountToBorrowVariable = parseEther('80');
await weth.connect(user.signer).mint(amountToDepositWeth);
await dai.connect(user.signer).mint(amountToDepositDAI);
await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await dai.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await pool.connect(user.signer).deposit(weth.address, amountToDepositWeth, user.address, '0');
await pool.connect(user.signer).deposit(dai.address, amountToDepositDAI, user.address, '0');
await pool.connect(user.signer).borrow(dai.address, amountToBorrowVariable, 2, 0, user.address);
const amountToRepay = parseEther('80');
const {userData: wethUserDataBefore} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {reserveData: daiReserveDataBefore, userData: daiUserDataBefore} = await getContractsData(
dai.address,
user.address,
testEnv
);
await mockSwapAdapter.setAmountToReturn(amountToRepay);
await waitForTx(
await pool
.connect(user.signer)
.repayWithCollateral(
weth.address,
dai.address,
user.address,
amountToRepay,
mockSwapAdapter.address,
'0x'
)
);
const repayWithCollateralTimestamp = await timeLatest();
const {userData: wethUserDataAfter} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {userData: daiUserDataAfter} = await getContractsData(dai.address, user.address, testEnv);
const collateralPrice = await oracle.getAssetPrice(weth.address);
const principalPrice = await oracle.getAssetPrice(dai.address);
const collateralConfig = await helpersContract.getReserveConfigurationData(weth.address);
const collateralDecimals = collateralConfig.decimals.toString();
const collateralLiquidationBonus = collateralConfig.liquidationBonus.toString();
const principalDecimals = (
await helpersContract.getReserveConfigurationData(dai.address)
).decimals.toString();
const expectedDebtCovered = new BigNumber(collateralPrice.toString())
.times(new BigNumber(wethUserDataBefore.currentATokenBalance.toString()))
.times(new BigNumber(10).pow(principalDecimals))
.div(
new BigNumber(principalPrice.toString()).times(new BigNumber(10).pow(collateralDecimals))
)
.div(new BigNumber(collateralLiquidationBonus).div(10000).toString())
.decimalPlaces(0, BigNumber.ROUND_DOWN);
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
daiReserveDataBefore,
daiUserDataBefore,
new BigNumber(repayWithCollateralTimestamp)
).minus(daiUserDataBefore.currentVariableDebt);
expect(daiUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual(
new BigNumber(daiUserDataBefore.currentVariableDebt)
.minus(expectedDebtCovered.toString())
.plus(expectedVariableDebtIncrease),
'INVALID_VARIABLE_DEBT_POSITION'
);
expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.almostEqual(0);
expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.false;
});
it('User 5 deposits WETH and DAI, then borrows USDC at Variable, then disables WETH as collateral', async () => {
const {pool, weth, dai, usdc, users} = testEnv;
const user = users[4];
const amountWETHToDeposit = parseEther('10');
const amountDAIToDeposit = parseEther('120');
const amountToBorrow = parseUnits('65', 6);
await weth.connect(user.signer).mint(amountWETHToDeposit);
await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await pool.connect(user.signer).deposit(weth.address, amountWETHToDeposit, user.address, '0');
await dai.connect(user.signer).mint(amountDAIToDeposit);
await dai.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await pool.connect(user.signer).deposit(dai.address, amountDAIToDeposit, user.address, '0');
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0, user.address);
});
it('User 5 tries to repay his USDC loan by swapping his WETH collateral, should not revert even with WETH collateral disabled', async () => {
const {pool, weth, usdc, users, mockSwapAdapter, oracle, helpersContract} = testEnv;
const user = users[4];
const amountToRepay = parseUnits('65', 6);
// Disable WETH as collateral
await pool.connect(user.signer).setUserUseReserveAsCollateral(weth.address, false);
const {userData: wethUserDataBefore} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {
reserveData: usdcReserveDataBefore,
userData: usdcUserDataBefore,
} = await getContractsData(usdc.address, user.address, testEnv);
expect(wethUserDataBefore.usageAsCollateralEnabled).to.be.false;
// User 5 should be able to liquidate himself with WETH, even if is disabled
await mockSwapAdapter.setAmountToReturn(amountToRepay);
expect(
await pool
.connect(user.signer)
.repayWithCollateral(
weth.address,
usdc.address,
user.address,
amountToRepay,
mockSwapAdapter.address,
'0x'
)
);
const repayWithCollateralTimestamp = await timeLatest();
const {userData: wethUserDataAfter} = await getContractsData(
weth.address,
user.address,
testEnv
);
const {userData: usdcUserDataAfter} = await getContractsData(
usdc.address,
user.address,
testEnv
);
const collateralPrice = await oracle.getAssetPrice(weth.address);
const principalPrice = await oracle.getAssetPrice(usdc.address);
const collateralDecimals = (
await helpersContract.getReserveConfigurationData(weth.address)
).decimals.toString();
const principalDecimals = (
await helpersContract.getReserveConfigurationData(usdc.address)
).decimals.toString();
const expectedCollateralLiquidated = new BigNumber(principalPrice.toString())
.times(new BigNumber(amountToRepay.toString()).times(105))
.times(new BigNumber(10).pow(collateralDecimals))
.div(
new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals))
)
.div(100)
.decimalPlaces(0, BigNumber.ROUND_DOWN);
const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance(
usdcReserveDataBefore,
usdcUserDataBefore,
new BigNumber(repayWithCollateralTimestamp)
).minus(usdcUserDataBefore.currentVariableDebt);
expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual(
new BigNumber(usdcUserDataBefore.currentVariableDebt)
.minus(amountToRepay.toString())
.plus(expectedVariableDebtIncrease)
.toString(),
'INVALID_DEBT_POSITION'
);
expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal(
new BigNumber(wethUserDataBefore.currentATokenBalance).minus(
expectedCollateralLiquidated.toString()
),
'INVALID_COLLATERAL_POSITION'
);
expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.false;
});
});

View File

@ -0,0 +1,32 @@
import {configuration as actionsConfiguration} from './helpers/actions';
import {configuration as calculationsConfiguration} from './helpers/utils/calculations';
import BigNumber from 'bignumber.js';
import {makeSuite} from './helpers/make-suite';
import {getReservesConfigByPool} from '../helpers/configuration';
import {AavePools, iAavePoolAssets, IReserveParams} from '../helpers/types';
import {executeStory} from './helpers/scenario-engine';
makeSuite('Subgraph scenario tests', async (testEnv) => {
let story: any;
let scenario;
before('Initializing configuration', async () => {
const scenario = require(`./helpers/scenarios/borrow-repay-stable`);
story = scenario.stories[0];
// Sets BigNumber for this suite, instead of globally
BigNumber.config({DECIMAL_PLACES: 0, ROUNDING_MODE: BigNumber.ROUND_DOWN});
actionsConfiguration.skipIntegrityCheck = false; //set this to true to execute solidity-coverage
calculationsConfiguration.reservesParams = <iAavePoolAssets<IReserveParams>>(
getReservesConfigByPool(AavePools.proto)
);
});
after('Reset', () => {
// Reset BigNumber
BigNumber.config({DECIMAL_PLACES: 20, ROUNDING_MODE: BigNumber.ROUND_HALF_UP});
});
it('deposit-borrow', async () => {
await executeStory(story, testEnv);
});
});