Removed interest redirection, fixed tests

This commit is contained in:
The3D 2020-09-09 21:16:39 +02:00
parent a3934152fe
commit a67c56c09f
11 changed files with 15 additions and 1383 deletions

View File

@ -28,13 +28,8 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
address public immutable UNDERLYING_ASSET_ADDRESS;
mapping(address => address) private _interestRedirectionAddresses;
mapping(address => uint256) private _interestRedirectionIndexes;
mapping(address => uint256) private _scaledRedirectedBalances;
mapping(address => uint256) private _redirectedBalanceIndexes;
mapping(address => address) private _interestRedirectionAllowances;
LendingPool private immutable _pool;
@ -69,44 +64,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
_setDecimals(underlyingAssetDecimals);
}
/**
* @dev redirects the interest generated to a target address.
* when the interest is redirected, the user balance is added to
* the recepient redirected balance.
* @param to the address to which the interest will be redirected
**/
function redirectInterestStream(address to) external override {
_redirectInterestStream(msg.sender, to);
}
/**
* @dev redirects the interest generated by from to a target address.
* when the interest is redirected, the user balance is added to
* the recepient redirected balance. The caller needs to have allowance on
* the interest redirection to be able to execute the function.
* @param from the address of the user whom interest is being redirected
* @param to the address to which the interest will be redirected
**/
function redirectInterestStreamOf(address from, address to) external override {
require(
msg.sender == _interestRedirectionAllowances[from],
Errors.INTEREST_REDIRECTION_NOT_ALLOWED
);
_redirectInterestStream(from, to);
}
/**
* @dev gives allowance to an address to execute the interest redirection
* on behalf of the caller.
* @param to the address to which the interest will be redirected. Pass address(0) to reset
* the allowance.
**/
function allowInterestRedirectionTo(address to) external override {
require(to != msg.sender, Errors.CANNOT_GIVE_ALLOWANCE_TO_HIMSELF);
_interestRedirectionAllowances[msg.sender] = to;
emit InterestRedirectionAllowanceChanged(msg.sender, to);
}
/**
* @dev burns the aTokens and sends the equivalent amount of underlying to the target.
* only lending pools can call this function
@ -128,16 +85,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
_burn(user, scaledAmount);
if(amount == currentBalance){
_resetDataOnZeroBalance(user);
}
//if the user is redirecting his interest towards someone else,
//we update the redirected balance of the redirection address by adding the accrued interest,
//and removing the amount to redeem
_updateRedirectedBalanceOfRedirectionAddress(user, user, scaledAmount, 0, index);
//transfers the underlying to the target
ERC20(UNDERLYING_ASSET_ADDRESS).safeTransfer(receiverOfUnderlying, amount);
@ -160,11 +107,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
//mint an equivalent amount of tokens to cover the new deposit
_mint(user,scaledAmount);
//if the user is redirecting his interest towards someone else,
//we update the redirected balance of the redirection address by adding the accrued interest
//and the amount deposited
_updateRedirectedBalanceOfRedirectionAddress(user, user, scaledAmount, 0, index);
emit Mint(user, amount, index);
}
@ -187,39 +129,17 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
/**
* @dev calculates the balance of the user, which is the
* principal balance + interest generated by the principal balance + interest generated by the redirected balance
* principal balance + interest generated by the principal balance
* @param user the user for which the balance is being calculated
* @return the total balance of the user
**/
function balanceOf(address user) public override(ERC20, IERC20) view returns (uint256) {
//current scaled balance of the user
uint256 currentScaledBalance = super.balanceOf(user);
//balance redirected by other users to user for interest rate accrual
uint256 scaledRedirectedBalance = _scaledRedirectedBalances[user];
if (currentScaledBalance == 0 && scaledRedirectedBalance == 0) {
return 0;
}
uint256 actualBalanceRedirectedToUser = scaledRedirectedBalance > 0 ? scaledRedirectedBalance.rayMul(_redirectedBalanceIndexes[user]) : 0;
uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS);
if(_interestRedirectionAddresses[user] == address(0)){
//if the user is not redirecting the interest, his balance is the result of
//the interest accrued by his current scaled balance and the interest accrued by his
//scaled redirected balance
return currentScaledBalance.add(scaledRedirectedBalance).rayMul(index).sub(actualBalanceRedirectedToUser);
}
return super.balanceOf(user).rayMul(index);
//if the user is redirecting, his balance only increases by the balance he is being redirected to
uint256 lastBalanceRedirectedByUser = currentScaledBalance.rayMul(_interestRedirectionIndexes[user]);
return
lastBalanceRedirectedByUser.add(
scaledRedirectedBalance.rayMul(index)
).sub(actualBalanceRedirectedToUser);
}
/**
@ -232,18 +152,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
return super.balanceOf(user);
}
/**
* @dev returns the scaled balance of the user. The scaled balance is the sum of all the
* updated stored balance divided the reserve index at the moment of the update
* @param user the address of the user
* @return the scaled balance of the user
**/
function getUserInterestRedirectionIndex(address user) external override view returns (uint256) {
return _interestRedirectionIndexes[user];
}
/**
* @dev calculates the total supply of the specific aToken
* since the balance of every single user increases over time, the total supply
@ -274,153 +182,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
return _pool.balanceDecreaseAllowed(UNDERLYING_ASSET_ADDRESS, user, amount);
}
/**
* @dev returns the address to which the interest is redirected
* @param user address of the user
* @return 0 if there is no redirection, an address otherwise
**/
function getInterestRedirectionAddress(address user) external override view returns (address) {
return _interestRedirectionAddresses[user];
}
/**
* @dev returns the redirected balance of the user. The redirected balance is the balance
* redirected by other accounts to the user, that is accrueing interest for him.
* @param user address of the user
* @return the total redirected balance
**/
function getScaledRedirectedBalance(address user) external override view returns (uint256) {
return _scaledRedirectedBalances[user];
}
/**
* @dev returns the index stored during the last action that affected the redirected balance.
* scaledRedirectedBalance * redirectedBalanceIndex allows to calculate the actual redirected balance
* which is needed to calculate the interest accrued
* @param user address of the user
* @return the total redirected balance
**/
function getRedirectedBalanceIndex(address user) external override view returns (uint256) {
return _redirectedBalanceIndexes[user];
}
/**
* @dev updates the redirected balance of the user. If the user is not redirecting his
* interest, nothing is executed.
* @param user the address of the user for which the interest is being accumulated
* @param scaledBalanceToAdd the amount to add to the redirected balance
* @param scaledBalanceToRemove the amount to remove from the redirected balance
**/
function _updateRedirectedBalanceOfRedirectionAddress(
address origin,
address user,
uint256 scaledBalanceToAdd,
uint256 scaledBalanceToRemove,
uint256 index
) internal {
address redirectionAddress = _interestRedirectionAddresses[user];
//if there isn't any redirection, nothing to be done
if (redirectionAddress == address(0)) {
return;
}
//updating the redirected balance
_scaledRedirectedBalances[redirectionAddress] = _scaledRedirectedBalances[redirectionAddress]
.add(scaledBalanceToAdd)
.sub(scaledBalanceToRemove);
//updating the redirected balance index of the redirection target
_redirectedBalanceIndexes[redirectionAddress] = index;
//if the interest of redirectionAddress is also being redirected, we need to update
//the redirected balance of the redirection target by adding the balance increase
address targetOfRedirectionAddress = _interestRedirectionAddresses[redirectionAddress];
// if the redirection address is also redirecting the interest, we update his index to
// accumulate the interest until now
// note: if the next address of redirection is the same as the one who originated the update,
// it means a loop of redirection has been formed: in this case, we break the recursion as no
// further updates are needed
if (targetOfRedirectionAddress != address(0) && targetOfRedirectionAddress != origin) {
_updateRedirectedBalanceOfRedirectionAddress(origin, redirectionAddress, 0, 0, index);
}
console.log("Interest redirection completed");
emit RedirectedBalanceUpdated(
redirectionAddress,
scaledBalanceToAdd,
scaledBalanceToRemove,
index
);
}
/**
* @dev executes the redirection of the interest from one address to another.
* immediately after redirection, the destination address will start to accrue interest.
* @param from the address from which transfer the aTokens
* @param to the destination address
**/
function _redirectInterestStream(address from, address to) internal {
address currentRedirectionAddress = _interestRedirectionAddresses[from];
require(to != currentRedirectionAddress, Errors.INTEREST_ALREADY_REDIRECTED);
uint256 scaledFromBalance = super.balanceOf(from);
uint256 fromBalance = balanceOf(from);
require(fromBalance > 0, Errors.NO_VALID_BALANCE_FOR_REDIRECTION);
uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS);
uint256 scaledBalance = fromBalance.rayDiv(index);
//if the user is already redirecting the interest to someone, before changing
//the redirection address we substract the redirected balance of the previous
//recipient
if (currentRedirectionAddress != address(0)) {
_updateRedirectedBalanceOfRedirectionAddress(from, from, 0, scaledFromBalance, index);
}
//if the user is redirecting the interest back to himself,
//we simply set to 0 the interest redirection address
if (to == from) {
_interestRedirectionAddresses[from] = address(0);
emit InterestStreamRedirected(from, address(0), scaledBalance, index);
return;
}
//first set the redirection address to the new recipient
_interestRedirectionAddresses[from] = to;
_interestRedirectionIndexes[from] = index;
//adds the user balance to the redirected balance of the destination
_updateRedirectedBalanceOfRedirectionAddress(from, from, scaledBalance, 0, index);
emit InterestStreamRedirected(from, to, fromBalance, index);
}
/**
* @dev function to reset the interest stream redirection and the user index, if the
* user has no balance left.
* @param user the address of the user
* @return true if the user index has also been reset, false otherwise. useful to emit the proper user index value
**/
function _resetDataOnZeroBalance(address user) internal returns (bool) {
//if the user has 0 principal balance, the interest stream redirection gets reset
_interestRedirectionAddresses[user] = address(0);
_interestRedirectionIndexes[user] = 0;
//emits a InterestStreamRedirected event to notify that the redirection has been reset
emit InterestStreamRedirected(user, address(0), 0, 0);
}
/**
* @dev transfers the underlying asset to the target. Used by the lendingpool to transfer
* assets in borrow(), redeem() and flashLoan()
@ -458,20 +219,6 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
super._transfer(from, to, scaledAmount);
//if the sender is redirecting his interest towards someone else,
//adds to the redirected balance the accrued interest and removes the amount
//being transferred
_updateRedirectedBalanceOfRedirectionAddress(from, from, 0, scaledAmount, index);
//if the receiver is redirecting his interest towards someone else,
//adds to the redirected balance the accrued interest and the amount
//being transferred
_updateRedirectedBalanceOfRedirectionAddress(to, to, scaledAmount, 0, index);
if(super.balanceOf(from) == 0){
_resetDataOnZeroBalance(from);
}
emit BalanceTransfer(from, to, amount, index);
}

View File

@ -39,63 +39,6 @@ interface IAToken is IERC20 {
uint256 index
);
/**
* @dev emitted when the accumulation of the interest
* by an user is redirected to another user
* @param from the address from which the interest is being redirected
* @param to the adress of the destination
* @param redirectedBalance the scaled balance being redirected
* @param index the last index of the reserve
**/
event InterestStreamRedirected(
address indexed from,
address indexed to,
uint256 redirectedBalance,
uint256 index
);
/**
* @dev emitted when the redirected balance of an user is being updated
* @param targetAddress the address of which the balance is being updated
* @param redirectedBalanceAdded the redirected balance being added
* @param redirectedBalanceRemoved the redirected balance being removed
* @param index the last index of the reserve
**/
event RedirectedBalanceUpdated(
address indexed targetAddress,
uint256 redirectedBalanceAdded,
uint256 redirectedBalanceRemoved,
uint256 index
);
event InterestRedirectionAllowanceChanged(address indexed from, address indexed to);
/**
* @dev redirects the interest generated to a target address.
* when the interest is redirected, the user balance is added to
* the recepient redirected balance.
* @param to the address to which the interest will be redirected
**/
function redirectInterestStream(address to) external;
/**
* @dev redirects the interest generated by from to a target address.
* when the interest is redirected, the user balance is added to
* the recepient redirected balance. The caller needs to have allowance on
* the interest redirection to be able to execute the function.
* @param from the address of the user whom interest is being redirected
* @param to the address to which the interest will be redirected
**/
function redirectInterestStreamOf(address from, address to) external;
/**
* @dev gives allowance to an address to execute the interest redirection
* on behalf of the caller.
* @param to the address to which the interest will be redirected. Pass address(0) to reset
* the allowance.
**/
function allowInterestRedirectionTo(address to) external;
/**
* @dev burns the aTokens and sends the equivalent amount of underlying to the target.
* only lending pools can call this function
@ -145,42 +88,11 @@ interface IAToken is IERC20 {
function isTransferAllowed(address user, uint256 amount) external view returns (bool);
/**
* @dev returns the address to which the interest is redirected
* @param user address of the user
* @return 0 if there is no redirection, an address otherwise
**/
function getInterestRedirectionAddress(address user) external view returns (address);
/**
* @dev returns the index of the user at the moment of redirection
* @param user address of the user
* @return interest redirection index
**/
function getUserInterestRedirectionIndex(address user) external view returns (uint256);
/**
* @dev returns the scaled redirected balance of the user. The scaled redirected balance is the sum of all the redirected balances
* divided by the index at the moment of each specific redirection.
* @param user address of the user
* @return the total redirected balance
**/
function getScaledRedirectedBalance(address user) external view returns (uint256);
/**
* @dev returns the redirected balance index. The redirected balance index is the
* index at the moment the last scaled redirected balance has been performed, and allows to calculate the actual redirected balance
* @dev transfer the amount of the underlying asset to the user
* @param user address of the user
* @param amount the amount to transfer
* @return the redirected balance index
**/
function getRedirectedBalanceIndex(address user) external view returns (uint256);
/**
* @dev transfers the underlying asset to the target. Used by the lendingpool to transfer
* assets in borrow(), redeem() and flashLoan()
* @param target the target of the transfer
* @param amount the amount to transfer
* @return the amount transferred
**/
function transferUnderlyingTo(address target, uint256 amount) external returns (uint256);
function transferUnderlyingTo(address user, uint256 amount) external returns (uint256);
}

