Fix conflicts

This commit is contained in:
David Racero 2020-09-14 19:59:00 +02:00
commit 59bfdc39f2
23 changed files with 849 additions and 56 deletions

View File

@ -2,6 +2,7 @@ import {usePlugin, BuidlerConfig} from '@nomiclabs/buidler/config';
// @ts-ignore
import {accounts} from './test-wallets.js';
import {eEthereumNetwork} from './helpers/types';
import {BUIDLEREVM_CHAINID, COVERAGE_CHAINID} from './helpers/constants';
usePlugin('@nomiclabs/buidler-ethers');
usePlugin('buidler-typechain');
@ -59,6 +60,7 @@ const config: any = {
networks: {
coverage: {
url: 'http://localhost:8555',
chainId: COVERAGE_CHAINID,
},
kovan: getCommonNetworkConfig(eEthereumNetwork.kovan, 42),
ropsten: getCommonNetworkConfig(eEthereumNetwork.ropsten, 3),
@ -68,7 +70,7 @@ const config: any = {
blockGasLimit: DEFAULT_BLOCK_GAS_LIMIT,
gas: DEFAULT_BLOCK_GAS_LIMIT,
gasPrice: 8000000000,
chainId: 31337,
chainId: BUIDLEREVM_CHAINID,
throwOnTransactionFailures: true,
throwOnCallFailures: true,
accounts: accounts.map(({secretKey, balance}: {secretKey: string; balance: string}) => ({

View File

@ -29,6 +29,13 @@ interface ILendingPool {
**/
event Withdraw(address indexed reserve, address indexed user, uint256 amount);
event BorrowAllowanceDelegated(
address indexed asset,
address indexed fromUser,
address indexed toUser,
uint256 interestRateMode,
uint256 amount
);
/**
* @dev emitted on borrow
* @param reserve the address of the reserve
@ -40,7 +47,8 @@ interface ILendingPool {
**/
event Borrow(
address indexed reserve,
address indexed user,
address user,
address indexed onBehalfOf,
uint256 amount,
uint256 borrowRateMode,
uint256 borrowRate,
@ -160,6 +168,27 @@ interface ILendingPool {
**/
function withdraw(address reserve, uint256 amount) external;
/**
* @dev Sets allowance to borrow on a certain type of debt asset for a certain user address
* @param asset The underlying asset of the debt token
* @param user The user to give allowance to
* @param interestRateMode Type of debt: 1 for stable, 2 for variable
* @param amount Allowance amount to borrow
**/
function delegateBorrowAllowance(
address asset,
address user,
uint256 interestRateMode,
uint256 amount
) external;
function getBorrowAllowance(
address fromUser,
address toUser,
address asset,
uint256 interestRateMode
) external view returns (uint256);
/**
* @dev Allows users to borrow a specific amount of the reserve currency, provided that the borrower
* already deposited enough collateral.
@ -171,7 +200,8 @@ interface ILendingPool {
address reserve,
uint256 amount,
uint256 interestRateMode,
uint16 referralCode
uint16 referralCode,
address onBehalfOf
) external;
/**

View File

@ -49,6 +49,8 @@ contract LendingPool is VersionedInitializable, PausablePool, ILendingPool {
mapping(address => ReserveLogic.ReserveData) internal _reserves;
mapping(address => UserConfiguration.Map) internal _usersConfig;
// debt token address => user who gives allowance => user who receives allowance => amount
mapping(address => mapping(address => mapping(address => uint256))) internal _borrowAllowance;
address[] internal _reservesList;
@ -160,6 +162,35 @@ contract LendingPool is VersionedInitializable, PausablePool, ILendingPool {
emit Withdraw(asset, msg.sender, amount);
}
function getBorrowAllowance(
address fromUser,
address toUser,
address asset,
uint256 interestRateMode
) external override view returns (uint256) {
return
_borrowAllowance[_reserves[asset].getDebtTokenAddress(interestRateMode)][fromUser][toUser];
}
/**
* @dev Sets allowance to borrow on a certain type of debt asset for a certain user address
* @param asset The underlying asset of the debt token
* @param user The user to give allowance to
* @param interestRateMode Type of debt: 1 for stable, 2 for variable
* @param amount Allowance amount to borrow
**/
function delegateBorrowAllowance(
address asset,
address user,
uint256 interestRateMode,
uint256 amount
) external override {
address debtToken = _reserves[asset].getDebtTokenAddress(interestRateMode);
_borrowAllowance[debtToken][msg.sender][user] = amount;
emit BorrowAllowanceDelegated(asset, msg.sender, user, interestRateMode, amount);
}
/**
* @dev Allows users to borrow a specific amount of the reserve currency, provided that the borrower
* already deposited enough collateral.
@ -167,20 +198,34 @@ contract LendingPool is VersionedInitializable, PausablePool, ILendingPool {
* @param amount the amount to be borrowed
* @param interestRateMode the interest rate mode at which the user wants to borrow. Can be 0 (STABLE) or 1 (VARIABLE)
* @param referralCode a referral code for integrators
* @param onBehalfOf address of the user who will receive the debt
**/
function borrow(
address asset,
uint256 amount,
uint256 interestRateMode,
uint16 referralCode
uint16 referralCode,
address onBehalfOf
) external override whenNotPaused {
ReserveLogic.ReserveData storage reserve = _reserves[asset];
if (onBehalfOf != msg.sender) {
address debtToken = reserve.getDebtTokenAddress(interestRateMode);
_borrowAllowance[debtToken][onBehalfOf][msg
.sender] = _borrowAllowance[debtToken][onBehalfOf][msg.sender].sub(
amount,
Errors.BORROW_ALLOWANCE_ARE_NOT_ENOUGH
);
}
_executeBorrow(
ExecuteBorrowParams(
asset,
msg.sender,
onBehalfOf,
amount,
interestRateMode,
_reserves[asset].aTokenAddress,
reserve.aTokenAddress,
referralCode,
true
)
@ -528,6 +573,7 @@ contract LendingPool is VersionedInitializable, PausablePool, ILendingPool {
ExecuteBorrowParams(
asset,
msg.sender,
msg.sender,
vars.amountPlusPremium.sub(vars.availableBalance),
mode,
vars.aTokenAddress,
@ -757,6 +803,7 @@ contract LendingPool is VersionedInitializable, PausablePool, ILendingPool {
struct ExecuteBorrowParams {
address asset;
address user;
address onBehalfOf;
uint256 amount;
uint256 interestRateMode;
address aTokenAddress;
@ -770,7 +817,7 @@ contract LendingPool is VersionedInitializable, PausablePool, ILendingPool {
**/
function _executeBorrow(ExecuteBorrowParams memory vars) internal {
ReserveLogic.ReserveData storage reserve = _reserves[vars.asset];
UserConfiguration.Map storage userConfig = _usersConfig[msg.sender];
UserConfiguration.Map storage userConfig = _usersConfig[vars.onBehalfOf];
address oracle = _addressesProvider.getPriceOracle();
@ -780,7 +827,7 @@ contract LendingPool is VersionedInitializable, PausablePool, ILendingPool {
ValidationLogic.validateBorrow(
reserve,
vars.asset,
vars.onBehalfOf,
vars.amount,
amountInETH,
vars.interestRateMode,
@ -807,12 +854,12 @@ contract LendingPool is VersionedInitializable, PausablePool, ILendingPool {
currentStableRate = reserve.currentStableBorrowRate;
IStableDebtToken(reserve.stableDebtTokenAddress).mint(
vars.user,
vars.onBehalfOf,
vars.amount,
currentStableRate
);
} else {
IVariableDebtToken(reserve.variableDebtTokenAddress).mint(vars.user, vars.amount);
IVariableDebtToken(reserve.variableDebtTokenAddress).mint(vars.onBehalfOf, vars.amount);
}
reserve.updateInterestRates(
@ -823,12 +870,13 @@ contract LendingPool is VersionedInitializable, PausablePool, ILendingPool {
);
if (vars.releaseUnderlying) {
IAToken(vars.aTokenAddress).transferUnderlyingTo(msg.sender, vars.amount);
IAToken(vars.aTokenAddress).transferUnderlyingTo(vars.user, vars.amount);
}
emit Borrow(
vars.asset,
msg.sender,
vars.user,
vars.onBehalfOf,
vars.amount,
vars.interestRateMode,
ReserveLogic.InterestRateMode(vars.interestRateMode) == ReserveLogic.InterestRateMode.STABLE

View File

@ -45,6 +45,7 @@ contract LendingPoolLiquidationManager is VersionedInitializable, Pausable {
mapping(address => ReserveLogic.ReserveData) internal reserves;
mapping(address => UserConfiguration.Map) internal usersConfig;
mapping(address => mapping(address => mapping(address => uint256))) internal _borrowAllowance;
address[] internal reservesList;

View File

@ -38,6 +38,7 @@ library Errors {
string public constant INCONSISTENT_PROTOCOL_ACTUAL_BALANCE = '26'; // 'The actual balance of the protocol is inconsistent'
string public constant CALLER_NOT_LENDING_POOL_CONFIGURATOR = '27'; // 'The actual balance of the protocol is inconsistent'
string public constant INVALID_FLASHLOAN_MODE = '43'; //Invalid flashloan mode selected
string public constant BORROW_ALLOWANCE_ARE_NOT_ENOUGH = '54'; // User borrows on behalf, but allowance are too small
string public constant REENTRANCY_NOT_ALLOWED = '52';
string public constant FAILED_REPAY_WITH_COLLATERAL = '53';

View File

@ -62,14 +62,11 @@ library ReserveLogic {
//the current stable borrow rate. Expressed in ray
uint128 currentStableBorrowRate;
uint40 lastUpdateTimestamp;
//tokens addresses
address aTokenAddress;
address stableDebtTokenAddress;
address variableDebtTokenAddress;
address interestRateStrategyAddress;
//the id of the reserve. Represents the position in the list of the active reserves
uint8 id;
}
@ -120,6 +117,28 @@ library ReserveLogic {
return cumulated;
}
/**
* @dev returns an address of the debt token used for particular interest rate mode on asset.
* @param reserve the reserve object
* @param interestRateMode - STABLE or VARIABLE from ReserveLogic.InterestRateMode enum
* @return an address of the corresponding debt token from reserve configuration
**/
function getDebtTokenAddress(ReserveLogic.ReserveData storage reserve, uint256 interestRateMode)
internal
view
returns (address)
{
require(
ReserveLogic.InterestRateMode.STABLE == ReserveLogic.InterestRateMode(interestRateMode) ||
ReserveLogic.InterestRateMode.VARIABLE == ReserveLogic.InterestRateMode(interestRateMode),
Errors.INVALID_INTEREST_RATE_MODE_SELECTED
);
return
ReserveLogic.InterestRateMode.STABLE == ReserveLogic.InterestRateMode(interestRateMode)
? reserve.stableDebtTokenAddress
: reserve.variableDebtTokenAddress;
}
/**
* @dev Updates the liquidity cumulative index Ci and variable borrow cumulative index Bvc. Refer to the whitepaper for
* a formal specification.

View File

@ -100,7 +100,7 @@ library ValidationLogic {
/**
* @dev validates a borrow.
* @param reserve the reserve state from which the user is borrowing
* @param reserveAddress the address of the reserve
* @param userAddress the address of the user
* @param amount the amount to be borrowed
* @param amountInETH the amount to be borrowed, in ETH
* @param interestRateMode the interest rate mode at which the user is borrowing
@ -113,7 +113,7 @@ library ValidationLogic {
function validateBorrow(
ReserveLogic.ReserveData storage reserve,
address reserveAddress,
address userAddress,
uint256 amount,
uint256 amountInETH,
uint256 interestRateMode,
@ -151,7 +151,7 @@ library ValidationLogic {
vars.currentLiquidationThreshold,
vars.healthFactor
) = GenericLogic.calculateUserAccountData(
msg.sender,
userAddress,
reservesData,
userConfig,
reserves,
@ -192,7 +192,7 @@ library ValidationLogic {
require(
!userConfig.isUsingAsCollateral(reserve.id) ||
reserve.configuration.getLtv() == 0 ||
amount > IERC20(reserve.aTokenAddress).balanceOf(msg.sender),
amount > IERC20(reserve.aTokenAddress).balanceOf(userAddress),
Errors.CALLATERAL_SAME_AS_BORROWING_CURRENCY
);

View File

@ -1,5 +1,7 @@
pragma solidity ^0.6.8;
// Comments made with // are due current max code size at LendingPool
// import {Errors} from '../libraries/helpers/Errors.sol';
/**

View File

@ -24,9 +24,22 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
uint256 public constant UINT_MAX_VALUE = uint256(-1);
address public immutable UNDERLYING_ASSET_ADDRESS;
uint256 public constant ATOKEN_REVISION = 0x1;
LendingPool public immutable POOL;
/// @dev owner => next valid nonce to submit with permit()
mapping(address => uint256) public _nonces;
uint256 public constant ATOKEN_REVISION = 0x1;
bytes32 public DOMAIN_SEPARATOR;
bytes public constant EIP712_REVISION = bytes('1');
bytes32 internal constant EIP712_DOMAIN = keccak256(
'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'
);
bytes32 public constant PERMIT_TYPEHASH = keccak256(
'Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'
);
modifier onlyLendingPool {
require(msg.sender == address(POOL), Errors.CALLER_MUST_BE_LENDING_POOL);
_;
@ -51,6 +64,23 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
string calldata tokenName,
string calldata tokenSymbol
) external virtual initializer {
uint256 chainId;
//solium-disable-next-line
assembly {
chainId := chainid()
}
DOMAIN_SEPARATOR = keccak256(
abi.encode(
EIP712_DOMAIN,
keccak256(bytes(tokenName)),
keccak256(EIP712_REVISION),
chainId,
address(this)
)
);
_setName(tokenName);
_setSymbol(tokenSymbol);
_setDecimals(underlyingAssetDecimals);
@ -180,6 +210,41 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
return amount;
}
/**
* @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md
* @param owner the owner of the funds
* @param spender the spender
* @param value the amount
* @param deadline the deadline timestamp, type(uint256).max for max deadline
* @param v signature param
* @param s signature param
* @param r signature param
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
require(owner != address(0), 'INVALID_OWNER');
//solium-disable-next-line
require(block.timestamp <= deadline, 'INVALID_EXPIRATION');
uint256 currentValidNonce = _nonces[owner];
bytes32 digest = keccak256(
abi.encodePacked(
'\x19\x01',
DOMAIN_SEPARATOR,
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline))
)
);
require(owner == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE');
_nonces[owner] = currentValidNonce.add(1);
_approve(owner, spender, value);
}
function _transfer(
address from,
address to,

View File

@ -8,12 +8,16 @@ import {
IReserveParams,
tEthereumAddress,
iBasicDistributionParams,
eEthereumNetwork,
} from './types';
import BigNumber from 'bignumber.js';
import {getParamPerPool} from './contracts-helpers';
import {getParamPerPool, getParamPerNetwork} from './contracts-helpers';
export const TEST_SNAPSHOT_ID = '0x1';
export const BUIDLEREVM_CHAINID = 31337;
export const COVERAGE_CHAINID = 1337;
// ----------------
// MATH
// ----------------
@ -531,3 +535,17 @@ export const getFeeDistributionParamsCommon = (
percentages,
};
};
export const getATokenDomainSeparatorPerNetwork = (network: eEthereumNetwork): tEthereumAddress =>
getParamPerNetwork<tEthereumAddress>(
{
[eEthereumNetwork.coverage]:
'0x95b73a72c6ecf4ccbbba5178800023260bad8e75cdccdb8e4827a2977a37c820',
[eEthereumNetwork.buidlerevm]:
'0x76cbbf8aa4b11a7c207dd79ccf8c394f59475301598c9a083f8258b4fafcfa86',
[eEthereumNetwork.kovan]: '',
[eEthereumNetwork.ropsten]: '',
[eEthereumNetwork.main]: '',
},
network
);

View File

@ -32,6 +32,8 @@ import {Ierc20Detailed} from '../types/Ierc20Detailed';
import {StableDebtToken} from '../types/StableDebtToken';
import {VariableDebtToken} from '../types/VariableDebtToken';
import {MockSwapAdapter} from '../types/MockSwapAdapter';
import {signTypedData_v4, TypedData} from 'eth-sig-util';
import {fromRpcSig, ECDSASignature} from 'ethereumjs-util';
export const registerContractInJsonDb = async (contractId: string, contractInstance: Contract) => {
const currentNetwork = BRE.network.name;
@ -431,10 +433,14 @@ const linkBytecode = (artifact: Artifact, libraries: any) => {
};
export const getParamPerNetwork = <T>(
{kovan, ropsten, main}: iParamsPerNetwork<T>,
{kovan, ropsten, main, buidlerevm, coverage}: iParamsPerNetwork<T>,
network: eEthereumNetwork
) => {
switch (network) {
case eEthereumNetwork.coverage:
return coverage;
case eEthereumNetwork.buidlerevm:
return buidlerevm;
case eEthereumNetwork.kovan:
return kovan;
case eEthereumNetwork.ropsten:
@ -471,3 +477,55 @@ export const convertToCurrencyUnits = async (tokenAddress: string, amount: strin
const amountInCurrencyUnits = new BigNumber(amount).div(currencyUnit);
return amountInCurrencyUnits.toFixed();
};
export const buildPermitParams = (
chainId: number,
token: tEthereumAddress,
revision: string,
tokenName: string,
owner: tEthereumAddress,
spender: tEthereumAddress,
nonce: number,
deadline: string,
value: tStringTokenSmallUnits
) => ({
types: {
EIP712Domain: [
{name: 'name', type: 'string'},
{name: 'version', type: 'string'},
{name: 'chainId', type: 'uint256'},
{name: 'verifyingContract', type: 'address'},
],
Permit: [
{name: 'owner', type: 'address'},
{name: 'spender', type: 'address'},
{name: 'value', type: 'uint256'},
{name: 'nonce', type: 'uint256'},
{name: 'deadline', type: 'uint256'},
],
},
primaryType: 'Permit' as const,
domain: {
name: tokenName,
version: revision,
chainId: chainId,
verifyingContract: token,
},
message: {
owner,
spender,
value,
nonce,
deadline,
},
});
export const getSignatureFromTypedData = (
privateKey: string,
typedData: any // TODO: should be TypedData, from eth-sig-utils, but TS doesn't accept it
): ECDSASignature => {
const signature = signTypedData_v4(Buffer.from(privateKey.substring(2, 66), 'hex'), {
data: typedData,
});
return fromRpcSig(signature);
};

View File

@ -5,6 +5,7 @@ export enum eEthereumNetwork {
kovan = 'kovan',
ropsten = 'ropsten',
main = 'main',
coverage = 'coverage',
}
export enum AavePools {
@ -248,6 +249,8 @@ export interface IMarketRates {
}
export interface iParamsPerNetwork<T> {
[eEthereumNetwork.coverage]: T;
[eEthereumNetwork.buidlerevm]: T;
[eEthereumNetwork.kovan]: T;
[eEthereumNetwork.ropsten]: T;
[eEthereumNetwork.main]: T;

View File

@ -19,6 +19,7 @@
"test-flash": "buidler test test/__setup.spec.ts test/flashloan.spec.ts",
"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",
"dev:coverage": "buidler coverage --network coverage",
"dev:deployment": "buidler dev-deployment",
"dev:deployExample": "buidler deploy-Example",
@ -61,7 +62,9 @@
"tslint-config-prettier": "^1.18.0",
"tslint-plugin-prettier": "^2.3.0",
"typechain": "2.0.0",
"typescript": "3.9.3"
"typescript": "3.9.3",
"eth-sig-util": "2.5.3",
"ethereumjs-util": "7.0.2"
},
"husky": {
"hooks": {

312
test/atoken-permit.spec.ts Normal file
View File

@ -0,0 +1,312 @@
import {
MAX_UINT_AMOUNT,
ZERO_ADDRESS,
getATokenDomainSeparatorPerNetwork,
BUIDLEREVM_CHAINID,
} from '../helpers/constants';
import {buildPermitParams, getSignatureFromTypedData} from '../helpers/contracts-helpers';
import {expect} from 'chai';
import {ethers} from 'ethers';
import {eEthereumNetwork} from '../helpers/types';
import {makeSuite, TestEnv} from './helpers/make-suite';
import {BRE} from '../helpers/misc-utils';
import {waitForTx} from './__setup.spec';
const {parseEther} = ethers.utils;
makeSuite('AToken: Permit', (testEnv: TestEnv) => {
it('Checks the domain separator', async () => {
const DOMAIN_SEPARATOR_ENCODED = getATokenDomainSeparatorPerNetwork(
eEthereumNetwork.buidlerevm
);
const {aDai} = testEnv;
const separator = await aDai.DOMAIN_SEPARATOR();
expect(separator).to.be.equal(DOMAIN_SEPARATOR_ENCODED, 'Invalid domain separator');
});
it('Get aDAI for tests', async () => {
const {dai, deployer, pool} = testEnv;
await dai.mint(parseEther('20000'));
await dai.approve(pool.address, parseEther('20000'));
await pool.deposit(dai.address, parseEther('20000'), deployer.address, 0);
});
it('Reverts submitting a permit with 0 expiration', async () => {
const {aDai, deployer, users} = testEnv;
const owner = deployer;
const spender = users[1];
const tokenName = await aDai.name();
const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID;
const expiration = 0;
const nonce = (await aDai._nonces(owner.address)).toNumber();
const permitAmount = ethers.utils.parseEther('2').toString();
const msgParams = buildPermitParams(
chainId,
aDai.address,
'1',
tokenName,
owner.address,
spender.address,
nonce,
permitAmount,
expiration.toFixed()
);
const ownerPrivateKey = require('../test-wallets.js').accounts[0].secretKey;
if (!ownerPrivateKey) {
throw new Error('INVALID_OWNER_PK');
}
expect((await aDai.allowance(owner.address, spender.address)).toString()).to.be.equal(
'0',
'INVALID_ALLOWANCE_BEFORE_PERMIT'
);
const {v, r, s} = getSignatureFromTypedData(ownerPrivateKey, msgParams);
await expect(
aDai
.connect(spender.signer)
.permit(owner.address, spender.address, permitAmount, expiration, v, r, s)
).to.be.revertedWith('INVALID_EXPIRATION');
expect((await aDai.allowance(owner.address, spender.address)).toString()).to.be.equal(
'0',
'INVALID_ALLOWANCE_AFTER_PERMIT'
);
});
it('Submits a permit with maximum expiration length', async () => {
const {aDai, deployer, users} = testEnv;
const owner = deployer;
const spender = users[1];
const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID;
const deadline = MAX_UINT_AMOUNT;
const nonce = (await aDai._nonces(owner.address)).toNumber();
const permitAmount = parseEther('2').toString();
const msgParams = buildPermitParams(
chainId,
aDai.address,
'1',
await aDai.name(),
owner.address,
spender.address,
nonce,
deadline,
permitAmount
);
const ownerPrivateKey = require('../test-wallets.js').accounts[0].secretKey;
if (!ownerPrivateKey) {
throw new Error('INVALID_OWNER_PK');
}
expect((await aDai.allowance(owner.address, spender.address)).toString()).to.be.equal(
'0',
'INVALID_ALLOWANCE_BEFORE_PERMIT'
);
const {v, r, s} = getSignatureFromTypedData(ownerPrivateKey, msgParams);
await waitForTx(
await aDai
.connect(spender.signer)
.permit(owner.address, spender.address, permitAmount, deadline, v, r, s)
);
expect((await aDai._nonces(owner.address)).toNumber()).to.be.equal(1);
});
it('Cancels the previous permit', async () => {
const {aDai, deployer, users} = testEnv;
const owner = deployer;
const spender = users[1];
const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID;
const deadline = MAX_UINT_AMOUNT;
const nonce = (await aDai._nonces(owner.address)).toNumber();
const permitAmount = '0';
const msgParams = buildPermitParams(
chainId,
aDai.address,
'1',
await aDai.name(),
owner.address,
spender.address,
nonce,
deadline,
permitAmount
);
const ownerPrivateKey = require('../test-wallets.js').accounts[0].secretKey;
if (!ownerPrivateKey) {
throw new Error('INVALID_OWNER_PK');
}
const {v, r, s} = getSignatureFromTypedData(ownerPrivateKey, msgParams);
expect((await aDai.allowance(owner.address, spender.address)).toString()).to.be.equal(
ethers.utils.parseEther('2'),
'INVALID_ALLOWANCE_BEFORE_PERMIT'
);
await waitForTx(
await aDai
.connect(spender.signer)
.permit(owner.address, spender.address, permitAmount, deadline, v, r, s)
);
expect((await aDai.allowance(owner.address, spender.address)).toString()).to.be.equal(
permitAmount,
'INVALID_ALLOWANCE_AFTER_PERMIT'
);
expect((await aDai._nonces(owner.address)).toNumber()).to.be.equal(2);
});
it('Tries to submit a permit with invalid nonce', async () => {
const {aDai, deployer, users} = testEnv;
const owner = deployer;
const spender = users[1];
const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID;
const deadline = MAX_UINT_AMOUNT;
const nonce = 1000;
const permitAmount = '0';
const msgParams = buildPermitParams(
chainId,
aDai.address,
'1',
await aDai.name(),
owner.address,
spender.address,
nonce,
deadline,
permitAmount
);
const ownerPrivateKey = require('../test-wallets.js').accounts[0].secretKey;
if (!ownerPrivateKey) {
throw new Error('INVALID_OWNER_PK');
}
const {v, r, s} = getSignatureFromTypedData(ownerPrivateKey, msgParams);
await expect(
aDai
.connect(spender.signer)
.permit(owner.address, spender.address, permitAmount, deadline, v, r, s)
).to.be.revertedWith('INVALID_SIGNATURE');
});
it('Tries to submit a permit with invalid expiration (previous to the current block)', async () => {
const {aDai, deployer, users} = testEnv;
const owner = deployer;
const spender = users[1];
const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID;
const expiration = '1';
const nonce = (await aDai._nonces(owner.address)).toNumber();
const permitAmount = '0';
const msgParams = buildPermitParams(
chainId,
aDai.address,
'1',
await aDai.name(),
owner.address,
spender.address,
nonce,
expiration,
permitAmount
);
const ownerPrivateKey = require('../test-wallets.js').accounts[0].secretKey;
if (!ownerPrivateKey) {
throw new Error('INVALID_OWNER_PK');
}
const {v, r, s} = getSignatureFromTypedData(ownerPrivateKey, msgParams);
await expect(
aDai
.connect(spender.signer)
.permit(owner.address, spender.address, expiration, permitAmount, v, r, s)
).to.be.revertedWith('INVALID_EXPIRATION');
});
it('Tries to submit a permit with invalid signature', async () => {
const {aDai, deployer, users} = testEnv;
const owner = deployer;
const spender = users[1];
const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID;
const deadline = MAX_UINT_AMOUNT;
const nonce = (await aDai._nonces(owner.address)).toNumber();
const permitAmount = '0';
const msgParams = buildPermitParams(
chainId,
aDai.address,
'1',
await aDai.name(),
owner.address,
spender.address,
nonce,
deadline,
permitAmount
);
const ownerPrivateKey = require('../test-wallets.js').accounts[0].secretKey;
if (!ownerPrivateKey) {
throw new Error('INVALID_OWNER_PK');
}
const {v, r, s} = getSignatureFromTypedData(ownerPrivateKey, msgParams);
await expect(
aDai
.connect(spender.signer)
.permit(owner.address, ZERO_ADDRESS, permitAmount, deadline, v, r, s)
).to.be.revertedWith('INVALID_SIGNATURE');
});
it('Tries to submit a permit with invalid owner', async () => {
const {aDai, deployer, users} = testEnv;
const owner = deployer;
const spender = users[1];
const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID;
const expiration = MAX_UINT_AMOUNT;
const nonce = (await aDai._nonces(owner.address)).toNumber();
const permitAmount = '0';
const msgParams = buildPermitParams(
chainId,
aDai.address,
'1',
await aDai.name(),
owner.address,
spender.address,
nonce,
expiration,
permitAmount
);
const ownerPrivateKey = require('../test-wallets.js').accounts[0].secretKey;
if (!ownerPrivateKey) {
throw new Error('INVALID_OWNER_PK');
}
const {v, r, s} = getSignatureFromTypedData(ownerPrivateKey, msgParams);
await expect(
aDai
.connect(spender.signer)
.permit(ZERO_ADDRESS, spender.address, expiration, permitAmount, v, r, s)
).to.be.revertedWith('INVALID_OWNER');
});
});

View File

@ -60,7 +60,13 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => {
await expect(
pool
.connect(users[1].signer)
.borrow(weth.address, ethers.utils.parseEther('0.1'), RateMode.Stable, AAVE_REFERRAL),
.borrow(
weth.address,
ethers.utils.parseEther('0.1'),
RateMode.Stable,
AAVE_REFERRAL,
users[1].address
),
COLLATERAL_BALANCE_IS_0
).to.be.revertedWith(COLLATERAL_BALANCE_IS_0);
});
@ -73,7 +79,13 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => {
await pool
.connect(users[1].signer)
.borrow(weth.address, ethers.utils.parseEther('0.1'), RateMode.Stable, AAVE_REFERRAL);
.borrow(
weth.address,
ethers.utils.parseEther('0.1'),
RateMode.Stable,
AAVE_REFERRAL,
users[1].address
);
await expect(
aDai.connect(users[1].signer).transfer(users[0].address, aDAItoTransfer),

View File

@ -47,9 +47,9 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn
const usdcPrice = await oracle.getAssetPrice(usdc.address);
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0);
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0, user.address);
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 1, 0);
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 1, 0, user.address);
const {userData: wethUserDataBefore} = await getContractsData(
weth.address,
@ -203,7 +203,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn
.toFixed(0)
);
await pool.connect(user.signer).borrow(usdc.address, amountUSDCToBorrow, 2, 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 () => {
@ -464,7 +464,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn
.toFixed(0)
);
await pool.connect(user.signer).borrow(dai.address, amountDAIToBorrow, 2, 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 () => {
@ -736,7 +736,9 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn
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);
await pool
.connect(user.signer)
.borrow(usdc.address, amountToBorrowVariable, 2, 0, user.address);
const amountToRepay = amountToBorrowVariable;
@ -844,7 +846,7 @@ makeSuite('LendingPool. repayWithCollateral() with liquidator', (testEnv: TestEn
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);
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0, user.address);
});
it('Liquidator tries to liquidates User 5 USDC loan by swapping his WETH collateral, should revert due WETH collateral disabled', async () => {

View File

@ -54,7 +54,6 @@ const almostEqualOrEqual = function (
return;
}
this.assert(actual[key] != undefined, `Property ${key} is undefined in the actual data`);
expect(expected[key] != undefined, `Property ${key} is undefined in the expected data`);
@ -63,10 +62,9 @@ const almostEqualOrEqual = function (
}
if (actual[key] instanceof BigNumber) {
const actualValue = (<BigNumber>actual[key]).decimalPlaces(0, BigNumber.ROUND_DOWN);
const expectedValue = (<BigNumber>expected[key]).decimalPlaces(0, BigNumber.ROUND_DOWN);
this.assert(
actualValue.eq(expectedValue) ||
actualValue.plus(1).eq(expectedValue) ||
@ -283,11 +281,41 @@ export const withdraw = async (
}
};
export const delegateBorrowAllowance = async (
reserveSymbol: string,
amount: string,
interestRateMode: string,
user: SignerWithAddress,
receiver: tEthereumAddress,
expectedResult: string,
testEnv: TestEnv,
revertMessage?: string
) => {
const {pool} = testEnv;
const reserve = await getReserveAddressFromSymbol(reserveSymbol);
const amountToDelegate = await convertToCurrencyDecimals(reserve, amount);
const delegateAllowancePromise = pool
.connect(user.signer)
.delegateBorrowAllowance(reserve, receiver, interestRateMode, amountToDelegate.toString());
if (expectedResult === 'revert') {
await expect(delegateAllowancePromise, revertMessage).to.be.reverted;
return;
} else {
await delegateAllowancePromise;
expect(
(await pool.getBorrowAllowance(user.address, receiver, reserve, interestRateMode)).toString()
).to.be.equal(amountToDelegate.toString(), 'borrowAllowance are set incorrectly');
}
};
export const borrow = async (
reserveSymbol: string,
amount: string,
interestRateMode: string,
user: SignerWithAddress,
onBehalfOf: tEthereumAddress,
timeTravel: string,
expectedResult: string,
testEnv: TestEnv,
@ -299,15 +327,18 @@ export const borrow = async (
const {reserveData: reserveDataBefore, userData: userDataBefore} = await getContractsData(
reserve,
user.address,
testEnv
onBehalfOf,
testEnv,
user.address
);
const amountToBorrow = await convertToCurrencyDecimals(reserve, amount);
if (expectedResult === 'success') {
const txResult = await waitForTx(
await pool.connect(user.signer).borrow(reserve, amountToBorrow, interestRateMode, '0')
await pool
.connect(user.signer)
.borrow(reserve, amountToBorrow, interestRateMode, '0', onBehalfOf)
);
const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult);
@ -322,7 +353,7 @@ export const borrow = async (
reserveData: reserveDataAfter,
userData: userDataAfter,
timestamp,
} = await getContractsData(reserve, user.address, testEnv);
} = await getContractsData(reserve, onBehalfOf, testEnv, user.address);
const expectedReserveData = calcExpectedReserveDataAfterBorrow(
amountToBorrow.toString(),
@ -369,7 +400,7 @@ export const borrow = async (
// });
} else if (expectedResult === 'revert') {
await expect(
pool.connect(user.signer).borrow(reserve, amountToBorrow, interestRateMode, '0'),
pool.connect(user.signer).borrow(reserve, amountToBorrow, interestRateMode, '0', onBehalfOf),
revertMessage
).to.be.reverted;
}

View File

@ -8,7 +8,8 @@ import {
repay,
setUseAsCollateral,
swapBorrowRateMode,
rebalanceStableBorrowRate
rebalanceStableBorrowRate,
delegateBorrowAllowance,
} from './actions';
import {RateMode} from '../../helpers/types';
@ -59,7 +60,7 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv
if (borrowRateMode) {
if (borrowRateMode === 'none') {
RateMode.None;
rateMode = RateMode.None;
} else if (borrowRateMode === 'stable') {
rateMode = RateMode.Stable;
} else if (borrowRateMode === 'variable') {
@ -111,6 +112,27 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv
}
break;
case 'delegateBorrowAllowance':
{
const {amount, toUser: toUserIndex} = action.args;
const toUser = users[parseInt(toUserIndex, 10)].address;
if (!amount || amount === '') {
throw `Invalid amount to deposit into the ${reserve} reserve`;
}
await delegateBorrowAllowance(
reserve,
amount,
rateMode,
user,
toUser,
expected,
testEnv,
revertMessage
);
}
break;
case 'withdraw':
{
const {amount} = action.args;
@ -124,13 +146,27 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv
break;
case 'borrow':
{
const {amount, timeTravel} = action.args;
const {amount, timeTravel, onBehalfOf: onBehalfOfIndex} = action.args;
const onBehalfOf = onBehalfOfIndex
? users[parseInt(onBehalfOfIndex)].address
: user.address;
if (!amount || amount === '') {
throw `Invalid amount to borrow from the ${reserve} reserve`;
}
await borrow(reserve, amount, rateMode, user, timeTravel, expected, testEnv, revertMessage);
await borrow(
reserve,
amount,
rateMode,
user,
onBehalfOf,
timeTravel,
expected,
testEnv,
revertMessage
);
}
break;
@ -194,7 +230,7 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv
await rebalanceStableBorrowRate(reserve, user, target, expected, testEnv, revertMessage);
}
break;
default:
throw `Invalid action requested: ${name}`;
}

View File

@ -0,0 +1,148 @@
{
"title": "LendingPool: credit delegation",
"description": "Test cases for the credit delegation related functions.",
"stories": [
{
"description": "User 0 deposits 1000 DAI, user 0 delegates borrowing of 1 WETH on variable to user 4, user 4 borrows 1 WETH variable on behalf of user 0",
"actions": [
{
"name": "mint",
"args": {
"reserve": "WETH",
"amount": "1000",
"user": "0"
},
"expected": "success"
},
{
"name": "approve",
"args": {
"reserve": "WETH",
"user": "0"
},
"expected": "success"
},
{
"name": "deposit",
"args": {
"reserve": "WETH",
"amount": "1000",
"user": "0"
},
"expected": "success"
},
{
"name": "delegateBorrowAllowance",
"args": {
"reserve": "WETH",
"amount": "2",
"user": "0",
"borrowRateMode": "variable",
"toUser": "4"
},
"expected": "success"
},
{
"name": "borrow",
"args": {
"reserve": "WETH",
"amount": "1",
"user": "4",
"onBehalfOf": "0",
"borrowRateMode": "variable"
},
"expected": "success"
}
]
},
{
"description": "User 4 trying to borrow 1 WETH stable on behalf of user 0, revert expected",
"actions": [
{
"name": "borrow",
"args": {
"reserve": "WETH",
"amount": "1",
"user": "4",
"onBehalfOf": "0",
"borrowRateMode": "stable"
},
"expected": "revert",
"revertMessage": "54"
}
]
},
{
"description": "User 0 delegates borrowing of 1 WETH to user 4, user 4 borrows 3 WETH variable on behalf of user 0, revert expected",
"actions": [
{
"name": "delegateBorrowAllowance",
"args": {
"reserve": "WETH",
"amount": "1",
"user": "0",
"borrowRateMode": "variable",
"toUser": "4"
},
"expected": "success"
},
{
"name": "borrow",
"args": {
"reserve": "WETH",
"amount": "3",
"user": "4",
"onBehalfOf": "0",
"borrowRateMode": "variable"
},
"expected": "revert",
"revertMessage": "54"
}
]
},
{
"description": "User 0 delegates borrowing of 1 WETH on stable to user 2, user 2 borrows 1 WETH stable on behalf of user 0",
"actions": [
{
"name": "delegateBorrowAllowance",
"args": {
"reserve": "WETH",
"amount": "1",
"user": "0",
"borrowRateMode": "stable",
"toUser": "2"
},
"expected": "success"
},
{
"name": "borrow",
"args": {
"reserve": "WETH",
"amount": "1",
"user": "2",
"onBehalfOf": "0",
"borrowRateMode": "stable"
},
"expected": "success"
}
]
},
{
"description": "User 0 delegates borrowing of 1 WETH to user 2 with wrong borrowRateMode, revert expected",
"actions": [
{
"name": "delegateBorrowAllowance",
"args": {
"reserve": "WETH",
"amount": "1",
"user": "0",
"borrowRateMode": "random",
"toUser": "2"
},
"expected": "revert",
"revertMessage": "8"
}
]
}
]
}

View File

@ -64,7 +64,7 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
await pool
.connect(borrower.signer)
.borrow(dai.address, amountDAIToBorrow, RateMode.Variable, '0');
.borrow(dai.address, amountDAIToBorrow, RateMode.Variable, '0', borrower.address);
const userGlobalDataAfter = await pool.getUserAccountData(borrower.address);
@ -262,7 +262,7 @@ makeSuite('LendingPool liquidation - liquidator receiving aToken', (testEnv) =>
await pool
.connect(borrower.signer)
.borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0');
.borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0', borrower.address);
//drops HF below 1

View File

@ -68,7 +68,7 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
await pool
.connect(borrower.signer)
.borrow(dai.address, amountDAIToBorrow, RateMode.Stable, '0');
.borrow(dai.address, amountDAIToBorrow, RateMode.Stable, '0', borrower.address);
const userGlobalDataAfter = await pool.getUserAccountData(borrower.address);
@ -240,7 +240,7 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
await pool
.connect(borrower.signer)
.borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0');
.borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0', borrower.address);
//drops HF below 1
await oracle.setAssetPrice(

View File

@ -219,7 +219,7 @@ makeSuite('Pausable Pool', (testEnv: TestEnv) => {
await pool
.connect(borrower.signer)
.borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0');
.borrow(usdc.address, amountUSDCToBorrow, RateMode.Stable, '0', borrower.address);
// Drops HF below 1
await oracle.setAssetPrice(
@ -264,7 +264,7 @@ makeSuite('Pausable Pool', (testEnv: TestEnv) => {
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);
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0, user.address);
const amountToRepay = parseUnits('65', 6);
@ -306,7 +306,7 @@ makeSuite('Pausable Pool', (testEnv: TestEnv) => {
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);
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0, user.address);
// Pause pool
await configurator.pausePool();

View File

@ -67,7 +67,7 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
await pool.connect(user.signer).borrow(dai.address, amountToBorrow, 2, 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 () => {
@ -189,7 +189,7 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 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 () => {
@ -311,9 +311,11 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
await pool.connect(user.signer).deposit(weth.address, amountToDeposit, user.address, '0');
await pool.connect(user.signer).borrow(usdc.address, amountToBorrowVariable, 2, 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);
await pool.connect(user.signer).borrow(usdc.address, amountToBorrowStable, 1, 0, user.address);
const amountToRepay = parseUnits('80', 6);
@ -452,7 +454,7 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
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);
await pool.connect(user.signer).borrow(dai.address, amountToBorrowVariable, 2, 0, user.address);
const amountToRepay = parseEther('80');
@ -544,7 +546,7 @@ makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
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);
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 () => {