Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance rounding for scaled tokens to mitigate minor insolvency #852

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions contracts/protocol/libraries/math/RayMathExplicitRounding.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity ^0.8.10;

enum Rounding {
UP,
DOWN
}

/**
* Simplified version of RayMath that instead of half-up rounding does explicit rounding in a specified direction.
* This is needed to have a 4626 complient implementation, that always predictable rounds in favor of the vault / static a token.
*/
library RayMathExplicitRounding {
uint256 internal constant RAY = 1e27;
uint256 internal constant WAD_RAY_RATIO = 1e9;

function rayMulRoundDown(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0 || b == 0) {
return 0;
}
return (a * b) / RAY;
}

function rayMulRoundUp(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0 || b == 0) {
return 0;
}
return ((a * b) + RAY - 1) / RAY;
}

function rayDivRoundDown(uint256 a, uint256 b) internal pure returns (uint256) {
return (a * RAY) / b;
}

function rayDivRoundUp(uint256 a, uint256 b) internal pure returns (uint256) {
return ((a * RAY) + b - 1) / b;
}

function rayToWadRoundDown(uint256 a) internal pure returns (uint256) {
return a / WAD_RAY_RATIO;
}
}
10 changes: 6 additions & 4 deletions contracts/protocol/tokenization/AToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {SafeCast} from '../../dependencies/openzeppelin/contracts/SafeCast.sol';
import {VersionedInitializable} from '../libraries/aave-upgradeability/VersionedInitializable.sol';
import {Errors} from '../libraries/helpers/Errors.sol';
import {WadRayMath} from '../libraries/math/WadRayMath.sol';
import {RayMathExplicitRounding, Rounding} from '../libraries/math/RayMathExplicitRounding.sol';
import {IPool} from '../../interfaces/IPool.sol';
import {IAToken} from '../../interfaces/IAToken.sol';
import {IAaveIncentivesController} from '../../interfaces/IAaveIncentivesController.sol';
Expand All @@ -22,6 +23,7 @@ import {EIP712Base} from './base/EIP712Base.sol';
*/
contract AToken is VersionedInitializable, ScaledBalanceTokenBase, EIP712Base, IAToken {
using WadRayMath for uint256;
using RayMathExplicitRounding for uint256;
using SafeCast for uint256;
using GPv2SafeERC20 for IERC20;

Expand Down Expand Up @@ -89,7 +91,7 @@ contract AToken is VersionedInitializable, ScaledBalanceTokenBase, EIP712Base, I
uint256 amount,
uint256 index
) external virtual override onlyPool returns (bool) {
return _mintScaled(caller, onBehalfOf, amount, index);
return _mintScaled(caller, onBehalfOf, amount, index, Rounding.DOWN);
}

/// @inheritdoc IAToken
Expand All @@ -99,7 +101,7 @@ contract AToken is VersionedInitializable, ScaledBalanceTokenBase, EIP712Base, I
uint256 amount,
uint256 index
) external virtual override onlyPool {
_burnScaled(from, receiverOfUnderlying, amount, index);
_burnScaled(from, receiverOfUnderlying, amount, index, Rounding.UP);
if (receiverOfUnderlying != address(this)) {
IERC20(_underlyingAsset).safeTransfer(receiverOfUnderlying, amount);
}
Expand All @@ -110,7 +112,7 @@ contract AToken is VersionedInitializable, ScaledBalanceTokenBase, EIP712Base, I
if (amount == 0) {
return;
}
_mintScaled(address(POOL), _treasury, amount, index);
_mintScaled(address(POOL), _treasury, amount, index, Rounding.DOWN);
}

/// @inheritdoc IAToken
Expand All @@ -128,7 +130,7 @@ contract AToken is VersionedInitializable, ScaledBalanceTokenBase, EIP712Base, I
function balanceOf(
address user
) public view virtual override(IncentivizedERC20, IERC20) returns (uint256) {
return super.balanceOf(user).rayMul(POOL.getReserveNormalizedIncome(_underlyingAsset));
return super.balanceOf(user).rayMulRoundDown(POOL.getReserveNormalizedIncome(_underlyingAsset));
}

/// @inheritdoc IERC20
Expand Down
8 changes: 5 additions & 3 deletions contracts/protocol/tokenization/VariableDebtToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {IERC20} from '../../dependencies/openzeppelin/contracts/IERC20.sol';
import {SafeCast} from '../../dependencies/openzeppelin/contracts/SafeCast.sol';
import {VersionedInitializable} from '../libraries/aave-upgradeability/VersionedInitializable.sol';
import {WadRayMath} from '../libraries/math/WadRayMath.sol';
import {RayMathExplicitRounding, Rounding} from '../libraries/math/RayMathExplicitRounding.sol';
import {Errors} from '../libraries/helpers/Errors.sol';
import {IPool} from '../../interfaces/IPool.sol';
import {IAaveIncentivesController} from '../../interfaces/IAaveIncentivesController.sol';
Expand All @@ -23,6 +24,7 @@ import {ScaledBalanceTokenBase} from './base/ScaledBalanceTokenBase.sol';
*/
contract VariableDebtToken is DebtTokenBase, ScaledBalanceTokenBase, IVariableDebtToken {
using WadRayMath for uint256;
using RayMathExplicitRounding for uint256;
using SafeCast for uint256;

uint256 public constant DEBT_TOKEN_REVISION = 0x1;
Expand Down Expand Up @@ -84,7 +86,7 @@ contract VariableDebtToken is DebtTokenBase, ScaledBalanceTokenBase, IVariableDe
return 0;
}