View File

@ -47,50 +47,6 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => {
);
});
it('User 1 redirects interest to user 2, transfers 500 DAI back to user 0', async () => {
const {users, aDai, dai} = testEnv;
await aDai.connect(users[1].signer).redirectInterestStream(users[2].address);
const aDAIRedirected = await convertToCurrencyDecimals(dai.address, '1000');
const aDAItoTransfer = await convertToCurrencyDecimals(dai.address, '500');
const user2RedirectedBalanceBefore = await aDai.getRedirectedBalance(users[2].address);
expect(user2RedirectedBalanceBefore.toString()).to.be.equal(
aDAIRedirected,
INVALID_REDIRECTED_BALANCE_BEFORE_TRANSFER
);
await aDai.connect(users[1].signer).transfer(users[0].address, aDAItoTransfer);
const user2RedirectedBalanceAfter = await aDai.getRedirectedBalance(users[2].address);
const user1RedirectionAddress = await aDai.getInterestRedirectionAddress(users[1].address);
expect(user2RedirectedBalanceAfter.toString()).to.be.equal(
aDAItoTransfer,
INVALID_REDIRECTED_BALANCE_BEFORE_TRANSFER
);
expect(user1RedirectionAddress.toString()).to.be.equal(
users[2].address,
INVALID_REDIRECTION_ADDRESS
);
});
it('User 0 transfers back to user 1', async () => {
const {users, aDai, dai, weth} = testEnv;
const aDAItoTransfer = await convertToCurrencyDecimals(dai.address, '500');
await aDai.connect(users[0].signer).transfer(users[1].address, aDAItoTransfer);
const user2RedirectedBalanceAfter = await aDai.getRedirectedBalance(users[2].address);
const user1BalanceAfter = await aDai.balanceOf(users[1].address);
expect(user2RedirectedBalanceAfter.toString()).to.be.equal(
user1BalanceAfter.toString(),
INVALID_REDIRECTED_BALANCE_AFTER_TRANSFER
);
});
it('User 0 deposits 1 WETH and user 1 tries to borrow, but the aTokens received as a transfer are not available as collateral (revert expected)', async () => {
const {users, pool, weth} = testEnv;
@ -124,79 +80,4 @@ makeSuite('AToken: Transfer', (testEnv: TestEnv) => {
).to.be.revertedWith(TRANSFER_NOT_ALLOWED);
});
it('User 1 repays the borrow, transfers aDAI back to user 0', async () => {
const {users, pool, aDai, dai, weth} = testEnv;
await weth.connect(users[1].signer).mint(await convertToCurrencyDecimals(weth.address, '2'));
await weth.connect(users[1].signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL);
await pool
.connect(users[1].signer)
.repay(weth.address, MAX_UINT_AMOUNT, RateMode.Stable, users[1].address);
const aDAItoTransfer = await convertToCurrencyDecimals(aDai.address, '1000');
await aDai.connect(users[1].signer).transfer(users[0].address, aDAItoTransfer);
const user2RedirectedBalanceAfter = await aDai.getRedirectedBalance(users[2].address);
const user1RedirectionAddress = await aDai.getInterestRedirectionAddress(users[1].address);
expect(user2RedirectedBalanceAfter.toString()).to.be.equal(
'0',
INVALID_REDIRECTED_BALANCE_AFTER_TRANSFER
);
expect(user1RedirectionAddress.toString()).to.be.equal(
ZERO_ADDRESS,
INVALID_REDIRECTION_ADDRESS
);
});
it('User 0 redirects interest to user 2, transfers 500 aDAI to user 1. User 1 redirects to user 3. User 0 transfers another 100 aDAI', async () => {
const {users, pool, aDai, dai, weth} = testEnv;
let aDAItoTransfer = await convertToCurrencyDecimals(aDai.address, '500');
await aDai.connect(users[0].signer).redirectInterestStream(users[2].address);
await aDai.connect(users[0].signer).transfer(users[1].address, aDAItoTransfer);
await aDai.connect(users[1].signer).redirectInterestStream(users[3].address);
aDAItoTransfer = await convertToCurrencyDecimals(aDai.address, '100');
await aDai.connect(users[0].signer).transfer(users[1].address, aDAItoTransfer);
const user2RedirectedBalanceAfter = await aDai.getRedirectedBalance(users[2].address);
const user3RedirectedBalanceAfter = await aDai.getRedirectedBalance(users[3].address);
const expectedUser2Redirected = await convertToCurrencyDecimals(aDai.address, '400');
const expectedUser3Redirected = await convertToCurrencyDecimals(aDai.address, '600');
expect(user2RedirectedBalanceAfter.toString()).to.be.equal(
expectedUser2Redirected,
INVALID_REDIRECTED_BALANCE_AFTER_TRANSFER
);
expect(user3RedirectedBalanceAfter.toString()).to.be.equal(
expectedUser3Redirected,
INVALID_REDIRECTED_BALANCE_AFTER_TRANSFER
);
});
it('User 1 transfers the whole amount to himself', async () => {
const {users, pool, aDai, dai} = testEnv;
const user1BalanceBefore = await aDai.balanceOf(users[1].address);
await aDai.connect(users[1].signer).transfer(users[1].address, user1BalanceBefore);
const user1BalanceAfter = await aDai.balanceOf(users[1].address);
expect(user1BalanceAfter.toString()).to.be.equal(
user1BalanceBefore,
INVALID_REDIRECTED_BALANCE_AFTER_TRANSFER
);
});
});

