diff --git a/contracts/interfaces/IAToken.sol b/contracts/interfaces/IAToken.sol index cf0ea261..3a6ac663 100644 --- a/contracts/interfaces/IAToken.sol +++ b/contracts/interfaces/IAToken.sol @@ -99,9 +99,4 @@ interface IAToken is IERC20, IScaledBalanceToken, IInitializableAToken { * @dev Returns the address of the incentives controller contract **/ function getIncentivesController() external view returns (IAaveIncentivesController); - - /** - * @dev Returns the address of the underlying asset of this aToken (E.g. WETH for aWETH) - **/ - function UNDERLYING_ASSET_ADDRESS() external view returns (address); } diff --git a/contracts/interfaces/IRewardsAwareAToken.sol b/contracts/interfaces/IRewardsAwareAToken.sol new file mode 100644 index 00000000..53b1af80 --- /dev/null +++ b/contracts/interfaces/IRewardsAwareAToken.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; + +import {IAToken} from './IAToken.sol'; + +interface IRewardsAwareAToken is IAToken { + /** + * @dev Emitted after the update rewards state + * @param from The address which can claim rewards + * @param token The reward token address + * @param claimable The amount available to claim + **/ + event UserRewardSnapshot(address indexed from, address indexed token, uint256 claimable); + + /** + * @dev Emitted after the claim action + * @param from The address performing the claim + * @param token The reward token address + * @param claimed The amount claimed + * @param rewards The rewards amount, this can differ from claimed rewards if is staked + **/ + event Claim(address indexed from, address indexed token, uint256 claimed, uint256 rewards); + + /** + * @dev Emitted after the update of the user index + * @param from The address with updated user index + * @param token The reward token address + * @param newIndex The latest user index + **/ + event UserIndexUpdate(address indexed from, address indexed token, uint256 newIndex); + + /** + * @dev Emitted after the update of the reward token index + * @param token The reward token address + * @param newIndex The amount available to claim + **/ + event RewardIndexUpdate(address indexed token, uint256 newIndex); + + /** + * @dev Emitted after the update of the rewards reserve factor + * @param reserveFactor the new reserve factor + **/ + event RewardsReserveFactorUpdate(uint256 reserveFactor); + + /** + * @dev Get the current claimable rewards dinamically by calling the external rewards contract and simulate the rewards without storage + * @param token Address of the rewards token + * @param user Address of the account to get current claimable rewards + * @return The claimable rewards of the address passed at the "user" argument + */ + function getClaimableRewards(address token, address user) external view returns (uint256); + + /** + * @dev Get the total lifetime rewards of an address from contract storage + * @param token Address of the rewards token + * @param user Address of the account to get the total lifetime rewards + * @return The total lifetime rewards of an address, this includes claimed and pending rewards + */ + function getUserRewardsAccrued(address token, address user) external view returns (uint256); + + /** + * @dev Get the claimed rewards of an address from contract storage + * @param token Address of the rewards token + * @param user Address of the account to get the claimed rewards + * @return The claimed rewards of an address + */ + function getUserClaimedRewards(address token, address user) external view returns (uint256); + + /** + * @dev Get the total lifetime rewards of the aToken contract itself, from contract storage + * @param token Address of the rewards token + * @return The total lifetime rewards, this includes claimed and pending rewards from the aToken contract + */ + function getLifetimeRewards(address token) external view returns (uint256); + + /** + * @dev Get lifetime minted rewards of a rewards token + * @param token Address of the rewards token + * @return The lifetime rewards claimed + */ + function getLifetimeClaimed(address token) external view returns (uint256); + + /** + * @dev Get the user checkpoint of the aToken contract itself, from contract storage + * @param token Address of the rewards token + * @param user Address of the account to get the claimed rewards + * @return The total lifetime rewards, this includes claimed and pending rewards from the aToken contract + */ + function getUserIndex(address token, address user) external view returns (uint256); + + /** + * @dev Get the rewards ERC20 token address by index position + * @param index The position of the rewards, starting from zero up + * @return The rewards ERC20 token address + */ + function getRewardsTokenAddress(uint256 index) external view returns (address); + + /** + * @dev Get all the rewards token addresses + * @return The list of rewards token addresseses + */ + function getRewardsTokenAddressList() external view returns (address[9] memory); + + /** + * @dev Return the rewards reserve factor to the treasury + * @return reserve factor in percent value + */ + function getRewardsReserveFactor() external view returns (uint256); + + /** + * @dev Claim the available reward from the caller and transfers to `msg.sender` + */ + function claim(address token) external; + + /** + * @dev Set the rewards reserve factor to the treasury, only allowed by LendingPoolAddressesProvider pool admin + * @param reserveFactor reserve factor in percent value + */ + function setRewardsReserveFactor(uint256 reserveFactor) external; +} diff --git a/contracts/mocks/tokens/RewardsATokenMock.sol b/contracts/mocks/tokens/RewardsATokenMock.sol new file mode 100644 index 00000000..0ba31555 --- /dev/null +++ b/contracts/mocks/tokens/RewardsATokenMock.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; + +import {IAaveIncentivesController} from '../../interfaces/IAaveIncentivesController.sol'; +import {ILendingPool} from '../../interfaces/ILendingPool.sol'; +import {RewardsAwareAToken} from '../../protocol/tokenization/RewardsAwareAToken.sol'; +import {RewardsToken} from './RewardsToken.sol'; +import {SafeERC20} from '../../dependencies/openzeppelin/contracts/SafeERC20.sol'; +import {IERC20} from '../../dependencies/openzeppelin/contracts/IERC20.sol'; + +/* + * @dev A Simple mockup of RewardsAwareAToken that manages only one reward token. The token have a constant emission per second. + * In this scenario, the rewards ERC20 token is also the Rewards Controller for simplicity. + */ +contract RewardsATokenMock is RewardsAwareAToken { + RewardsToken public _rewardsController; + + /** + * @dev Initializes the aToken + * @param pool The address of the lending pool where this aToken will be used + * @param treasury The address of the Aave treasury, receiving the fees on this aToken + * @param underlyingAsset The address of the underlying asset of this aToken (E.g. WETH for aWETH) + * @param incentivesController The smart contract managing potential incentives distribution + * @param aTokenDecimals The decimals of the aToken, same as the underlying asset's + * @param aTokenName The name of the aToken + * @param aTokenSymbol The symbol of the aToken + */ + function initialize( + ILendingPool pool, + address treasury, + address underlyingAsset, + IAaveIncentivesController incentivesController, + uint8 aTokenDecimals, + string calldata aTokenName, + string calldata aTokenSymbol, + bytes calldata params + ) external virtual override initializer { + uint256 chainId; + + //solium-disable-next-line + assembly { + chainId := chainid() + } + + DOMAIN_SEPARATOR = keccak256( + abi.encode( + EIP712_DOMAIN, + keccak256(bytes(aTokenName)), + keccak256(EIP712_REVISION), + chainId, + address(this) + ) + ); + + _setName(aTokenName); + _setSymbol(aTokenSymbol); + _setDecimals(aTokenDecimals); + + _pool = pool; + _treasury = treasury; + _underlyingAsset = underlyingAsset; + _incentivesController = incentivesController; + + // Specific RewardAToken initialization + _rewardsController = RewardsToken(underlyingAsset); + _rewardTokens[0] = underlyingAsset; + + emit Initialized( + underlyingAsset, + address(pool), + treasury, + address(incentivesController), + aTokenDecimals, + aTokenName, + aTokenSymbol, + params + ); + } + + /** + * @dev Due this aToken only holds one reward, is not needed to differentiate the input token + */ + function _computeExternalLifetimeRewards(address token) internal override returns (uint256) { + return _getExternalLifetimeRewards(token); + } + + /** + * @dev Due this aToken only holds one reward, is not needed to differentiate the input token + */ + function _getExternalLifetimeRewards(address token) internal view override returns (uint256) { + require(token == address(_rewardsController), 'Reward token mismatch'); + + uint256 externalLifetime = _rewardsController.getLifetimeRewards(address(this)); + + return (externalLifetime); + } + + /** + * @dev Due this aToken only holds one reward, is not needed to differentiate the input token + */ + function _claimRewardsFromController() internal override { + _rewardsController.claimRewards(); + } +} diff --git a/contracts/mocks/tokens/RewardsToken.sol b/contracts/mocks/tokens/RewardsToken.sol new file mode 100644 index 00000000..31329265 --- /dev/null +++ b/contracts/mocks/tokens/RewardsToken.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; + +import {ERC20} from '../../dependencies/openzeppelin/contracts/ERC20.sol'; +import {WadRayMath} from '../../protocol/libraries/math/WadRayMath.sol'; +import {PercentageMath} from '../../protocol/libraries/math/PercentageMath.sol'; + +/* + * @dev Mocked token with linear distribution via emission by time distributed to token holders + */ +contract RewardsToken is ERC20 { + using WadRayMath for uint256; + using PercentageMath for uint256; + + uint256 private constant EMISION_PER_SECOND = 1 ether; + uint256 public immutable INIT_TIMESTAMP; + + uint256 internal lifetimeMintable; + + mapping(address => uint256) userMinted; + mapping(address => uint256) userLifetimeRewards; + mapping(address => uint256) userCheckpoint; + + constructor() public ERC20('Rewards', 'REW') { + INIT_TIMESTAMP = block.timestamp; + } + + /* + * @dev Get accumulated rewards of a user between + */ + function getAccruedRewards(address user, uint256 totalMintable) internal view returns (uint256) { + uint256 balance = balanceOf(user); + if (balance == 0) return 0; + + return balance.rayDiv(this.totalSupply()).rayMul(totalMintable.sub(userCheckpoint[user])); + } + + /* + * @dev Update lifetimeMintable state and user lifeTimeRewards + */ + function updateMintableEmission(address user) public { + if (user == address(0)) return; + + lifetimeMintable = (block.timestamp.sub(INIT_TIMESTAMP)).mul(EMISION_PER_SECOND); + userLifetimeRewards[user] = userLifetimeRewards[user].add( + getAccruedRewards(user, lifetimeMintable) + ); + userCheckpoint[user] = lifetimeMintable; + } + + /* + * @dev Claim rewards from the extra emission by time + */ + function claimRewards() public returns (bool) { + updateMintableEmission(msg.sender); + uint256 claimableRewards = userLifetimeRewards[msg.sender].sub(userMinted[msg.sender]); + userMinted[msg.sender] = claimableRewards.add(userMinted[msg.sender]); + _mint(msg.sender, claimableRewards); + return true; + } + + /* + * @dev Getter for retrieving the expected claimable rewards + */ + function getLifetimeRewards(address user) public view returns (uint256) { + uint256 totalMintable = (block.timestamp.sub(INIT_TIMESTAMP)).mul(EMISION_PER_SECOND); + + return userLifetimeRewards[user].add(getAccruedRewards(user, totalMintable)); + } + + /* + * @dev Getter for retrieving the expected claimable rewards + */ + function getClaimableRewards(address user) external view returns (uint256) { + return getLifetimeRewards(user).sub(userMinted[user]); + } + + /* + * @dev Mint an arbitrary amount of REW to the msg.sender and start rewards to msg.sender + */ + function mint(uint256 value) external returns (bool) { + _mint(msg.sender, value); + return true; + } + + /* + * @dev Update user distribution state at transfer/mint hook + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 + ) internal override { + updateMintableEmission(from); + updateMintableEmission(to); + } +} diff --git a/contracts/protocol/lendingpool/DefaultReserveInterestRateStrategy.sol b/contracts/protocol/lendingpool/DefaultReserveInterestRateStrategy.sol index 7b321d0c..de926822 100644 --- a/contracts/protocol/lendingpool/DefaultReserveInterestRateStrategy.sol +++ b/contracts/protocol/lendingpool/DefaultReserveInterestRateStrategy.sol @@ -120,6 +120,7 @@ contract DefaultReserveInterestRateStrategy is IReserveInterestRateStrategy { ) external view + virtual override returns ( uint256, @@ -172,6 +173,7 @@ contract DefaultReserveInterestRateStrategy is IReserveInterestRateStrategy { ) public view + virtual override returns ( uint256, diff --git a/contracts/protocol/libraries/helpers/Errors.sol b/contracts/protocol/libraries/helpers/Errors.sol index 8756d797..79f4e2b0 100644 --- a/contracts/protocol/libraries/helpers/Errors.sol +++ b/contracts/protocol/libraries/helpers/Errors.sol @@ -103,6 +103,7 @@ library Errors { string public constant LP_NOT_CONTRACT = '78'; string public constant SDT_STABLE_DEBT_OVERFLOW = '79'; string public constant SDT_BURN_EXCEEDS_BALANCE = '80'; + string public constant AT_CALLER_MUST_BE_POOL_ADMIN = '81'; enum CollateralManagerErrors { NO_ERROR, diff --git a/contracts/protocol/tokenization/AToken.sol b/contracts/protocol/tokenization/AToken.sol index 46aae19f..6f0764db 100644 --- a/contracts/protocol/tokenization/AToken.sol +++ b/contracts/protocol/tokenization/AToken.sol @@ -70,7 +70,7 @@ contract AToken is string calldata aTokenName, string calldata aTokenSymbol, bytes calldata params - ) external override initializer { + ) external virtual override initializer { uint256 chainId; //solium-disable-next-line @@ -122,7 +122,7 @@ contract AToken is address receiverOfUnderlying, uint256 amount, uint256 index - ) external override onlyLendingPool { + ) public virtual override onlyLendingPool { uint256 amountScaled = amount.rayDiv(index); require(amountScaled != 0, Errors.CT_INVALID_BURN_AMOUNT); _burn(user, amountScaled); @@ -145,7 +145,7 @@ contract AToken is address user, uint256 amount, uint256 index - ) external override onlyLendingPool returns (bool) { + ) public virtual override onlyLendingPool returns (bool) { uint256 previousBalance = super.balanceOf(user); uint256 amountScaled = amount.rayDiv(index); @@ -273,7 +273,7 @@ contract AToken is /** * @dev Returns the address of the underlying asset of this aToken (E.g. WETH for aWETH) **/ - function UNDERLYING_ASSET_ADDRESS() public override view returns (address) { + function UNDERLYING_ASSET_ADDRESS() public view returns (address) { return _underlyingAsset; } @@ -307,6 +307,7 @@ contract AToken is **/ function transferUnderlyingTo(address target, uint256 amount) external + virtual override onlyLendingPool returns (uint256) @@ -320,7 +321,12 @@ contract AToken is * @param user The user executing the repayment * @param amount The amount getting repaid **/ - function handleRepayment(address user, uint256 amount) external override onlyLendingPool {} + function handleRepayment(address user, uint256 amount) + external + virtual + override + onlyLendingPool + {} /** * @dev implements the permit function as for diff --git a/contracts/protocol/tokenization/RewardsAwareAToken.sol b/contracts/protocol/tokenization/RewardsAwareAToken.sol new file mode 100644 index 00000000..32775d35 --- /dev/null +++ b/contracts/protocol/tokenization/RewardsAwareAToken.sol @@ -0,0 +1,515 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; + +import {ILendingPool} from '../../interfaces/ILendingPool.sol'; +import {ILendingPoolAddressesProvider} from '../../interfaces/ILendingPoolAddressesProvider.sol'; +import {IDelegationToken} from '../../interfaces/IDelegationToken.sol'; +import {Errors} from '../libraries/helpers/Errors.sol'; +import {AToken} from './AToken.sol'; +import {SafeERC20} from '../../dependencies/openzeppelin/contracts/SafeERC20.sol'; +import {IERC20} from '../../dependencies/openzeppelin/contracts/IERC20.sol'; +import {IRewardsAwareAToken} from '../../interfaces/IRewardsAwareAToken.sol'; +import {IAToken} from '../../interfaces/IRewardsAwareAToken.sol'; +import {WadRayMath} from '../../protocol/libraries/math/WadRayMath.sol'; +import {PercentageMath} from '../../protocol/libraries/math/PercentageMath.sol'; + +/** + * @title Rewards Aware AToken + * @notice AToken aware to claim and distribute rewards from an external rewards controller. + * @author Aave + */ +abstract contract RewardsAwareAToken is AToken, IRewardsAwareAToken { + using SafeERC20 for IERC20; + using WadRayMath for uint256; + using PercentageMath for uint256; + + // Precision of the multiplier for calculating distribution percentages + uint256 public constant PRECISION = 24; + // Max rewards allowed + uint256 public constant MAX_REWARD_TOKENS = 9; + // Multiplier for calculating share indexes + uint256 public constant MULTIPLIER = 10**PRECISION; + // Max rewards reserve factor + uint256 public constant MAX_VALID_RESERVE_FACTOR = 10000; + // Rewards Reserve Factor + uint256 internal rewardsReserveFactor; + + // token => user => userIndex + mapping(address => mapping(address => uint256)) private _userIndex; + // token => user => userRewardsAccrued + mapping(address => mapping(address => uint256)) private _userRewardsAccrued; + // token => user => userRewardsClaimed + mapping(address => mapping(address => uint256)) private _userRewardsClaimed; + + // token => rewardIndex + mapping(address => uint256) private _rewardIndex; + // reward address => lifetime rewards + mapping(address => uint256) private _lifetimeRewards; + // token => lifetimeMinted + mapping(address => uint256) private _lifetimeClaimed; + // reward tokens + address[MAX_REWARD_TOKENS] internal _rewardTokens; + + modifier onlyPoolAdmin { + address poolAdmin = + ILendingPoolAddressesProvider(ILendingPool(_pool).getAddressesProvider()).getPoolAdmin(); + require(_msgSender() == poolAdmin, Errors.AT_CALLER_MUST_BE_POOL_ADMIN); + _; + } + + /** + * @dev Get the current claimable rewards dinamically by calling the external rewards contract and simulate the rewards without storage + * @param token Address of the rewards token + * @param user Address of the account to get current claimable rewards + * @return The claimable rewards of the address passed at the "user" argument + */ + function getClaimableRewards(address token, address user) public view override returns (uint256) { + return _getPendingRewards(token, user); + } + + /** + * @dev Get the total lifetime rewards of an address from contract storage + * @param token Address of the rewards token + * @param user Address of the account to get the total lifetime rewards + * @return The total lifetime rewards of an address, this includes claimed and pending rewards + */ + function getUserRewardsAccrued(address token, address user) + external + view + override + returns (uint256) + { + return _userRewardsAccrued[token][user]; + } + + /** + * @dev Get the claimed rewards of an address from contract storage + * @param token Address of the rewards token + * @param user Address of the account to get the claimed rewards + * @return The claimed rewards of an address + */ + function getUserClaimedRewards(address token, address user) + external + view + override + returns (uint256) + { + return _userRewardsClaimed[token][user]; + } + + /** + * @dev Get lifetime rewards of a rewards token + * @param token Address of the rewards token + * @return The total of lifetime rewards + */ + function getLifetimeRewards(address token) external view override returns (uint256) { + return _getLifetimeRewards(token); + } + + /** + * @dev Get lifetime minted rewards of a rewards token + * @param token Address of the rewards token + * @return The lifetime rewards claimed + */ + function getLifetimeClaimed(address token) external view override returns (uint256) { + return _lifetimeClaimed[token]; + } + + /** + * @dev Get the user checkpoint of the aToken contract itself, from contract storage + * @param token Address of the rewards token + * @param user Address of the account to get the claimed rewards + * @return The total lifetime rewards, this includes claimed and pending rewards from the aToken contract + */ + function getUserIndex(address token, address user) external view override returns (uint256) { + return _userIndex[token][user]; + } + + /** + * @dev Get the rewards ERC20 token address by index position + * @param index The position of the rewards, starting from zero up + * @return The rewards ERC20 token address + */ + function getRewardsTokenAddress(uint256 index) external view override returns (address) { + return _getRewardsTokenAddress(index); + } + + /** + * @dev Get all the rewards token addresses + * @return The list of rewards token addresseses + */ + function getRewardsTokenAddressList() + external + view + override + returns (address[MAX_REWARD_TOKENS] memory) + { + return _rewardTokens; + } + + /** + * @dev Claim the available token rewards from the caller and transfers to `msg.sender` + */ + function claim(address token) external override { + _claim(token); + } + + /** + * @dev Burns aTokens from `user` and sends the equivalent amount of underlying to `receiverOfUnderlying` + * - Only callable by the LendingPool, as extra state updates there need to be managed + * @param user The owner of the aTokens, getting them burned + * @param receiverOfUnderlying The address that will receive the underlying + * @param amount The amount being burned + * @param index The new liquidity index of the reserve + **/ + function burn( + address user, + address receiverOfUnderlying, + uint256 amount, + uint256 index + ) public virtual override(AToken, IAToken) onlyLendingPool { + // Unstake + _unstake(UNDERLYING_ASSET_ADDRESS(), amount); + + // Update distribution of rewards + _updateDistribution(user); + + // burns aTokens + return super.burn(user, receiverOfUnderlying, amount, index); + } + + /** + * @dev Mints `amount` aTokens to `user` + * - Only callable by the LendingPool, as extra state updates there need to be managed + * @param user The address receiving the minted tokens + * @param amount The amount of tokens getting minted + * @param index The new liquidity index of the reserve + * @return `true` if the the previous balance of the user was 0 + */ + function mint( + address user, + uint256 amount, + uint256 index + ) public virtual override(AToken, IAToken) onlyLendingPool returns (bool) { + // Stake + _stake(UNDERLYING_ASSET_ADDRESS(), amount); + + // Update distribution of rewards + _updateDistribution(user); + + // mint aTokens + return super.mint(user, amount, index); + } + + /** + * @dev Transfers the underlying asset to `target`. Used by the LendingPool to transfer + * assets in borrow(), withdraw() and flashLoan() + * @param target The recipient of the aTokens + * @param amount The amount getting transferred + * @return The amount transferred + **/ + function transferUnderlyingTo(address target, uint256 amount) + external + virtual + override(AToken, IAToken) + onlyLendingPool + returns (uint256) + { + _unstake(UNDERLYING_ASSET_ADDRESS(), amount); + _updateDistribution(target); + + IERC20(UNDERLYING_ASSET_ADDRESS()).safeTransfer(target, amount); + return amount; + } + + /** + * @dev Invoked to execute actions on the aToken side after a repayment. + * @param user The user executing the repayment + * @param amount The amount getting repaid + **/ + function handleRepayment(address user, uint256 amount) + external + virtual + override(AToken, IAToken) + onlyLendingPool + { + _stake(UNDERLYING_ASSET_ADDRESS(), amount); + _updateDistribution(user); + } + + /** + * @dev Set the rewards reserve factor to the treasury, only allowed by LendingPoolAddressesProvider pool admin + * @param reserveFactor reserve factor in percent value + */ + function setRewardsReserveFactor(uint256 reserveFactor) external override onlyPoolAdmin { + _setRewardsReserveFactor(reserveFactor); + } + + /** + * @dev Return the rewards reserve factor to the treasury + * @return reserve factor in percent value + */ + function getRewardsReserveFactor() external view override returns (uint256) { + return rewardsReserveFactor; + } + + /** + * @dev Hook to update distributions before token transfer. Called at "burn" and "transferUnderlyingTo". + * @param from address of the `from` + * @param to address of the `to` + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 + ) internal override { + _updateDistribution(from); + _updateDistribution(to); + } + + /** + * @dev Get lifetime rewards of a rewards token + * @param token Address of the rewards token + * @return The total of lifetime rewards + */ + function _getLifetimeRewards(address token) internal view returns (uint256) { + return _lifetimeRewards[token]; + } + + /** + * @dev Get the rewards ERC20 token address by index position + * @param index The position of the rewards, starting from zero up + * @return The rewards ERC20 token address + */ + function _getRewardsTokenAddress(uint256 index) internal view returns (address) { + if (index > MAX_REWARD_TOKENS) return address(0); + + return _rewardTokens[index]; + } + + /** + * @dev Calculate rewards distribution in proportion of aTokens balance during a timeframe. + * @param aTokensBalance The amounts of aTokens of the user + * @param lastRewardIndex The last reward token index of the distribution percentage + * @param lastUserIndex The last user index of the distribution percentage + * @return The proportional rewards between two distribution percentages indexes + */ + function _getRewards( + uint256 aTokensBalance, + uint256 lastRewardIndex, + uint256 lastUserIndex + ) internal pure returns (uint256) { + return aTokensBalance.mul(lastRewardIndex.sub(lastUserIndex)).div(MULTIPLIER); + } + + /** + * @dev Calculate the next reward token index, that contains the sums of the percentages of the distribution of the token rewards. + * @param latestRewardIndex The latest reward token index + * @param accruedRewards The accrued rewards during the current distribution + * @param aTokenSupply The total supply of this aToken contract + * @return The sums of the reward indexes plus the latest percentage distribution index of the reward + */ + function _getRewardIndex( + uint256 latestRewardIndex, + uint256 accruedRewards, + uint256 aTokenSupply + ) internal pure returns (uint256) { + return latestRewardIndex.add(accruedRewards.mul(MULTIPLIER).div(aTokenSupply)); + } + + /** + * @dev Getter for virtually calculating the pending rewards of an user without altering the storage of the contract + * @param user Address of the user to lookup for rewards + * @param token The rewards token to lookup for user rewards + * @return Expected token rewards for an user + */ + function _getPendingRewards(address token, address user) internal view returns (uint256) { + uint256 aTokenTotalSupply = totalSupply(); + uint256 externalLifetimeRewards = _getExternalLifetimeRewards(token); + uint256 rewardAccrued = externalLifetimeRewards.sub(_lifetimeRewards[token]); + uint256 rewardAssetIndex = _rewardIndex[token]; + + if (aTokenTotalSupply != 0 && rewardAccrued != 0) { + rewardAssetIndex = _getRewardIndex(_rewardIndex[token], rewardAccrued, aTokenTotalSupply); + } + + uint256 userLatestRewards = + _getRewards(balanceOf(user), rewardAssetIndex, _userIndex[token][user]); + + return + _userRewardsAccrued[token][user].add(userLatestRewards).sub(_userRewardsClaimed[token][user]); + } + + /** + * @dev Retrieve the latest rewards distribution to an user and update the distribution of a token rewards at storage + * @param user The `user` address to update the rewards index and retrieve latest reward distribution + * @param token The reward token to lookup the distribution + * @param aTokenBalance The aToken balance of the user determines his share during the distribution + * @param aTokenSupply The current total supply of this aToken + */ + function _updateRewardDistribution( + address user, + address token, + uint256 aTokenBalance, + uint256 aTokenSupply + ) private { + uint256 userRewardsIndex = _userIndex[token][user]; + uint256 externalLifetimeRewards = _computeExternalLifetimeRewards(token); + uint256 rewardAccrued = externalLifetimeRewards.sub(_lifetimeRewards[token]); + uint256 previousRewardIndex = _rewardIndex[token]; + uint256 rewardIndex = _getRewardIndex(previousRewardIndex, rewardAccrued, aTokenSupply); + + // Check reward index + if (rewardIndex != previousRewardIndex) { + _rewardIndex[token] = rewardIndex; + _setLifetimeRewards(token, externalLifetimeRewards); + emit RewardIndexUpdate(token, rewardIndex); + } + + // Check user reward index + if (userRewardsIndex != rewardIndex) { + _userIndex[token][user] = rewardIndex; + emit UserIndexUpdate(user, token, rewardIndex); + + uint256 userRewardAccrued = _getRewards(aTokenBalance, rewardIndex, userRewardsIndex); + if (userRewardAccrued != 0) { + _userRewardsAccrued[token][user] = _userRewardsAccrued[token][user].add(userRewardAccrued); + emit UserRewardSnapshot(user, token, userRewardAccrued); + } + } + } + + /** + * @dev Update the distribution of each rewards configured at this aToken + * @param user The `user` address to update the rewards index and retrieve latest reward distribution + */ + function _updateDistribution(address user) internal { + uint256 aTokenBalance = balanceOf(user); + if (aTokenBalance == 0) { + return; + } + + uint256 aTokenSupply = totalSupply(); + if (aTokenSupply == 0) { + return; + } + + for (uint256 index; index < MAX_REWARD_TOKENS; index++) { + address rewardToken = _rewardTokens[index]; + if (rewardToken == address(0)) break; + + _updateRewardDistribution(user, rewardToken, aTokenBalance, aTokenSupply); + } + } + + /** + * @dev Set token rewards claimed from an `user` address + * @param user The `user` address to sum and set the claimed rewards + * @param token The claimed reward `token` address + * @param claimed The amount of `claimed` rewards by the `user` + */ + function _setUserRewardsClaimed( + address user, + address token, + uint256 claimed + ) private { + _userRewardsClaimed[token][user] = _userRewardsClaimed[token][user].add(claimed); + _lifetimeClaimed[token] = _lifetimeClaimed[token].add(claimed); + } + + /** + * @dev Claim the available rewards from the caller and transfers to `msg.sender` + * @param token Reward adresss to lookup rewards and claim + */ + function _claim(address token) private { + // Mint the available rewards from the external rewards controller to this aToken contract implementation + _claimRewardsFromController(); + + // Update aToken and user rewards + _updateDistribution(msg.sender); + + // Get the remaining tokens to claim + uint256 accruedRewards = + _userRewardsAccrued[token][msg.sender].sub(_userRewardsClaimed[token][msg.sender]); + + if (accruedRewards > 0) { + // Track the claimed rewards + _setUserRewardsClaimed(msg.sender, token, accruedRewards); + // Unstake reward token, if needed + uint256 unstaked = _unstake(token, accruedRewards); + uint256 userRewards = unstaked; + + // Transfer rewards to treasury + if (_treasury != address(0)) { + uint256 reserveRewards = unstaked.percentMul(rewardsReserveFactor); + if (reserveRewards > 0) { + userRewards = unstaked.sub(reserveRewards); + IERC20(token).transfer(_treasury, reserveRewards); + } + } + + // Transfer rewards to user + IERC20(token).transfer(msg.sender, userRewards); + emit Claim(msg.sender, token, accruedRewards, userRewards); + } + } + + /** + * @dev Set the lifetime token lifetime rewards + * @param token The reward token address + * @param amount The amount of lifetime rewards + */ + function _setLifetimeRewards(address token, uint256 amount) private { + _lifetimeRewards[token] = amount; + } + + /** + * @dev External call to retrieve the lifetime rewards of the aToken contract to the external Rewards Controller contract + * @notice Should only mutate the state outside RewardsAwareAToken abstract contract + * @param token Reward adresss to lookup available rewards + * @notice To be implemented by the contract that inherits this abstract contract RewardsAwareAToken + */ + function _computeExternalLifetimeRewards(address token) internal virtual returns (uint256); + + /** + * @dev External view call to retrieve the lifetime rewards of the aToken contract to the external Rewards Controller contract + * @param token Reward adresss to lookup available rewards + * @notice To be implemented by the contract that inherits this abstract contract RewardsAwareAToken + */ + function _getExternalLifetimeRewards(address token) internal view virtual returns (uint256); + + /** + * @dev External call to claim the lifetime accrued rewards of the aToken contract to the external Rewards Controller contract + * @notice To be implemented by the contract that inherits this abstract contract RewardsAwareAToken + * @notice WIP pending to check virtual balance versus claimed + */ + function _claimRewardsFromController() internal virtual; + + /** + * @dev External call to stake a token of the aToken contract + * @notice Optional, to be implemented by the contract that inherits this abstract contract RewardsAwareAToken,if needed. + * @param amount to stake + */ + function _stake(address, uint256 amount) internal virtual returns (uint256) { + return amount; + } + + /** + * @dev External call to unstake a token of the aToken contract + * @notice Optional, to be implemented by the contract that inherits this abstract contract RewardsAwareAToken, if needed. + * @param amount to unstake + */ + function _unstake(address, uint256 amount) internal virtual returns (uint256) { + return amount; + } + + /** + * @dev Set the rewards reserve factor to the treasury + * @param reserveFactor reserve factor in percent value + */ + function _setRewardsReserveFactor(uint256 reserveFactor) internal { + require(reserveFactor <= MAX_VALID_RESERVE_FACTOR, Errors.RC_INVALID_RESERVE_FACTOR); + rewardsReserveFactor = reserveFactor; + emit RewardsReserveFactorUpdate(rewardsReserveFactor); + } +} diff --git a/package-lock.json b/package-lock.json index 9b6c4f40..accf7a09 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3123,6 +3123,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, "requires": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -5534,7 +5535,8 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true }, "functional-red-black-tree": { "version": "1.0.1", @@ -6373,6 +6375,36 @@ "@ethersproject/strings": ">=5.0.0-beta.130" } }, + "@ethersproject/abstract-provider": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.0.8.tgz", + "integrity": "sha512-fqJXkewcGdi8LogKMgRyzc/Ls2js07yor7+g9KfPs09uPOcQLg7cc34JN+lk34HH9gg2HU0DIA5797ZR8znkfw==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/bignumber": "^5.0.13", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/networks": "^5.0.7", + "@ethersproject/properties": "^5.0.7", + "@ethersproject/transactions": "^5.0.9", + "@ethersproject/web": "^5.0.12" + } + }, + "@ethersproject/abstract-signer": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.0.10.tgz", + "integrity": "sha512-irx7kH7FDAeW7QChDPW19WsxqeB1d3XLyOLSXm0bfPqL1SS07LXWltBJUBUxqC03ORpAOcM3JQj57DU8JnVY2g==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/abstract-provider": "^5.0.8", + "@ethersproject/bignumber": "^5.0.13", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/properties": "^5.0.7" + } + }, "@ethersproject/address": { "version": "5.0.9", "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.0.9.tgz", @@ -6387,6 +6419,16 @@ "@ethersproject/rlp": "^5.0.7" } }, + "@ethersproject/base64": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.0.7.tgz", + "integrity": "sha512-S5oh5DVfCo06xwJXT8fQC68mvJfgScTl2AXvbYMsHNfIBTDb084Wx4iA9MNlEReOv6HulkS+gyrUM/j3514rSw==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/bytes": "^5.0.9" + } + }, "@ethersproject/bignumber": { "version": "5.0.13", "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.13.tgz", @@ -6454,6 +6496,16 @@ "dev": true, "optional": true }, + "@ethersproject/networks": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.0.7.tgz", + "integrity": "sha512-dI14QATndIcUgcCBL1c5vUr/YsI5cCHLN81rF7PU+yS7Xgp2/Rzbr9+YqpC6NBXHFUASjh6GpKqsVMpufAL0BQ==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/logger": "^5.0.8" + } + }, "@ethersproject/properties": { "version": "5.0.7", "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.0.7.tgz", @@ -6518,6 +6570,20 @@ "@ethersproject/signing-key": "^5.0.8" } }, + "@ethersproject/web": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.0.12.tgz", + "integrity": "sha512-gVxS5iW0bgidZ76kr7LsTxj4uzN5XpCLzvZrLp8TP+4YgxHfCeetFyQkRPgBEAJdNrexdSBayvyJvzGvOq0O8g==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/base64": "^5.0.7", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/properties": "^5.0.7", + "@ethersproject/strings": "^5.0.8" + } + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -7830,14 +7896,6 @@ "dev": true, "requires": { "node-gyp-build": "^4.2.0" - }, - "dependencies": { - "node-gyp-build": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", - "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==", - "dev": true - } } }, "bytes": { @@ -7938,6 +7996,16 @@ } } }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, "caniuse-lite": { "version": "1.0.30001174", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001174.tgz", @@ -8456,6 +8524,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -8681,6 +8750,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -10850,7 +10920,8 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true }, "functional-red-black-tree": { "version": "1.0.1", @@ -10858,6 +10929,17 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "get-intrinsic": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.2.tgz", + "integrity": "sha512-aeX0vrFm21ILl3+JpFFRNe9aUvp6VFZb2/CTbgLb8j75kOhvoNYjt9d8KA/tJG4gSo8nzEDedRl0h7vDmBYRVg==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, "get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -10965,6 +11047,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -11002,7 +11085,8 @@ "has-symbols": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true }, "has-to-string-tag-x": { "version": "1.4.1", @@ -11275,7 +11359,8 @@ "is-callable": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", - "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", + "dev": true }, "is-ci": { "version": "2.0.0", @@ -11298,7 +11383,8 @@ "is-date-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true }, "is-descriptor": { "version": "1.0.2", @@ -11347,7 +11433,8 @@ "is-negative-zero": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==" + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true }, "is-object": { "version": "1.0.2", @@ -11376,6 +11463,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, "requires": { "has-symbols": "^1.0.1" } @@ -11391,6 +11479,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, "requires": { "has-symbols": "^1.0.1" } @@ -12233,7 +12322,8 @@ "object-inspect": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", - "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==" + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", + "dev": true }, "object-is": { "version": "1.1.4", @@ -12243,33 +12333,13 @@ "requires": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" - }, - "dependencies": { - "es-abstract": { - "version": "1.18.0-next.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", - "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.0", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - } } }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true }, "object-visit": { "version": "1.0.1", @@ -12284,32 +12354,12 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, "requires": { "call-bind": "^1.0.0", "define-properties": "^1.1.3", "has-symbols": "^1.0.1", "object-keys": "^1.1.1" - }, - "dependencies": { - "es-abstract": { - "version": "1.18.0-next.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", - "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.0", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - } } }, "object.getownpropertydescriptors": { @@ -12870,47 +12920,16 @@ "unbox-primitive": "^1.0.0" }, "dependencies": { - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true - }, - "is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", - "dev": true - }, - "is-regex": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", - "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", "dev": true, "requires": { - "call-bind": "^1.0.2", + "function-bind": "^1.1.1", + "has": "^1.0.3", "has-symbols": "^1.0.1" } - }, - "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } } } } @@ -12999,15 +13018,6 @@ "uuid": "^3.3.2" } }, - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -13666,34 +13676,13 @@ "call-bind": "^1.0.0", "define-properties": "^1.1.3", "es-abstract": "^1.18.0-next.1" - }, - "dependencies": { - "es-abstract": { - "version": "1.18.0-next.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", - "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.0", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - } } }, "string.prototype.trimend": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", + "dev": true, "requires": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" @@ -13703,6 +13692,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", + "dev": true, "requires": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" @@ -13889,6 +13879,15 @@ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } } } }, @@ -14250,14 +14249,6 @@ "dev": true, "requires": { "node-gyp-build": "^4.2.0" - }, - "dependencies": { - "node-gyp-build": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", - "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==", - "dev": true - } } }, "utf8": { @@ -15236,6 +15227,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -15792,6 +15784,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -15817,7 +15810,8 @@ "has-symbols": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true }, "has-to-string-tag-x": { "version": "1.4.1",