return scaledBalance.rayMul(POOL.getReserveNormalizedVariableDebt(_underlyingAsset));
return scaledBalance.rayMulRoundUp(POOL.getReserveNormalizedVariableDebt(_underlyingAsset));
}

/// @inheritdoc IVariableDebtToken
Expand All @@ -97,7 +99,7 @@ contract VariableDebtToken is DebtTokenBase, ScaledBalanceTokenBase, IVariableDe
if (user != onBehalfOf) {
_decreaseBorrowAllowance(onBehalfOf, user, amount);
}
return (_mintScaled(user, onBehalfOf, amount, index), scaledTotalSupply());
return (_mintScaled(user, onBehalfOf, amount, index, Rounding.UP), scaledTotalSupply());
}

/// @inheritdoc IVariableDebtToken
Expand All @@ -106,7 +108,7 @@ contract VariableDebtToken is DebtTokenBase, ScaledBalanceTokenBase, IVariableDe
uint256 amount,
uint256 index
) external virtual override onlyPool returns (uint256) {
_burnScaled(from, address(0), amount, index);
_burnScaled(from, address(0), amount, index, Rounding.DOWN);
return scaledTotalSupply();
}

Expand Down
29 changes: 25 additions & 4 deletions contracts/protocol/tokenization/base/ScaledBalanceTokenBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity 0.8.10;
import {SafeCast} from '../../../dependencies/openzeppelin/contracts/SafeCast.sol';
import {Errors} from '../../libraries/helpers/Errors.sol';
import {WadRayMath} from '../../libraries/math/WadRayMath.sol';
import {RayMathExplicitRounding, Rounding} from '../../libraries/math/RayMathExplicitRounding.sol';
import {IPool} from '../../../interfaces/IPool.sol';
import {IScaledBalanceToken} from '../../../interfaces/IScaledBalanceToken.sol';
import {MintableIncentivizedERC20} from './MintableIncentivizedERC20.sol';
Expand All @@ -15,6 +16,7 @@ import {MintableIncentivizedERC20} from './MintableIncentivizedERC20.sol';
*/
abstract contract ScaledBalanceTokenBase is MintableIncentivizedERC20, IScaledBalanceToken {
using WadRayMath for uint256;
using RayMathExplicitRounding for uint256;
using SafeCast for uint256;

/**
Expand Down Expand Up @@ -61,15 +63,22 @@ abstract contract ScaledBalanceTokenBase is MintableIncentivizedERC20, IScaledBa
* @param onBehalfOf The address of the user that will receive the scaled tokens
* @param amount The amount of tokens getting minted
* @param index The next liquidity index of the reserve
* @param rounding Rounding up or down when calculating scaled amount
* @return `true` if the the previous balance of the user was 0
*/
function _mintScaled(
address caller,
address onBehalfOf,
uint256 amount,
uint256 index
uint256 index,
Rounding rounding
) internal returns (bool) {
uint256 amountScaled = amount.rayDiv(index);
uint256 amountScaled;
if (rounding == Rounding.UP) {
amountScaled = amount.rayDivRoundUp(index);
} else {
amountScaled = amount.rayDivRoundDown(index);
}
require(amountScaled != 0, Errors.INVALID_MINT_AMOUNT);

uint256 scaledBalance = super.balanceOf(onBehalfOf);
Expand All @@ -95,9 +104,21 @@ abstract contract ScaledBalanceTokenBase is MintableIncentivizedERC20, IScaledBa
* @param target The address that will receive the underlying, if any
* @param amount The amount getting burned
* @param index The variable debt index of the reserve
* @param rounding Rounding up or down when calculating scaled amount
*/
function _burnScaled(address user, address target, uint256 amount, uint256 index) internal {
uint256 amountScaled = amount.rayDiv(index);
function _burnScaled(
address user,
address target,
uint256 amount,
uint256 index,
Rounding rounding
) internal {
uint256 amountScaled;
if (rounding == Rounding.UP) {
amountScaled = amount.rayDivRoundUp(index);
} else {
amountScaled = amount.rayDivRoundDown(index);
}
require(amountScaled != 0, Errors.INVALID_BURN_AMOUNT);

uint256 scaledBalance = super.balanceOf(user);
Expand Down