View File

@ -14,7 +14,6 @@ import {
calcExpectedUserDataAfterStableRateRebalance,
calcExpectedUserDataAfterSwapRateMode,
calcExpectedUserDataAfterWithdraw,
calcExpectedUsersDataAfterRedirectInterest,
} from './utils/calculations';
import {getReserveAddressFromSymbol, getReserveData, getUserData} from './utils/helpers';
@ -652,156 +651,6 @@ export const rebalanceStableBorrowRate = async (
}
};
export const redirectInterestStream = async (
reserveSymbol: string,
user: SignerWithAddress,
to: tEthereumAddress,
expectedResult: string,
testEnv: TestEnv,
revertMessage?: string
) => {
const {
aTokenInstance,
reserve,
userData: fromDataBefore,
reserveData: reserveDataBefore,
} = await getDataBeforeAction(reserveSymbol, user.address, testEnv);
const {userData: toDataBefore} = await getContractsData(reserve, to, testEnv);
if (expectedResult === 'success') {
const txResult = await waitForTx(
await aTokenInstance.connect(user.signer).redirectInterestStream(to)
);
const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult);
const {userData: fromDataAfter} = await getContractsData(reserve, user.address, testEnv);
const {userData: toDataAfter} = await getContractsData(reserve, to, testEnv);
const [expectedFromData, expectedToData] = calcExpectedUsersDataAfterRedirectInterest(
reserveDataBefore,
fromDataBefore,
toDataBefore,
user.address,
to,
true,
txCost,
txTimestamp
);
console.log("Checking from data");
expectEqual(fromDataAfter, expectedFromData);
console.log("Checking to data");
expectEqual(toDataAfter, expectedToData);
// truffleAssert.eventEmitted(txResult, 'InterestStreamRedirected', (ev: any) => {
// const {_from, _to} = ev;
// return _from === user
// && _to === (to === user ? NIL_ADDRESS : to);
// });
} else if (expectedResult === 'revert') {
await expect(aTokenInstance.connect(user.signer).redirectInterestStream(to), revertMessage).to
.be.reverted;
}
};
export const redirectInterestStreamOf = async (
reserveSymbol: string,
user: SignerWithAddress,
from: tEthereumAddress,
to: tEthereumAddress,
expectedResult: string,
testEnv: TestEnv,
revertMessage?: string
) => {
const {
aTokenInstance,
reserve,
userData: fromDataBefore,
reserveData: reserveDataBefore,
} = await getDataBeforeAction(reserveSymbol, from, testEnv);
const {userData: toDataBefore} = await getContractsData(reserve, user.address, testEnv);
if (expectedResult === 'success') {
const txResult = await waitForTx(
await aTokenInstance.connect(user.signer).redirectInterestStreamOf(from, to)
);
const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult);
const {userData: fromDataAfter} = await getContractsData(reserve, from, testEnv);
const {userData: toDataAfter} = await getContractsData(reserve, to, testEnv);
const [expectedFromData, exptectedToData] = calcExpectedUsersDataAfterRedirectInterest(
reserveDataBefore,
fromDataBefore,
toDataBefore,
from,
to,
from === user.address,
txCost,
txTimestamp
);
expectEqual(fromDataAfter, expectedFromData);
expectEqual(toDataAfter, exptectedToData);
// truffleAssert.eventEmitted(
// txResult,
// "InterestStreamRedirected",
// (ev: any) => {
// const {_from, _to} = ev;
// return (
// _from.toLowerCase() === from.toLowerCase() &&
// _to.toLowerCase() === to.toLowerCase()
// );
// }
// );
} else if (expectedResult === 'revert') {
await expect(
aTokenInstance.connect(user.signer).redirectInterestStreamOf(from, to),
revertMessage
).to.be.reverted;
}
};
export const allowInterestRedirectionTo = async (
reserveSymbol: string,
user: SignerWithAddress,
to: tEthereumAddress,
expectedResult: string,
testEnv: TestEnv,
revertMessage?: string
) => {
const {aTokenInstance} = await getDataBeforeAction(reserveSymbol, user.address, testEnv);
if (expectedResult === 'success') {
const txResult = await waitForTx(
await aTokenInstance.connect(user.signer).allowInterestRedirectionTo(to)
);
// truffleAssert.eventEmitted(
// txResult,
// "InterestRedirectionAllowanceChanged",
// (ev: any) => {
// const {_from, _to} = ev;
// return (
// _from.toLowerCase() === user.toLowerCase() &&
// _to.toLowerCase() === to.toLowerCase()
// );
// }
// );
} else if (expectedResult === 'revert') {
await expect(aTokenInstance.connect(user.signer).allowInterestRedirectionTo(to), revertMessage)
.to.be.reverted;
}
};
const expectEqual = (
actual: UserReserveData | ReserveData,
expected: UserReserveData | ReserveData

View File

@ -8,10 +8,7 @@ import {
repay,
setUseAsCollateral,
swapBorrowRateMode,
rebalanceStableBorrowRate,
redirectInterestStream,
redirectInterestStreamOf,
allowInterestRedirectionTo,
rebalanceStableBorrowRate
} from './actions';
import {RateMode} from '../../helpers/types';
@ -185,71 +182,7 @@ const executeAction = async (action: Action, users: SignerWithAddress[], testEnv
await rebalanceStableBorrowRate(reserve, user, target, expected, testEnv, revertMessage);
}
break;
case 'redirectInterestStream':
{
const {to: toIndex} = action.args;
if (!toIndex || toIndex === '') {
throw `A target must be selected when trying to redirect the interest`;
}
const toUser = users[parseInt(toIndex)];
await redirectInterestStream(
reserve,
user,
toUser.address,
expected,
testEnv,
revertMessage
);
}
break;
case 'redirectInterestStreamOf':
{
const {from: fromIndex, to: toIndex} = action.args;
if (!fromIndex || fromIndex === '') {
throw `A from address must be specified when trying to redirect the interest`;
}
if (!toIndex || toIndex === '') {
throw `A target must be selected when trying to redirect the interest`;
}
const toUser = users[parseInt(toIndex)];
const fromUser = users[parseInt(fromIndex)];
await redirectInterestStreamOf(
reserve,
user,
fromUser.address,
toUser.address,
expected,
testEnv,
revertMessage
);
}
break;
case 'allowInterestRedirectionTo':
{
const {to: toIndex} = action.args;
if (!toIndex || toIndex === '') {
throw `A target must be selected when trying to redirect the interest`;
}
const toUser = users[parseInt(toIndex)];
await allowInterestRedirectionTo(
reserve,
user,
toUser.address,
expected,
testEnv,
revertMessage
);
}
break;
default:
throw `Invalid action requested: ${name}`;
}

