updated interest redirection

This commit is contained in:
The3D 2020-09-09 19:43:41 +02:00
parent 0f06c3b72e
commit a3934152fe
7 changed files with 103 additions and 64 deletions

View File

@ -31,7 +31,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
mapping(address => address) private _interestRedirectionAddresses;
mapping(address => uint256) private _interestRedirectionIndexes;
mapping(address => uint256) private _redirectedBalances;
mapping(address => uint256) private _scaledRedirectedBalances;
mapping(address => uint256) private _redirectedBalanceIndexes;
mapping(address => address) private _interestRedirectionAllowances;
@ -163,7 +163,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
//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, amount, 0, index);
_updateRedirectedBalanceOfRedirectionAddress(user, user, scaledAmount, 0, index);
emit Mint(user, amount, index);
}
@ -192,16 +192,17 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
* @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 redirectedBalance = _redirectedBalances[user];
uint256 scaledRedirectedBalance = _scaledRedirectedBalances[user];
if (currentScaledBalance == 0 && redirectedBalance == 0) {
if (currentScaledBalance == 0 && scaledRedirectedBalance == 0) {
return 0;
}
uint256 scaledRedirectedBalance = redirectedBalance > 0 ? redirectedBalance.rayDiv(_redirectedBalanceIndexes[user]) : 0;
uint256 actualBalanceRedirectedToUser = scaledRedirectedBalance > 0 ? scaledRedirectedBalance.rayMul(_redirectedBalanceIndexes[user]) : 0;
uint256 index = _pool.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS);
@ -209,16 +210,16 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
//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(scaledRedirectedBalance);
return currentScaledBalance.add(scaledRedirectedBalance).rayMul(index).sub(actualBalanceRedirectedToUser);
}
//if the user is redirecting, his balance only increases by the balance he is being redirected to
uint256 lastRedirectedBalance = currentScaledBalance.rayDiv(_interestRedirectionIndexes[user]);
uint256 lastBalanceRedirectedByUser = currentScaledBalance.rayMul(_interestRedirectionIndexes[user]);
return
lastRedirectedBalance.add(
lastBalanceRedirectedByUser.add(
scaledRedirectedBalance.rayMul(index)
).sub(scaledRedirectedBalance);
).sub(actualBalanceRedirectedToUser);
}
/**
@ -227,7 +228,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
* @param user the address of the user
* @return the scaled balance of the user
**/
function scaledBalanceOf(address user) public override view returns (uint256) {
function scaledBalanceOf(address user) external override view returns (uint256) {
return super.balanceOf(user);
}
@ -288,10 +289,22 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
* @param user address of the user
* @return the total redirected balance
**/
function getRedirectedBalance(address user) external override view returns (uint256) {
return _redirectedBalances[user];
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
@ -313,12 +326,8 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
return;
}
//updating the interest redirection index of the user
_interestRedirectionIndexes[user] = index;
//updating the redirected balance
_redirectedBalances[redirectionAddress] = _redirectedBalances[redirectionAddress]
_scaledRedirectedBalances[redirectionAddress] = _scaledRedirectedBalances[redirectionAddress]
.add(scaledBalanceToAdd)
.sub(scaledBalanceToRemove);
@ -362,6 +371,8 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
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);
@ -374,7 +385,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
//the redirection address we substract the redirected balance of the previous
//recipient
if (currentRedirectionAddress != address(0)) {
_updateRedirectedBalanceOfRedirectionAddress(from, from, 0, scaledBalance, index);
_updateRedirectedBalanceOfRedirectionAddress(from, from, 0, scaledFromBalance, index);
}
//if the user is redirecting the interest back to himself,
@ -390,7 +401,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
_interestRedirectionIndexes[from] = index;
//adds the user balance to the redirected balance of the destination
_updateRedirectedBalanceOfRedirectionAddress(from, from, fromBalance, 0, index);
_updateRedirectedBalanceOfRedirectionAddress(from, from, scaledBalance, 0, index);
emit InterestStreamRedirected(from, to, fromBalance, index);
}
@ -457,7 +468,7 @@ contract AToken is VersionedInitializable, ERC20, IAToken {
//being transferred
_updateRedirectedBalanceOfRedirectionAddress(to, to, scaledAmount, 0, index);
if(scaledBalanceOf(from) == 0){
if(super.balanceOf(from) == 0){
_resetDataOnZeroBalance(from);
}

View File

@ -159,12 +159,20 @@ interface IAToken is IERC20 {
function getUserInterestRedirectionIndex(address user) external view returns (uint256);
/**
* @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.
* @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 getRedirectedBalance(address user) external view returns (uint256);
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
* @param user address of the user
* @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

View File

@ -2483,7 +2483,7 @@ gas used: 6117750
28) AToken: interest rate redirection negative test cases
User 0 tries to redirect the interest to user 2 twice (revert expected):
AssertionError: expected '3000000004839170420641' to be almost equal or equal '3000002810040899373373' for property redirectionAddressRedirectedBalance
AssertionError: expected '3000000004839170420641' to be almost equal or equal '3000002810040899373373' for property redirectionAddressScaledRedirectedBalance
+ expected - actual
-3000000004839170420641
@ -2550,7 +2550,7 @@ gas used: 6117750
32) AToken: interest rate redirection
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:
AssertionError: expected '1020673496610825275870' to be almost equal or equal '1020673496616475023834' for property redirectionAddressRedirectedBalance
AssertionError: expected '1020673496610825275870' to be almost equal or equal '1020673496616475023834' for property redirectionAddressScaledRedirectedBalance
+ expected - actual
-1020673496610825275870

View File

@ -59,7 +59,7 @@ const almostEqualOrEqual = function (
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`);
if (expected[key] == null || !actual[key] == null) {
if (expected[key] == null || actual[key] == null) {
console.log('Found a undefined value for Key ', key, ' value ', expected[key], actual[key]);
}

View File

@ -70,12 +70,14 @@ export const calcExpectedUserDataAfterDeposit = (
expectedUserData.variableBorrowIndex = userDataBeforeAction.variableBorrowIndex;
expectedUserData.walletBalance = userDataBeforeAction.walletBalance.minus(amountDeposited);
expectedUserData.redirectedBalance = userDataBeforeAction.redirectedBalance;
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,
@ -88,10 +90,10 @@ export const calcExpectedUserDataAfterDeposit = (
txTimestamp
);
expectedUserData.redirectionAddressRedirectedBalance = calcExpectedRedirectedBalance(
expectedUserData.redirectionAddressScaledRedirectedBalance = calcExpectedRedirectedBalance(
expectedUserData,
reserveDataAfterAction.liquidityIndex,
userDataBeforeAction.redirectionAddressRedirectedBalance,
userDataBeforeAction.redirectionAddressScaledRedirectedBalance,
new BigNumber(amountDeposited),
new BigNumber(0)
);
@ -161,9 +163,11 @@ export const calcExpectedUserDataAfterWithdraw = (
}
expectedUserData.walletBalance = userDataBeforeAction.walletBalance.plus(amountWithdrawn);
expectedUserData.redirectedBalance = userDataBeforeAction.redirectedBalance;
expectedUserData.scaledRedirectedBalance = userDataBeforeAction.scaledRedirectedBalance;
expectedUserData.redirectedBalanceIndex = userDataBeforeAction.redirectedBalanceIndex;
if (expectedUserData.currentATokenBalance.eq(0) && expectedUserData.redirectedBalance.eq(0)) {
if (expectedUserData.currentATokenBalance.eq(0) && expectedUserData.scaledRedirectedBalance.eq(0)) {
expectedUserData.interestRedirectionAddress = ZERO_ADDRESS;
expectedUserData.interestRedirectionIndex = new BigNumber(0);
} else {
@ -174,10 +178,10 @@ export const calcExpectedUserDataAfterWithdraw = (
: reserveDataAfterAction.liquidityIndex;
}
expectedUserData.redirectionAddressRedirectedBalance = calcExpectedRedirectedBalance(
expectedUserData.redirectionAddressScaledRedirectedBalance = calcExpectedRedirectedBalance(
expectedUserData,
reserveDataAfterAction.liquidityIndex,
userDataBeforeAction.redirectionAddressRedirectedBalance,
userDataBeforeAction.redirectionAddressScaledRedirectedBalance,
new BigNumber(0),
new BigNumber(amountWithdrawn)
);
@ -574,11 +578,12 @@ export const calcExpectedUserDataAfterBorrow = (
currentTimestamp
);
expectedUserData.scaledATokenBalance = userDataBeforeAction.scaledATokenBalance;
expectedUserData.redirectedBalance = userDataBeforeAction.redirectedBalance;
expectedUserData.scaledRedirectedBalance = userDataBeforeAction.scaledRedirectedBalance;
expectedUserData.redirectedBalanceIndex = userDataBeforeAction.redirectedBalanceIndex;
expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress;
expectedUserData.interestRedirectionIndex = userDataBeforeAction.interestRedirectionIndex;
expectedUserData.redirectionAddressRedirectedBalance =
userDataBeforeAction.redirectionAddressRedirectedBalance;
expectedUserData.redirectionAddressScaledRedirectedBalance =
userDataBeforeAction.redirectionAddressScaledRedirectedBalance;
expectedUserData.currentATokenUserIndex = userDataBeforeAction.currentATokenUserIndex;
expectedUserData.walletBalance = userDataBeforeAction.walletBalance.plus(amountBorrowed);
@ -664,11 +669,12 @@ export const calcExpectedUserDataAfterRepay = (
txTimestamp
);
expectedUserData.scaledATokenBalance = userDataBeforeAction.scaledATokenBalance;
expectedUserData.redirectedBalance = userDataBeforeAction.redirectedBalance;
expectedUserData.redirectedBalanceIndex = userDataBeforeAction.redirectedBalanceIndex;
expectedUserData.scaledRedirectedBalance = userDataBeforeAction.scaledRedirectedBalance;
expectedUserData.interestRedirectionAddress = userDataBeforeAction.interestRedirectionAddress;
expectedUserData.interestRedirectionIndex = userDataBeforeAction.interestRedirectionIndex;
expectedUserData.redirectionAddressRedirectedBalance =
userDataBeforeAction.redirectionAddressRedirectedBalance;
expectedUserData.redirectionAddressScaledRedirectedBalance =
userDataBeforeAction.redirectionAddressScaledRedirectedBalance;
expectedUserData.currentATokenUserIndex = userDataBeforeAction.currentATokenUserIndex;
if (user === onBehalfOf) {
@ -996,25 +1002,29 @@ export const calcExpectedUsersDataAfterRedirectInterest = (
);
expectedToData.redirectedBalance = toDataBeforeAction.redirectedBalance.plus(
expectedFromData.currentATokenBalance.rayDiv(index)
expectedToData.scaledRedirectedBalance = toDataBeforeAction.scaledRedirectedBalance.plus(
expectedFromData.scaledATokenBalance
);
expectedToData.redirectedBalanceIndex = index;
if (fromAddress === toAddress) {
expectedFromData.interestRedirectionAddress = ZERO_ADDRESS;
expectedFromData.redirectedBalance = new BigNumber(0);
expectedFromData.redirectionAddressRedirectedBalance = new BigNumber(0);
expectedFromData.scaledRedirectedBalance = new BigNumber(0);
expectedFromData.redirectedBalanceIndex = new BigNumber(0);
expectedFromData.redirectionAddressScaledRedirectedBalance = new BigNumber(0);
expectedToData.interestRedirectionAddress = ZERO_ADDRESS;
expectedToData.redirectedBalance = new BigNumber(0);
expectedToData.redirectionAddressRedirectedBalance = new BigNumber(0);
expectedToData.scaledRedirectedBalance = new BigNumber(0);
expectedToData.redirectionAddressScaledRedirectedBalance = new BigNumber(0);
} else {
expectedFromData.interestRedirectionAddress = toAddress;
expectedFromData.interestRedirectionIndex = index;
expectedFromData.redirectionAddressRedirectedBalance = calcExpectedRedirectedBalance(
expectedFromData.redirectionAddressScaledRedirectedBalance = calcExpectedRedirectedBalance(
expectedFromData,
index,
toDataBeforeAction.redirectedBalance,
toDataBeforeAction.scaledRedirectedBalance,
expectedFromData.currentATokenBalance,
new BigNumber(0)
);
@ -1044,21 +1054,25 @@ const calcExpectedATokenBalance = (
const {
interestRedirectionAddress,
interestRedirectionIndex: redirectionIndexBeforeAction,
redirectedBalance,
scaledRedirectedBalance,
redirectedBalanceIndex: redirectedBalanceIndexBeforeAction,
scaledATokenBalance: scaledBalanceBeforeAction,
} = userDataBeforeAction;
if (scaledBalanceBeforeAction.eq(0) && redirectedBalance.eq(0)) {
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(redirectedBalance).rayMul(index).minus(redirectedBalance);
return scaledBalanceBeforeAction.plus(scaledRedirectedBalance).rayMul(index).minus(actualRedirectedBalance);
}
const lastRedirectedBalance = scaledBalanceBeforeAction.rayDiv(redirectionIndexBeforeAction);
return lastRedirectedBalance.plus(redirectedBalance.rayMul(index).minus(redirectedBalance));
const lastRedirectedBalance = scaledBalanceBeforeAction.rayMul(redirectionIndexBeforeAction);
return lastRedirectedBalance.plus(scaledRedirectedBalance.rayMul(index).minus(actualRedirectedBalance));
};
const calcExpectedRedirectedBalance = (

View File

@ -69,9 +69,10 @@ export const getUserData = async (
]);
const [
redirectedBalance,
scaledRedirectedBalance,
redirectedBalanceIndex,
scaledATokenBalance,
redirectionAddressRedirectedBalance,
redirectionAddressScaledRedirectedBalance,
interestRedirectionAddress,
interestRedirectionIndex,
] = aTokenData;
@ -83,8 +84,9 @@ export const getUserData = async (
scaledATokenBalance: new BigNumber(scaledATokenBalance),
interestRedirectionAddress,
interestRedirectionIndex: new BigNumber(interestRedirectionIndex),
redirectionAddressRedirectedBalance: new BigNumber(redirectionAddressRedirectedBalance),
redirectedBalance: new BigNumber(redirectedBalance),
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,25 +118,28 @@ const getATokenUserData = async (reserve: string, user: string, pool: LendingPoo
const aToken = await getAToken(aTokenAddress);
const [
interestRedirectionAddress,
redirectedBalance,
scaledRedirectedBalance,
redirectedBalanceIndex,
scaledATokenBalance,
interestRedirectionIndex
] = await Promise.all([
aToken.getInterestRedirectionAddress(user),
aToken.getRedirectedBalance(user),
aToken.getScaledRedirectedBalance(user),
aToken.getRedirectedBalanceIndex(user),
aToken.scaledBalanceOf(user),
aToken.getUserInterestRedirectionIndex(user)
]);
const redirectionAddressRedirectedBalance =
const redirectionAddressScaledRedirectedBalance =
interestRedirectionAddress !== ZERO_ADDRESS
? new BigNumber((await aToken.getRedirectedBalance(interestRedirectionAddress)).toString())
? new BigNumber((await aToken.getScaledRedirectedBalance(interestRedirectionAddress)).toString())
: new BigNumber('0');
return [
redirectedBalance.toString(),
scaledRedirectedBalance.toString(),
redirectedBalanceIndex.toString(),
scaledATokenBalance.toString(),
redirectionAddressRedirectedBalance.toString(),
redirectionAddressScaledRedirectedBalance.toString(),
interestRedirectionAddress,
interestRedirectionIndex.toString()
];

View File

@ -5,8 +5,9 @@ export interface UserReserveData {
currentATokenBalance: BigNumber;
interestRedirectionAddress: string;
interestRedirectionIndex: BigNumber;
redirectionAddressRedirectedBalance: BigNumber;
redirectedBalance: BigNumber;
redirectionAddressScaledRedirectedBalance: BigNumber;
scaledRedirectedBalance: BigNumber;
redirectedBalanceIndex: BigNumber;
currentStableDebt: BigNumber;
currentVariableDebt: BigNumber;
principalStableDebt: BigNumber;