diff --git a/contracts/tokenization/AToken.sol b/contracts/tokenization/AToken.sol index 2a810641..0161b149 100644 --- a/contracts/tokenization/AToken.sol +++ b/contracts/tokenization/AToken.sol @@ -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); } diff --git a/contracts/tokenization/interfaces/IAToken.sol b/contracts/tokenization/interfaces/IAToken.sol index 8250e242..68b70e60 100644 --- a/contracts/tokenization/interfaces/IAToken.sol +++ b/contracts/tokenization/interfaces/IAToken.sol @@ -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 diff --git a/test.log b/test.log index 64ab456c..73e89e84 100644 --- a/test.log +++ b/test.log @@ -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 diff --git a/test/helpers/actions.ts b/test/helpers/actions.ts index 2a84fa66..6e654349 100644 --- a/test/helpers/actions.ts +++ b/test/helpers/actions.ts @@ -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]); } diff --git a/test/helpers/utils/calculations.ts b/test/helpers/utils/calculations.ts index 14a3a1f5..c8b3e1d4 100644 --- a/test/helpers/utils/calculations.ts +++ b/test/helpers/utils/calculations.ts @@ -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 = ( diff --git a/test/helpers/utils/helpers.ts b/test/helpers/utils/helpers.ts index ccd7d1d1..008b904e 100644 --- a/test/helpers/utils/helpers.ts +++ b/test/helpers/utils/helpers.ts @@ -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() ]; diff --git a/test/helpers/utils/interfaces/index.ts b/test/helpers/utils/interfaces/index.ts index d64d8457..7aa89910 100644 --- a/test/helpers/utils/interfaces/index.ts +++ b/test/helpers/utils/interfaces/index.ts @@ -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;