View File

@ -1,102 +0,0 @@
{
"title": "AToken: interest rate redirection negative test cases",
"description": "Test cases for the aToken interest rate redirection.",
"stories": [
{
"description": "User 0 deposits 1000 DAI, tries to give allowance to redirect interest to himself (revert expected)",
"actions": [
{
"name": "mint",
"args": {
"reserve": "DAI",
"amount": "1000",
"user": "0"
},
"expected": "success"
},
{
"name": "approve",
"args": {
"reserve": "DAI",
"user": "0"
},
"expected": "success"
},
{
"name": "deposit",
"args": {
"reserve": "DAI",
"amount": "1000",
"user": "0"
},
"expected": "success"
},
{
"name": "allowInterestRedirectionTo",
"args": {
"reserve": "DAI",
"user": "0",
"to": "0"
},
"expected": "revert",
"revertMessage": "User cannot give allowance to himself"
}
]
},
{
"description": "User 1 tries to redirect the interest of user 0 without allowance (revert expected)",
"actions": [
{
"name": "redirectInterestStreamOf",
"args": {
"reserve": "DAI",
"user": "1",
"from": "0",
"to": "2"
},
"expected": "revert",
"revertMessage": "Caller is not allowed to redirect the interest of the user"
}
]
},
{
"description": "User 0 tries to redirect the interest to user 2 twice (revert expected)",
"actions": [
{
"name": "redirectInterestStream",
"args": {
"reserve": "DAI",
"user": "0",
"to": "2"
},
"expected": "success"
},
{
"name": "redirectInterestStream",
"args": {
"reserve": "DAI",
"user": "0",
"to": "2"
},
"expected": "revert",
"revertMessage": "Interest is already redirected to the user"
}
]
},
{
"description": "User 3 with 0 balance tries to redirect the interest to user 2 (revert expected)",
"actions": [
{
"name": "redirectInterestStream",
"args": {
"reserve": "DAI",
"user": "3",
"to": "2"
},
"expected": "revert",
"revertMessage": "Interest stream can only be redirected if there is a valid balance"
}
]
}
]
}

View File

@ -1,405 +0,0 @@
{
"title": "AToken: interest rate redirection",
"description": "Test cases for the aToken interest rate redirection.",
"stories": [
{
"description": "User 0 deposits 1000 DAI, redirects the interest to user 2",
"actions": [
{
"name": "mint",
"args": {
"reserve": "DAI",
"amount": "1000",
"user": "0"
},
"expected": "success"
},
{
"name": "approve",
"args": {
"reserve": "DAI",
"user": "0"
},
"expected": "success"
},
{
"name": "deposit",
"args": {
"reserve": "DAI",
"amount": "1000",
"user": "0"
},
"expected": "success"
},
{
"name": "redirectInterestStream",
"args": {
"reserve": "DAI",
"user": "0",
"to": "2"
},
"expected": "success"
}
]
},
{
"description": "User 1 deposits 1 ETH, borrows 100 DAI, repays after one year. Users 0 deposits another 1000 DAI. Redirected balance of user 2 is updated",
"actions": [
{
"name": "mint",
"args": {
"reserve": "WETH",
"amount": "2",
"user": "1"
},
"expected": "success"
},
{
"name": "approve",
"args": {
"reserve": "WETH",
"user": "1"
},
"expected": "success"
},
{
"name": "deposit",
"args": {
"reserve": "WETH",
"amount": "2",
"user": "1"
},
"expected": "success"
},
{
"name": "borrow",
"args": {
"reserve": "DAI",
"amount": "100",
"borrowRateMode": "stable",
"user": "1",
"timeTravel": "365"
},
"expected": "success"
},
{
"name": "mint",
"args": {
"reserve": "DAI",
"amount": "1000",
"user": "0"
},
"expected": "success"
},
{
"name": "deposit",
"args": {
"reserve": "DAI",
"amount": "1000",
"user": "0"
},
"expected": "success"
}
]
},
{
"description": "User 1 borrows another 100 DAI, repay the whole amount. Users 0 and User 2 withdraw",
"actions": [
{
"name": "borrow",
"args": {
"reserve": "DAI",
"amount": "100",
"borrowRateMode": "stable",
"user": "1",
"timeTravel": "365"
},
"expected": "success"
},
{
"name": "mint",
"args": {
"reserve": "DAI",
"amount": "100",
"user": "1"
},
"expected": "success"
},
{
"name": "approve",
"args": {
"reserve": "DAI",
"user": "1"
},
"expected": "success"
},
{
"name": "repay",
"args": {
"reserve": "DAI",
"amount": "-1",
"user": "1",
"onBehalfOf": "1",
"borrowRateMode": "stable"
},
"expected": "success"
},
{
"name": "withdraw",
"args": {
"reserve": "DAI",
"amount": "-1",
"user": "0"
},
"expected": "success"
},
{
"name": "withdraw",
"args": {
"reserve": "DAI",
"amount": "-1",
"user": "2"
},
"expected": "success"
}
]
},
{
"description": "User 0 deposits 1000 DAI, redirects interest to user 2, user 1 borrows 100 DAI. After one year, user 0 redirects interest back to himself, user 1 borrows another 100 DAI and after another year repays the whole amount. Users 0 and User 2 withdraw",
"actions": [
{
"name": "deposit",
"args": {
"reserve": "DAI",
"amount": "1000",
"user": "0"
},
"expected": "success"
},
{
"name": "redirectInterestStream",
"args": {
"reserve": "DAI",
"user": "0",
"to": "2"
},
"expected": "success"
},
{
"name": "borrow",
"args": {
"reserve": "DAI",
"amount": "100",
"borrowRateMode": "stable",
"user": "1",
"timeTravel": "365"
},
"expected": "success"
},
{
"name": "redirectInterestStream",
"args": {
"reserve": "DAI",
"user": "0",
"to": "0"
},
"expected": "success"
},
{
"name": "borrow",
"args": {
"reserve": "DAI",
"amount": "100",
"borrowRateMode": "stable",
"user": "1",
"timeTravel": "365"
},
"expected": "success"
},
{
"name": "mint",
"args": {
"reserve": "DAI",
"amount": "100",
"user": "1"
},
"expected": "success"
},
{
"name": "approve",
"args": {
"reserve": "DAI",
"user": "1"
},
"expected": "success"
},
{
"name": "repay",
"args": {
"reserve": "DAI",
"amount": "-1",
"user": "1",
"onBehalfOf": "1",
"borrowRateMode": "stable"
},
"expected": "success"
},
{
"name": "withdraw",
"args": {
"reserve": "DAI",
"amount": "-1",
"user": "0"
},
"expected": "success"
},
{
"name": "withdraw",
"args": {
"reserve": "DAI",
"amount": "-1",
"user": "2"
},
"expected": "success"
}
]
},
{
"description": "User 0 deposits 1000 DAI, redirects interest to user 2, user 1 borrows 100 DAI. After one year, user 2 redirects interest to user 3. user 1 borrows another 100 DAI, user 0 deposits another 100 DAI. User 1 repays the whole amount. Users 0, 2 first 1 DAI, then everything. User 3 withdraws",
"actions": [
{
"name": "deposit",
"args": {
"reserve": "DAI",
"amount": "1000",
"user": "0"
},
"expected": "success"
},
{
"name": "redirectInterestStream",
"args": {
"reserve": "DAI",
"user": "0",
"to": "2"
},
"expected": "success"
},
{
"name": "borrow",
"args": {
"reserve": "DAI",
"amount": "100",
"borrowRateMode": "stable",
"user": "1",
"timeTravel": "365"
},
"expected": "success"
},
{
"name": "redirectInterestStream",
"args": {
"reserve": "DAI",
"user": "2",
"to": "3"
},
"expected": "success"
},
{
"name": "borrow",
"args": {
"reserve": "DAI",
"amount": "100",
"borrowRateMode": "stable",
"user": "1",
"timeTravel": "365"
},
"expected": "success"
},
{
"name": "deposit",
"args": {
"reserve": "DAI",
"amount": "100",
"user": "0"
},
"expected": "success"
},
{
"name": "mint",
"args": {
"reserve": "DAI",
"amount": "100",
"user": "1"
},
"expected": "success"
},
{
"name": "approve",
"args": {
"reserve": "DAI",
"user": "1"
},
"expected": "success"
},
{
"name": "repay",
"args": {
"reserve": "DAI",
"amount": "-1",
"user": "1",
"onBehalfOf": "1",
"borrowRateMode": "stable"
},
"expected": "success"
},
{
"name": "withdraw",
"args": {
"reserve": "DAI",
"amount": "1",
"user": "0"
},
"expected": "success"
},
{
"name": "withdraw",
"args": {
"reserve": "DAI",
"amount": "1",
"user": "2"
},
"expected": "success"
},
{
"name": "withdraw",
"args": {
"reserve": "DAI",
"amount": "-1",
"user": "0"
},
"expected": "success"
},
{
"name": "withdraw",
"args": {
"reserve": "DAI",
"amount": "-1",
"user": "2"
},
"expected": "success"
},
{
"name": "withdraw",
"args": {
"reserve": "DAI",
"amount": "-1",
"user": "3"
},
"expected": "success"
}
]
}
]
}

View File

@ -70,15 +70,6 @@ export const calcExpectedUserDataAfterDeposit = (
expectedUserData.variableBorrowIndex = userDataBeforeAction.variableBorrowIndex;
expectedUserData.walletBalance = userDataBeforeAction.walletBalance.minus(amountDeposited);
expectedUserData.scaledRedirectedBalance = userDataBeforeAction.scaledRedirectedBalance;
expectedUserData.redirectedBalanceIndex = userDataBeforeAction.redirectedBalanceIndex;
expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress;
expectedUserData.interestRedirectionIndex =
userDataBeforeAction.interestRedirectionAddress == ZERO_ADDRESS
? new BigNumber(0)
: reserveDataAfterAction.liquidityIndex;
expectedUserData.currentStableDebt = expectedUserData.principalStableDebt = calcExpectedStableDebtTokenBalance(
userDataBeforeAction,
txTimestamp
@ -90,14 +81,6 @@ export const calcExpectedUserDataAfterDeposit = (
txTimestamp
);
expectedUserData.redirectionAddressScaledRedirectedBalance = calcExpectedRedirectedBalance(
expectedUserData,
reserveDataAfterAction.liquidityIndex,
userDataBeforeAction.redirectionAddressScaledRedirectedBalance,
new BigNumber(amountDeposited),
new BigNumber(0)
);
return expectedUserData;
};
@ -163,28 +146,6 @@ export const calcExpectedUserDataAfterWithdraw = (
}
expectedUserData.walletBalance = userDataBeforeAction.walletBalance.plus(amountWithdrawn);
expectedUserData.scaledRedirectedBalance = userDataBeforeAction.scaledRedirectedBalance;
expectedUserData.redirectedBalanceIndex = userDataBeforeAction.redirectedBalanceIndex;
if (expectedUserData.currentATokenBalance.eq(0) && expectedUserData.scaledRedirectedBalance.eq(0)) {
expectedUserData.interestRedirectionAddress = ZERO_ADDRESS;
expectedUserData.interestRedirectionIndex = new BigNumber(0);
} else {
expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress;
expectedUserData.interestRedirectionIndex =
userDataBeforeAction.interestRedirectionAddress == ZERO_ADDRESS
? new BigNumber(0)
: reserveDataAfterAction.liquidityIndex;
}
expectedUserData.redirectionAddressScaledRedirectedBalance = calcExpectedRedirectedBalance(
expectedUserData,
reserveDataAfterAction.liquidityIndex,
userDataBeforeAction.redirectionAddressScaledRedirectedBalance,
new BigNumber(0),
new BigNumber(amountWithdrawn)
);
return expectedUserData;
};
@ -578,14 +539,7 @@ export const calcExpectedUserDataAfterBorrow = (
currentTimestamp
);
expectedUserData.scaledATokenBalance = userDataBeforeAction.scaledATokenBalance;
expectedUserData.scaledRedirectedBalance = userDataBeforeAction.scaledRedirectedBalance;
expectedUserData.redirectedBalanceIndex = userDataBeforeAction.redirectedBalanceIndex;
expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress;
expectedUserData.interestRedirectionIndex = userDataBeforeAction.interestRedirectionIndex;
expectedUserData.redirectionAddressScaledRedirectedBalance =
userDataBeforeAction.redirectionAddressScaledRedirectedBalance;
expectedUserData.currentATokenUserIndex = userDataBeforeAction.currentATokenUserIndex;
expectedUserData.walletBalance = userDataBeforeAction.walletBalance.plus(amountBorrowed);
return expectedUserData;
@ -669,14 +623,7 @@ export const calcExpectedUserDataAfterRepay = (
txTimestamp
);
expectedUserData.scaledATokenBalance = userDataBeforeAction.scaledATokenBalance;
expectedUserData.redirectedBalanceIndex = userDataBeforeAction.redirectedBalanceIndex;
expectedUserData.scaledRedirectedBalance = userDataBeforeAction.scaledRedirectedBalance;
expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress;
expectedUserData.interestRedirectionIndex = userDataBeforeAction.interestRedirectionIndex;
expectedUserData.redirectionAddressScaledRedirectedBalance =
userDataBeforeAction.redirectionAddressScaledRedirectedBalance;
expectedUserData.currentATokenUserIndex = userDataBeforeAction.currentATokenUserIndex;
if (user === onBehalfOf) {
expectedUserData.walletBalance = userDataBeforeAction.walletBalance.minus(totalRepaid);
} else {
@ -961,78 +908,6 @@ export const calcExpectedUserDataAfterStableRateRebalance = (
return expectedUserData;
};
export const calcExpectedUsersDataAfterRedirectInterest = (
reserveDataBeforeAction: ReserveData,
fromDataBeforeAction: UserReserveData,
toDataBeforeAction: UserReserveData,
fromAddress: string,
toAddress: string,
isFromExecutingTx: boolean,
txCost: BigNumber,
txTimestamp: BigNumber
): UserReserveData[] => {
const expectedFromData = { ...fromDataBeforeAction };
const expectedToData = { ...toDataBeforeAction };
const index = calcExpectedReserveNormalizedIncome(reserveDataBeforeAction, txTimestamp);
expectedFromData.currentStableDebt = calcExpectedStableDebtTokenBalance(
fromDataBeforeAction,
txTimestamp
);
expectedToData.currentVariableDebt = calcExpectedVariableDebtTokenBalance(
reserveDataBeforeAction,
toDataBeforeAction,
txTimestamp
);
expectedFromData.variableBorrowIndex = fromDataBeforeAction.variableBorrowIndex;
expectedToData.variableBorrowIndex = toDataBeforeAction.variableBorrowIndex;
expectedFromData.stableBorrowRate = fromDataBeforeAction.stableBorrowRate;
expectedToData.stableBorrowRate = toDataBeforeAction.stableBorrowRate;
expectedFromData.scaledATokenBalance = fromDataBeforeAction.scaledATokenBalance;
expectedFromData.currentATokenBalance = calcExpectedATokenBalance(
reserveDataBeforeAction,
fromDataBeforeAction,
txTimestamp
);
expectedToData.scaledRedirectedBalance = toDataBeforeAction.scaledRedirectedBalance.plus(
expectedFromData.scaledATokenBalance
);
expectedToData.redirectedBalanceIndex = index;
if (fromAddress === toAddress) {
expectedFromData.interestRedirectionAddress = ZERO_ADDRESS;
expectedFromData.scaledRedirectedBalance = new BigNumber(0);
expectedFromData.redirectedBalanceIndex = new BigNumber(0);
expectedFromData.redirectionAddressScaledRedirectedBalance = new BigNumber(0);
expectedToData.interestRedirectionAddress = ZERO_ADDRESS;
expectedToData.scaledRedirectedBalance = new BigNumber(0);
expectedToData.redirectionAddressScaledRedirectedBalance = new BigNumber(0);
} else {
expectedFromData.interestRedirectionAddress = toAddress;
expectedFromData.interestRedirectionIndex = index;
expectedFromData.redirectionAddressScaledRedirectedBalance = calcExpectedRedirectedBalance(
expectedFromData,
index,
toDataBeforeAction.scaledRedirectedBalance,
expectedFromData.currentATokenBalance,
new BigNumber(0)
);
}
return [expectedFromData, expectedToData];
};
const calcExpectedScaledATokenBalance = (
userDataBeforeAction: UserReserveData,
index: BigNumber,
@ -1052,27 +927,10 @@ const calcExpectedATokenBalance = (
const index = calcExpectedReserveNormalizedIncome(reserveDataBeforeAction, currentTimestamp);
const {
interestRedirectionAddress,
interestRedirectionIndex: redirectionIndexBeforeAction,
scaledRedirectedBalance,
redirectedBalanceIndex: redirectedBalanceIndexBeforeAction,
scaledATokenBalance: scaledBalanceBeforeAction,
} = userDataBeforeAction;
if (scaledBalanceBeforeAction.eq(0) && scaledRedirectedBalance.eq(0)) {
return new BigNumber(0);
}
const actualRedirectedBalance = scaledRedirectedBalance.gt(0) ? scaledRedirectedBalance.rayMul(redirectedBalanceIndexBeforeAction) : new BigNumber(0);
if (interestRedirectionAddress === ZERO_ADDRESS) {
return scaledBalanceBeforeAction.plus(scaledRedirectedBalance).rayMul(index).minus(actualRedirectedBalance);
}
const lastRedirectedBalance = scaledBalanceBeforeAction.rayMul(redirectionIndexBeforeAction);
return lastRedirectedBalance.plus(scaledRedirectedBalance.rayMul(index).minus(actualRedirectedBalance));
return scaledBalanceBeforeAction.rayMul(index);
};
const calcExpectedRedirectedBalance = (

View File

@ -63,30 +63,17 @@ export const getUserData = async (
reserve: string,
user: string
): Promise<UserReserveData> => {
const [userData, aTokenData] = await Promise.all([
const [userData, scaledATokenBalance] = await Promise.all([
pool.getUserReserveData(reserve, user),
getATokenUserData(reserve, user, pool),
]);
const [
scaledRedirectedBalance,
redirectedBalanceIndex,
scaledATokenBalance,
redirectionAddressScaledRedirectedBalance,
interestRedirectionAddress,
interestRedirectionIndex,
] = aTokenData;
const token = await getMintableErc20(reserve);
const walletBalance = new BigNumber((await token.balanceOf(user)).toString());
return {
scaledATokenBalance: new BigNumber(scaledATokenBalance),
interestRedirectionAddress,
interestRedirectionIndex: new BigNumber(interestRedirectionIndex),
redirectionAddressScaledRedirectedBalance: new BigNumber(redirectionAddressScaledRedirectedBalance),
scaledRedirectedBalance: new BigNumber(scaledRedirectedBalance),
redirectedBalanceIndex: new BigNumber(redirectedBalanceIndex),
currentATokenBalance: new BigNumber(userData.currentATokenBalance.toString()),
currentStableDebt: new BigNumber(userData.currentStableDebt.toString()),
currentVariableDebt: new BigNumber(userData.currentVariableDebt.toString()),
@ -116,31 +103,8 @@ const getATokenUserData = async (reserve: string, user: string, pool: LendingPoo
const aTokenAddress: string = (await pool.getReserveTokensAddresses(reserve)).aTokenAddress;
const aToken = await getAToken(aTokenAddress);
const [
interestRedirectionAddress,
scaledRedirectedBalance,
redirectedBalanceIndex,
scaledATokenBalance,
interestRedirectionIndex
] = await Promise.all([
aToken.getInterestRedirectionAddress(user),
aToken.getScaledRedirectedBalance(user),
aToken.getRedirectedBalanceIndex(user),
aToken.scaledBalanceOf(user),
aToken.getUserInterestRedirectionIndex(user)
]);
const redirectionAddressScaledRedirectedBalance =
interestRedirectionAddress !== ZERO_ADDRESS
? new BigNumber((await aToken.getScaledRedirectedBalance(interestRedirectionAddress)).toString())
: new BigNumber('0');
const scaledBalance = await aToken.scaledBalanceOf(user);
return scaledBalance.toString();
return [
scaledRedirectedBalance.toString(),
redirectedBalanceIndex.toString(),
scaledATokenBalance.toString(),
redirectionAddressScaledRedirectedBalance.toString(),
interestRedirectionAddress,
interestRedirectionIndex.toString()
];
};

View File

@ -3,11 +3,6 @@ import BigNumber from 'bignumber.js';
export interface UserReserveData {
scaledATokenBalance: BigNumber;
currentATokenBalance: BigNumber;
interestRedirectionAddress: string;
interestRedirectionIndex: BigNumber;
redirectionAddressScaledRedirectedBalance: BigNumber;
scaledRedirectedBalance: BigNumber;
redirectedBalanceIndex: BigNumber;
currentStableDebt: BigNumber;
currentVariableDebt: BigNumber;
principalStableDebt: BigNumber;

View File

@ -12,7 +12,7 @@ BigNumber.config({DECIMAL_PLACES: 0, ROUNDING_MODE: BigNumber.ROUND_DOWN});
const scenarioFolder = './test/helpers/scenarios/';
const selectedScenarios: string[] = ['interest-redirection.json'];
const selectedScenarios: string[] = [];
fs.readdirSync(scenarioFolder).forEach((file) => {
if (selectedScenarios.length > 0 && !selectedScenarios.includes(file)) return;