diff --git a/contracts/interfaces/IAToken.sol b/contracts/interfaces/IAToken.sol index 7e1437daf..e28ad1ad5 100644 --- a/contracts/interfaces/IAToken.sol +++ b/contracts/interfaces/IAToken.sol @@ -13,16 +13,23 @@ import {IInitializableAToken} from './IInitializableAToken.sol'; interface IAToken is IERC20, IScaledBalanceToken, IInitializableAToken { /** * @notice Emitted after the mint action - * @param from The address performing the mint + * @param caller The address performing the mint + * @param onBehalfOf The address of the user that will receive the minted aTokens * @param value The amount being minted (user entered amount + balance increase from interest) * @param balanceIncrease The increase in balance since the last action of the user * @param index The next liquidity index of the reserve **/ - event Mint(address indexed from, uint256 value, uint256 balanceIncrease, uint256 index); + event Mint( + address indexed caller, + address indexed onBehalfOf, + uint256 value, + uint256 balanceIncrease, + uint256 index + ); /** * @notice Emitted after aTokens are burned - * @param from The owner of the aTokens, getting them burned + * @param from The address from which the aTokens will be burned * @param target The address that will receive the underlying * @param value The amount being burned (user entered amount - balance increase from interest) * @param balanceIncrease The increase in balance since the last action of the user @@ -47,13 +54,15 @@ interface IAToken is IERC20, IScaledBalanceToken, IInitializableAToken { /** * @notice Mints `amount` aTokens to `user` - * @param user The address receiving the minted tokens + * @param caller The address performing the mint + * @param onBehalfOf The address of the user that will receive the minted aTokens * @param amount The amount of tokens getting minted * @param index The next liquidity index of the reserve * @return `true` if the the previous balance of the user was 0 */ function mint( - address user, + address caller, + address onBehalfOf, uint256 amount, uint256 index ) external returns (bool); @@ -62,13 +71,13 @@ interface IAToken is IERC20, IScaledBalanceToken, IInitializableAToken { * @notice Burns aTokens from `user` and sends the equivalent amount of underlying to `receiverOfUnderlying` * @dev In some instances, the mint event could be emitted from a burn transaction * if the amount to burn is less than the interest the user earned - * @param user The owner of the aTokens, getting them burned + * @param from The address from which the aTokens will be burned * @param receiverOfUnderlying The address that will receive the underlying * @param amount The amount being burned * @param index The next liquidity index of the reserve **/ function burn( - address user, + address from, address receiverOfUnderlying, uint256 amount, uint256 index diff --git a/contracts/interfaces/IPoolAddressesProvider.sol b/contracts/interfaces/IPoolAddressesProvider.sol index d75e972f9..49f4ada5d 100644 --- a/contracts/interfaces/IPoolAddressesProvider.sol +++ b/contracts/interfaces/IPoolAddressesProvider.sol @@ -9,193 +9,219 @@ pragma solidity 0.8.10; interface IPoolAddressesProvider { /** * @notice Emitted when the market identifier is updated. + * @param oldMarketId The old id of the market * @param newMarketId The new id of the market */ - event MarketIdSet(string newMarketId); + event MarketIdSet(string indexed oldMarketId, string indexed newMarketId); /** * @notice Emitted when the pool is updated. + * @param oldAddress The old address of the Pool * @param newAddress The new address of the Pool */ - event PoolUpdated(address indexed newAddress); + event PoolUpdated(address indexed oldAddress, address indexed newAddress); /** * @notice Emitted when the pool configurator is updated. + * @param oldAddress The old address of the PoolConfigurator * @param newAddress The new address of the PoolConfigurator */ - event PoolConfiguratorUpdated(address indexed newAddress); + event PoolConfiguratorUpdated(address indexed oldAddress, address indexed newAddress); /** * @notice Emitted when the price oracle is updated. + * @param oldAddress The old address of the PriceOracle * @param newAddress The new address of the PriceOracle */ - event PriceOracleUpdated(address indexed newAddress); + event PriceOracleUpdated(address indexed oldAddress, address indexed newAddress); /** * @notice Emitted when the ACL manager is updated. + * @param oldAddress The old address of the ACLManager * @param newAddress The new address of the ACLManager */ - event ACLManagerUpdated(address indexed newAddress); + event ACLManagerUpdated(address indexed oldAddress, address indexed newAddress); /** * @notice Emitted when the ACL admin is updated. + * @param oldAddress The old address of the ACLAdmin * @param newAddress The new address of the ACLAdmin */ - event ACLAdminUpdated(address indexed newAddress); + event ACLAdminUpdated(address indexed oldAddress, address indexed newAddress); /** * @notice Emitted when the price oracle sentinel is updated. + * @param oldAddress The old address of the PriceOracleSentinel * @param newAddress The new address of the PriceOracleSentinel */ - event PriceOracleSentinelUpdated(address indexed newAddress); + event PriceOracleSentinelUpdated(address indexed oldAddress, address indexed newAddress); /** * @notice Emitted when the pool data provider is updated. + * @param oldAddress The old address of the PoolDataProvider * @param newAddress The new address of the PoolDataProvider */ - event PoolDataProviderUpdated(address indexed newAddress); + event PoolDataProviderUpdated(address indexed oldAddress, address indexed newAddress); /** - * @notice Emitted when the bridge access control is updated. - * @param newAddress The new address of the BridgeAccessControl + * @notice Emitted when a new proxy is created. + * @param id The identifier of the proxy + * @param proxyAddress The address of the created proxy contract + * @param implementationAddress The address of the implementation contract */ - event BridgeAccessControlUpdated(address indexed newAddress); + event ProxyCreated( + bytes32 indexed id, + address indexed proxyAddress, + address indexed implementationAddress + ); /** - * @notice Emitted when a new proxy is created - * @param id The identifier of the proxy - * @param proxyAddress The address of the created proxy contract + * @notice Emitted when a new non-proxied contract address is registered. + * @param id The identifier of the contract + * @param oldAddress The address of the old contract + * @param newAddress The address of the new contract */ - event ProxyCreated(bytes32 id, address indexed proxyAddress); + event AddressSet(bytes32 indexed id, address indexed oldAddress, address indexed newAddress); /** - * @notice Emitted when a new contract address if registered + * @notice Emitted when the implementation of the proxy registered for id is updated * @param id The identifier of the contract - * @param implementationAddress The address of the implementation contract - * @param hasProxy True if the address is registered behind a proxy, false otherwise + * @param proxyAddress The address of the proxy contract + * @param oldImplementationAddress The address of the old implementation contract + * @param newImplementationAddress The address of the new implementation contract */ - event AddressSet(bytes32 id, address indexed implementationAddress, bool hasProxy); + event AddressSetAsProxy( + bytes32 indexed id, + address indexed proxyAddress, + address oldImplementationAddress, + address indexed newImplementationAddress + ); /** - * @notice Returns the id of the Aave market to which this contract points to + * @notice Returns the id of the Aave market to which this contract points to. * @return The market id **/ function getMarketId() external view returns (string memory); /** - * @notice Allows to set the market which this PoolAddressesProvider represents - * @param marketId The market id + * @notice Associates an id with a specific PoolAddressesProvider. + * @dev This can be used to create an onchain registry of PoolAddressesProviders to + * identify and validate multiple Aave markets. + * @param newMarketId The market id */ - function setMarketId(string calldata marketId) external; + function setMarketId(string calldata newMarketId) external; /** - * @notice Sets an address for an id replacing the address saved in the addresses map - * @dev IMPORTANT Use this function carefully, as it will do a hard replacement + * @notice Returns an address by its identifier. + * @dev The returned address might be an EOA or a contract, potentially proxied + * @dev It returns ZERO if there is no registered address with the given id * @param id The id - * @param newAddress The address to set + * @return The address of the registered for the specified id */ - function setAddress(bytes32 id, address newAddress) external; + function getAddress(bytes32 id) external view returns (address); /** * @notice General function to update the implementation of a proxy registered with * certain `id`. If there is no proxy registered, it will instantiate one and - * set as implementation the `impl` + * set as implementation the `newImplementationAddress`. * @dev IMPORTANT Use this function carefully, only for ids that don't have an explicit * setter function, in order to avoid unexpected consequences * @param id The id - * @param impl The address of the new implementation + * @param newImplementationAddress The address of the new implementation */ - function setAddressAsProxy(bytes32 id, address impl) external; + function setAddressAsProxy(bytes32 id, address newImplementationAddress) external; /** - * @notice Returns an address by id - * @return The address + * @notice Sets an address for an id replacing the address saved in the addresses map. + * @dev IMPORTANT Use this function carefully, as it will do a hard replacement + * @param id The id + * @param newAddress The address to set */ - function getAddress(bytes32 id) external view returns (address); + function setAddress(bytes32 id, address newAddress) external; /** - * @notice Returns the address of the Pool proxy + * @notice Returns the address of the Pool proxy. * @return The Pool proxy address **/ function getPool() external view returns (address); /** - * @notice Updates the implementation of the Pool, or creates the proxy - * setting the new `pool` implementation on the first time calling it - * @param pool The new Pool implementation + * @notice Updates the implementation of the Pool, or creates a proxy + * setting the new `pool` implementation when the function is called for the first time. + * @param newPoolImpl The new Pool implementation **/ - function setPoolImpl(address pool) external; + function setPoolImpl(address newPoolImpl) external; /** - * @notice Returns the address of the PoolConfigurator proxy + * @notice Returns the address of the PoolConfigurator proxy. * @return The PoolConfigurator proxy address **/ function getPoolConfigurator() external view returns (address); /** - * @notice Updates the implementation of the PoolConfigurator, or creates the proxy - * setting the new `configurator` implementation on the first time calling it - * @param configurator The new PoolConfigurator implementation + * @notice Updates the implementation of the PoolConfigurator, or creates a proxy + * setting the new `PoolConfigurator` implementation when the function is called for the first time. + * @param newPoolConfiguratorImpl The new PoolConfigurator implementation **/ - function setPoolConfiguratorImpl(address configurator) external; + function setPoolConfiguratorImpl(address newPoolConfiguratorImpl) external; /** - * @notice Returns the address of the price oracle + * @notice Returns the address of the price oracle. * @return The address of the PriceOracle */ function getPriceOracle() external view returns (address); /** - * @notice Updates the address of the price oracle - * @param priceOracle The address of the new PriceOracle + * @notice Updates the address of the price oracle. + * @param newPriceOracle The address of the new PriceOracle */ - function setPriceOracle(address priceOracle) external; + function setPriceOracle(address newPriceOracle) external; /** - * @notice Returns the address of the ACL manager proxy - * @return The ACLManager proxy address + * @notice Returns the address of the ACL manager. + * @return The address of the ACLManager */ function getACLManager() external view returns (address); /** - * @notice Updates the address of the ACL manager - * @param aclManager The address of the new ACLManager + * @notice Updates the address of the ACL manager. + * @param newAclManager The address of the new ACLManager **/ - function setACLManager(address aclManager) external; + function setACLManager(address newAclManager) external; /** - * @notice Returns the address of the ACL admin + * @notice Returns the address of the ACL admin. * @return The address of the ACL admin */ function getACLAdmin() external view returns (address); /** - * @notice Updates the address of the ACL admin - * @param aclAdmin The address of the new ACL admin + * @notice Updates the address of the ACL admin. + * @param newAclAdmin The address of the new ACL admin */ - function setACLAdmin(address aclAdmin) external; + function setACLAdmin(address newAclAdmin) external; /** - * @notice Returns the address of the PriceOracleSentinel - * @return The PriceOracleSentinel address + * @notice Returns the address of the price oracle sentinel. + * @return The address of the PriceOracleSentinel */ function getPriceOracleSentinel() external view returns (address); /** - * @notice Updates the address of the PriceOracleSentinel - * @param oracleSentinel The address of the new PriceOracleSentinel + * @notice Updates the address of the price oracle sentinel. + * @param newPriceOracleSentinel The address of the new PriceOracleSentinel **/ - function setPriceOracleSentinel(address oracleSentinel) external; + function setPriceOracleSentinel(address newPriceOracleSentinel) external; /** - * @notice Updates the address of the DataProvider - * @param dataProvider The address of the new DataProvider - **/ - function setPoolDataProvider(address dataProvider) external; - - /** - * @notice Returns the address of the DataProvider - * @return The DataProvider address + * @notice Returns the address of the data provider. + * @return The address of the DataProvider */ function getPoolDataProvider() external view returns (address); + + /** + * @notice Updates the address of the data provider. + * @param newDataProvider The address of the new DataProvider + **/ + function setPoolDataProvider(address newDataProvider) external; } diff --git a/contracts/interfaces/IPoolAddressesProviderRegistry.sol b/contracts/interfaces/IPoolAddressesProviderRegistry.sol index c376b9195..7b790ade2 100644 --- a/contracts/interfaces/IPoolAddressesProviderRegistry.sol +++ b/contracts/interfaces/IPoolAddressesProviderRegistry.sol @@ -10,14 +10,16 @@ interface IPoolAddressesProviderRegistry { /** * @notice Emitted when a new AddressesProvider is registered. * @param addressesProvider The address of the registered PoolAddressesProvider + * @param id The id of the registered PoolAddressesProvider */ - event AddressesProviderRegistered(address indexed addressesProvider); + event AddressesProviderRegistered(address indexed addressesProvider, uint256 indexed id); /** * @notice Emitted when an AddressesProvider is unregistered. * @param addressesProvider The address of the unregistered PoolAddressesProvider + * @param id The id of the unregistered PoolAddressesProvider */ - event AddressesProviderUnregistered(address indexed addressesProvider); + event AddressesProviderUnregistered(address indexed addressesProvider, uint256 indexed id); /** * @notice Returns the list of registered addresses providers diff --git a/contracts/mocks/helpers/MockReserveConfiguration.sol b/contracts/mocks/helpers/MockReserveConfiguration.sol index 0a1c4225a..114d29f94 100644 --- a/contracts/mocks/helpers/MockReserveConfiguration.sol +++ b/contracts/mocks/helpers/MockReserveConfiguration.sol @@ -119,6 +119,16 @@ contract MockReserveConfiguration { return configuration.getSupplyCap(); } + function setLiquidationProtocolFee(uint256 liquidationProtocolFee) external { + DataTypes.ReserveConfigurationMap memory config = configuration; + config.setLiquidationProtocolFee(liquidationProtocolFee); + configuration = config; + } + + function getLiquidationProtocolFee() external view returns (uint256) { + return configuration.getLiquidationProtocolFee(); + } + function setUnbackedMintCap(uint256 unbackedMintCap) external { DataTypes.ReserveConfigurationMap memory config = configuration; config.setUnbackedMintCap(unbackedMintCap); diff --git a/contracts/protocol/configuration/PoolAddressesProvider.sol b/contracts/protocol/configuration/PoolAddressesProvider.sol index 6c1382bfb..84974f682 100644 --- a/contracts/protocol/configuration/PoolAddressesProvider.sol +++ b/contracts/protocol/configuration/PoolAddressesProvider.sol @@ -13,9 +13,13 @@ import {InitializableImmutableAdminUpgradeabilityProxy} from '../libraries/aave- * @dev Owned by the Aave Governance **/ contract PoolAddressesProvider is Ownable, IPoolAddressesProvider { + // Identifier of the Aave Market string private _marketId; + + // Mapping of registered addresses (identifiers as keys) mapping(bytes32 => address) private _addresses; + // Main identifiers bytes32 private constant POOL = 'POOL'; bytes32 private constant POOL_CONFIGURATOR = 'POOL_CONFIGURATOR'; bytes32 private constant PRICE_ORACLE = 'PRICE_ORACLE'; @@ -34,29 +38,32 @@ contract PoolAddressesProvider is Ownable, IPoolAddressesProvider { } /// @inheritdoc IPoolAddressesProvider - function setMarketId(string memory marketId) external override onlyOwner { - _setMarketId(marketId); + function setMarketId(string memory newMarketId) external override onlyOwner { + _setMarketId(newMarketId); } /// @inheritdoc IPoolAddressesProvider - function setAddressAsProxy(bytes32 id, address implementationAddress) - external - override - onlyOwner - { - _updateImpl(id, implementationAddress); - emit AddressSet(id, implementationAddress, true); + function getAddress(bytes32 id) public view override returns (address) { + return _addresses[id]; } /// @inheritdoc IPoolAddressesProvider function setAddress(bytes32 id, address newAddress) external override onlyOwner { + address oldAddress = _addresses[id]; _addresses[id] = newAddress; - emit AddressSet(id, newAddress, false); + emit AddressSet(id, oldAddress, newAddress); } /// @inheritdoc IPoolAddressesProvider - function getAddress(bytes32 id) public view override returns (address) { - return _addresses[id]; + function setAddressAsProxy(bytes32 id, address newImplementationAddress) + external + override + onlyOwner + { + address proxyAddress = _addresses[id]; + address oldImplementationAddress = _getProxyImplementation(id); + _updateImpl(id, newImplementationAddress); + emit AddressSetAsProxy(id, proxyAddress, oldImplementationAddress, newImplementationAddress); } /// @inheritdoc IPoolAddressesProvider @@ -65,9 +72,10 @@ contract PoolAddressesProvider is Ownable, IPoolAddressesProvider { } /// @inheritdoc IPoolAddressesProvider - function setPoolImpl(address pool) external override onlyOwner { - _updateImpl(POOL, pool); - emit PoolUpdated(pool); + function setPoolImpl(address newPoolImpl) external override onlyOwner { + address oldPoolImpl = _getProxyImplementation(POOL); + _updateImpl(POOL, newPoolImpl); + emit PoolUpdated(oldPoolImpl, newPoolImpl); } /// @inheritdoc IPoolAddressesProvider @@ -76,9 +84,10 @@ contract PoolAddressesProvider is Ownable, IPoolAddressesProvider { } /// @inheritdoc IPoolAddressesProvider - function setPoolConfiguratorImpl(address configurator) external override onlyOwner { - _updateImpl(POOL_CONFIGURATOR, configurator); - emit PoolConfiguratorUpdated(configurator); + function setPoolConfiguratorImpl(address newPoolConfiguratorImpl) external override onlyOwner { + address oldPoolConfiguratorImpl = _getProxyImplementation(POOL_CONFIGURATOR); + _updateImpl(POOL_CONFIGURATOR, newPoolConfiguratorImpl); + emit PoolConfiguratorUpdated(oldPoolConfiguratorImpl, newPoolConfiguratorImpl); } /// @inheritdoc IPoolAddressesProvider @@ -87,9 +96,10 @@ contract PoolAddressesProvider is Ownable, IPoolAddressesProvider { } /// @inheritdoc IPoolAddressesProvider - function setPriceOracle(address priceOracle) external override onlyOwner { - _addresses[PRICE_ORACLE] = priceOracle; - emit PriceOracleUpdated(priceOracle); + function setPriceOracle(address newPriceOracle) external override onlyOwner { + address oldPriceOracle = _addresses[PRICE_ORACLE]; + _addresses[PRICE_ORACLE] = newPriceOracle; + emit PriceOracleUpdated(oldPriceOracle, newPriceOracle); } /// @inheritdoc IPoolAddressesProvider @@ -98,9 +108,10 @@ contract PoolAddressesProvider is Ownable, IPoolAddressesProvider { } /// @inheritdoc IPoolAddressesProvider - function setACLManager(address aclManager) external override onlyOwner { - _addresses[ACL_MANAGER] = aclManager; - emit ACLManagerUpdated(aclManager); + function setACLManager(address newAclManager) external override onlyOwner { + address oldAclManager = _addresses[ACL_MANAGER]; + _addresses[ACL_MANAGER] = newAclManager; + emit ACLManagerUpdated(oldAclManager, newAclManager); } /// @inheritdoc IPoolAddressesProvider @@ -109,15 +120,10 @@ contract PoolAddressesProvider is Ownable, IPoolAddressesProvider { } /// @inheritdoc IPoolAddressesProvider - function setACLAdmin(address aclAdmin) external override onlyOwner { - _addresses[ACL_ADMIN] = aclAdmin; - emit ACLAdminUpdated(aclAdmin); - } - - /// @inheritdoc IPoolAddressesProvider - function setPriceOracleSentinel(address oracleSentinel) external override onlyOwner { - _addresses[PRICE_ORACLE_SENTINEL] = oracleSentinel; - emit PriceOracleSentinelUpdated(oracleSentinel); + function setACLAdmin(address newAclAdmin) external override onlyOwner { + address oldAclAdmin = _addresses[ACL_ADMIN]; + _addresses[ACL_ADMIN] = newAclAdmin; + emit ACLAdminUpdated(oldAclAdmin, newAclAdmin); } /// @inheritdoc IPoolAddressesProvider @@ -126,9 +132,10 @@ contract PoolAddressesProvider is Ownable, IPoolAddressesProvider { } /// @inheritdoc IPoolAddressesProvider - function setPoolDataProvider(address dataProvider) external override onlyOwner { - _addresses[DATA_PROVIDER] = dataProvider; - emit PoolDataProviderUpdated(dataProvider); + function setPriceOracleSentinel(address newPriceOracleSentinel) external override onlyOwner { + address oldPriceOracleSentinel = _addresses[PRICE_ORACLE_SENTINEL]; + _addresses[PRICE_ORACLE_SENTINEL] = newPriceOracleSentinel; + emit PriceOracleSentinelUpdated(oldPriceOracleSentinel, newPriceOracleSentinel); } /// @inheritdoc IPoolAddressesProvider @@ -136,9 +143,16 @@ contract PoolAddressesProvider is Ownable, IPoolAddressesProvider { return getAddress(DATA_PROVIDER); } + /// @inheritdoc IPoolAddressesProvider + function setPoolDataProvider(address newDataProvider) external override onlyOwner { + address oldDataProvider = _addresses[DATA_PROVIDER]; + _addresses[DATA_PROVIDER] = newDataProvider; + emit PoolDataProviderUpdated(oldDataProvider, newDataProvider); + } + /** - * @notice Internal function to update the implementation of a specific proxied component of the protocol - * @dev If there is no proxy registered in the given `id`, it creates the proxy setting `newAdress` + * @notice Internal function to update the implementation of a specific proxied component of the protocol. + * @dev If there is no proxy registered with the given identifier, it creates the proxy setting `newAddress` * as implementation and calls the initialize() function on the proxy * @dev If there is already a proxy registered, it just updates the implementation to `newAddress` and * calls the initialize() function via upgradeToAndCall() in the proxy @@ -146,29 +160,45 @@ contract PoolAddressesProvider is Ownable, IPoolAddressesProvider { * @param newAddress The address of the new implementation **/ function _updateImpl(bytes32 id, address newAddress) internal { - address payable proxyAddress = payable(_addresses[id]); - - InitializableImmutableAdminUpgradeabilityProxy proxy = InitializableImmutableAdminUpgradeabilityProxy( - proxyAddress - ); + address proxyAddress = _addresses[id]; + InitializableImmutableAdminUpgradeabilityProxy proxy; bytes memory params = abi.encodeWithSignature('initialize(address)', address(this)); if (proxyAddress == address(0)) { proxy = new InitializableImmutableAdminUpgradeabilityProxy(address(this)); - _addresses[id] = address(proxy); + _addresses[id] = proxyAddress = address(proxy); proxy.initialize(newAddress, params); - emit ProxyCreated(id, address(proxy)); + emit ProxyCreated(id, proxyAddress, newAddress); } else { + proxy = InitializableImmutableAdminUpgradeabilityProxy(payable(proxyAddress)); proxy.upgradeToAndCall(newAddress, params); } } /** - * @notice Updates the identifier of the Aave market - * @param marketId The new id of the market + * @notice Updates the identifier of the Aave market. + * @param newMarketId The new id of the market **/ - function _setMarketId(string memory marketId) internal { - _marketId = marketId; - emit MarketIdSet(marketId); + function _setMarketId(string memory newMarketId) internal { + string memory oldMarketId = _marketId; + _marketId = newMarketId; + emit MarketIdSet(oldMarketId, newMarketId); + } + + /** + * @notice Returns the the implementation contract of the proxy contract by its identifier. + * @dev It returns ZERO if there is no registered address with the given id + * @dev It reverts if the registered address with the given id is not `InitializableImmutableAdminUpgradeabilityProxy` + * @param id The id + * @return The address of the implementation contract + */ + function _getProxyImplementation(bytes32 id) internal returns (address) { + address proxyAddress = _addresses[id]; + if (proxyAddress == address(0)) { + return address(0); + } else { + address payable payableProxyAddress = payable(proxyAddress); + return InitializableImmutableAdminUpgradeabilityProxy(payableProxyAddress).implementation(); + } } } diff --git a/contracts/protocol/configuration/PoolAddressesProviderRegistry.sol b/contracts/protocol/configuration/PoolAddressesProviderRegistry.sol index 8228519ae..c682aa354 100644 --- a/contracts/protocol/configuration/PoolAddressesProviderRegistry.sol +++ b/contracts/protocol/configuration/PoolAddressesProviderRegistry.sol @@ -40,14 +40,15 @@ contract PoolAddressesProviderRegistry is Ownable, IPoolAddressesProviderRegistr _addressesProviders[provider] = id; _addToAddressesProvidersList(provider); - emit AddressesProviderRegistered(provider); + emit AddressesProviderRegistered(provider, id); } /// @inheritdoc IPoolAddressesProviderRegistry function unregisterAddressesProvider(address provider) external override onlyOwner { require(_addressesProviders[provider] > 0, Errors.PROVIDER_NOT_REGISTERED); + uint256 id = _addressesProviders[provider]; _addressesProviders[provider] = 0; - emit AddressesProviderUnregistered(provider); + emit AddressesProviderUnregistered(provider, id); } /// @inheritdoc IPoolAddressesProviderRegistry diff --git a/contracts/protocol/libraries/configuration/ReserveConfiguration.sol b/contracts/protocol/libraries/configuration/ReserveConfiguration.sol index 2131cb094..3471a5c37 100644 --- a/contracts/protocol/libraries/configuration/ReserveConfiguration.sol +++ b/contracts/protocol/libraries/configuration/ReserveConfiguration.sol @@ -55,7 +55,7 @@ library ReserveConfiguration { uint256 internal constant MAX_VALID_RESERVE_FACTOR = 65535; uint256 internal constant MAX_VALID_BORROW_CAP = 68719476735; uint256 internal constant MAX_VALID_SUPPLY_CAP = 68719476735; - uint256 internal constant MAX_VALID_LIQUIDATION_PROTOCOL_FEE = 10000; + uint256 internal constant MAX_VALID_LIQUIDATION_PROTOCOL_FEE = 65535; uint256 internal constant MAX_VALID_EMODE_CATEGORY = 255; uint256 internal constant MAX_VALID_UNBACKED_MINT_CAP = 68719476735; uint256 internal constant MAX_VALID_DEBT_CEILING = 1099511627775; diff --git a/contracts/protocol/libraries/logic/BridgeLogic.sol b/contracts/protocol/libraries/logic/BridgeLogic.sol index fb5b65d38..76da61e4e 100644 --- a/contracts/protocol/libraries/logic/BridgeLogic.sol +++ b/contracts/protocol/libraries/logic/BridgeLogic.sol @@ -76,6 +76,7 @@ library BridgeLogic { reserve.updateInterestRates(reserveCache, asset, 0, 0); bool isFirstSupply = IAToken(reserveCache.aTokenAddress).mint( + msg.sender, onBehalfOf, amount, reserveCache.nextLiquidityIndex diff --git a/contracts/protocol/libraries/logic/LiquidationLogic.sol b/contracts/protocol/libraries/logic/LiquidationLogic.sol index af1e884e7..e929edde6 100644 --- a/contracts/protocol/libraries/logic/LiquidationLogic.sol +++ b/contracts/protocol/libraries/logic/LiquidationLogic.sol @@ -46,8 +46,8 @@ library LiquidationLogic { bool receiveAToken ); - uint256 internal constant DEFAULT_LIQUIDATION_CLOSE_FACTOR = 5000; - uint256 public constant MAX_LIQUIDATION_CLOSE_FACTOR = 10000; + uint256 internal constant DEFAULT_LIQUIDATION_CLOSE_FACTOR = 5e3; + uint256 public constant MAX_LIQUIDATION_CLOSE_FACTOR = 1e4; uint256 public constant CLOSE_FACTOR_HF_THRESHOLD = 0.95e18; struct LiquidationCallLocalVars { diff --git a/contracts/protocol/libraries/logic/SupplyLogic.sol b/contracts/protocol/libraries/logic/SupplyLogic.sol index f0cde6edc..66ec3d9c8 100644 --- a/contracts/protocol/libraries/logic/SupplyLogic.sol +++ b/contracts/protocol/libraries/logic/SupplyLogic.sol @@ -65,6 +65,7 @@ library SupplyLogic { IERC20(params.asset).safeTransferFrom(msg.sender, reserveCache.aTokenAddress, params.amount); bool isFirstSupply = IAToken(reserveCache.aTokenAddress).mint( + msg.sender, params.onBehalfOf, params.amount, reserveCache.nextLiquidityIndex diff --git a/contracts/protocol/libraries/types/DataTypes.sol b/contracts/protocol/libraries/types/DataTypes.sol index 792ae6661..bf06fd3a5 100644 --- a/contracts/protocol/libraries/types/DataTypes.sol +++ b/contracts/protocol/libraries/types/DataTypes.sol @@ -91,7 +91,7 @@ library DataTypes { uint256 currLiquidityRate; uint256 currVariableBorrowRate; uint256 reserveFactor; - DataTypes.ReserveConfigurationMap reserveConfiguration; + ReserveConfigurationMap reserveConfiguration; address aTokenAddress; address stableDebtTokenAddress; address variableDebtTokenAddress; @@ -123,7 +123,7 @@ library DataTypes { address user; address onBehalfOf; uint256 amount; - DataTypes.InterestRateMode interestRateMode; + InterestRateMode interestRateMode; uint16 referralCode; bool releaseUnderlying; uint256 maxStableRateBorrowSizePercent; @@ -136,7 +136,7 @@ library DataTypes { struct ExecuteRepayParams { address asset; uint256 amount; - DataTypes.InterestRateMode interestRateMode; + InterestRateMode interestRateMode; address onBehalfOf; bool useATokens; } @@ -205,12 +205,12 @@ library DataTypes { } struct ValidateBorrowParams { - DataTypes.ReserveCache reserveCache; - DataTypes.UserConfigurationMap userConfig; + ReserveCache reserveCache; + UserConfigurationMap userConfig; address asset; address userAddress; uint256 amount; - DataTypes.InterestRateMode interestRateMode; + InterestRateMode interestRateMode; uint256 maxStableLoanPercent; uint256 reservesCount; address oracle; @@ -222,7 +222,7 @@ library DataTypes { } struct ValidateLiquidationCallParams { - DataTypes.ReserveCache debtReserveCache; + ReserveCache debtReserveCache; uint256 totalDebt; uint256 healthFactor; address priceOracleSentinel; diff --git a/contracts/protocol/pool/Pool.sol b/contracts/protocol/pool/Pool.sol index f9be0e2d9..a49a022b8 100644 --- a/contracts/protocol/pool/Pool.sol +++ b/contracts/protocol/pool/Pool.sol @@ -565,14 +565,11 @@ contract Pool is VersionedInitializable, IPool, PoolStorage { } } - if (droppedReservesCount == 0) return reserves; - - address[] memory undroppedReserves = new address[](reserveListCount - droppedReservesCount); - for (uint256 i = 0; i < reserveListCount - droppedReservesCount; i++) { - undroppedReserves[i] = reserves[i]; + // Reduces the length of the reserves array by `droppedReservesCount` + assembly { + mstore(reserves, sub(reserveListCount, droppedReservesCount)) } - - return undroppedReserves; + return reserves; } /// @inheritdoc IPool diff --git a/contracts/protocol/pool/PoolConfigurator.sol b/contracts/protocol/pool/PoolConfigurator.sol index c4a4baf5e..74932c972 100644 --- a/contracts/protocol/pool/PoolConfigurator.sol +++ b/contracts/protocol/pool/PoolConfigurator.sol @@ -265,12 +265,10 @@ contract PoolConfigurator is VersionedInitializable, IPoolConfigurator { override onlyRiskOrPoolAdmins { + require(fee <= PercentageMath.PERCENTAGE_FACTOR, Errors.INVALID_LIQUIDATION_PROTOCOL_FEE); DataTypes.ReserveConfigurationMap memory currentConfig = _pool.getConfiguration(asset); - currentConfig.setLiquidationProtocolFee(fee); - _pool.setConfiguration(asset, currentConfig); - emit LiquidationProtocolFeeChanged(asset, fee); } diff --git a/contracts/protocol/pool/PoolStorage.sol b/contracts/protocol/pool/PoolStorage.sol index bd5ffa819..74922944e 100644 --- a/contracts/protocol/pool/PoolStorage.sol +++ b/contracts/protocol/pool/PoolStorage.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.10; import {UserConfiguration} from '../libraries/configuration/UserConfiguration.sol'; import {ReserveConfiguration} from '../libraries/configuration/ReserveConfiguration.sol'; import {ReserveLogic} from '../libraries/logic/ReserveLogic.sol'; -import {IPoolAddressesProvider} from '../../interfaces/IPoolAddressesProvider.sol'; import {DataTypes} from '../libraries/types/DataTypes.sol'; /** diff --git a/contracts/protocol/tokenization/AToken.sol b/contracts/protocol/tokenization/AToken.sol index 299143078..e338ebb7b 100644 --- a/contracts/protocol/tokenization/AToken.sol +++ b/contracts/protocol/tokenization/AToken.sol @@ -83,7 +83,7 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { /// @inheritdoc IAToken function burn( - address user, + address from, address receiverOfUnderlying, uint256 amount, uint256 index @@ -91,13 +91,13 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { uint256 amountScaled = amount.rayDiv(index); require(amountScaled != 0, Errors.INVALID_BURN_AMOUNT); - uint256 scaledBalance = super.balanceOf(user); + uint256 scaledBalance = super.balanceOf(from); uint256 balanceIncrease = scaledBalance.rayMul(index) - - scaledBalance.rayMul(_userState[user].additionalData); + scaledBalance.rayMul(_userState[from].additionalData); - _userState[user].additionalData = index.toUint128(); + _userState[from].additionalData = index.toUint128(); - _burn(user, amountScaled.toUint128()); + _burn(from, amountScaled.toUint128()); if (receiverOfUnderlying != address(this)) { IERC20(_underlyingAsset).safeTransfer(receiverOfUnderlying, amount); @@ -105,35 +105,36 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { if (balanceIncrease > amount) { uint256 amountToMint = balanceIncrease - amount; - emit Transfer(address(0), user, amountToMint); - emit Mint(user, amountToMint, balanceIncrease, index); + emit Transfer(address(0), from, amountToMint); + emit Mint(from, from, amountToMint, balanceIncrease, index); } else { uint256 amountToBurn = amount - balanceIncrease; - emit Transfer(user, address(0), amountToBurn); - emit Burn(user, receiverOfUnderlying, amountToBurn, balanceIncrease, index); + emit Transfer(from, address(0), amountToBurn); + emit Burn(from, receiverOfUnderlying, amountToBurn, balanceIncrease, index); } } /// @inheritdoc IAToken function mint( - address user, + address caller, + address onBehalfOf, uint256 amount, uint256 index ) public override onlyPool returns (bool) { uint256 amountScaled = amount.rayDiv(index); require(amountScaled != 0, Errors.INVALID_MINT_AMOUNT); - uint256 scaledBalance = super.balanceOf(user); + uint256 scaledBalance = super.balanceOf(onBehalfOf); uint256 balanceIncrease = scaledBalance.rayMul(index) - - scaledBalance.rayMul(_userState[user].additionalData); + scaledBalance.rayMul(_userState[onBehalfOf].additionalData); - _userState[user].additionalData = index.toUint128(); + _userState[onBehalfOf].additionalData = index.toUint128(); - _mint(user, amountScaled.toUint128()); + _mint(onBehalfOf, amountScaled.toUint128()); uint256 amountToMint = amount + balanceIncrease; - emit Transfer(address(0), user, amountToMint); - emit Mint(user, amountToMint, balanceIncrease, index); + emit Transfer(address(0), onBehalfOf, amountToMint); + emit Mint(caller, onBehalfOf, amountToMint, balanceIncrease, index); return scaledBalance == 0; } @@ -143,7 +144,7 @@ contract AToken is VersionedInitializable, IncentivizedERC20, IAToken { if (amount == 0) { return; } - mint(_treasury, amount, index); + mint(address(POOL), _treasury, amount, index); } /// @inheritdoc IAToken diff --git a/test-suites/aave-protocol-data-provider.spec.ts b/test-suites/aave-protocol-data-provider.spec.ts index 2227f357b..4d41dbec5 100644 --- a/test-suites/aave-protocol-data-provider.spec.ts +++ b/test-suites/aave-protocol-data-provider.spec.ts @@ -1,8 +1,11 @@ -import { MockPool } from '../types/MockPool'; +import hre from 'hardhat'; import { expect } from 'chai'; +import { utils } from 'ethers'; import { makeSuite, TestEnv } from './helpers/make-suite'; -import hre from 'hardhat'; -import { getMockPool } from '@aave/deploy-v3'; +import { getMockPool, ZERO_ADDRESS } from '@aave/deploy-v3'; +import { InitializableImmutableAdminUpgradeabilityProxy } from '../types'; +import { impersonateAccountsHardhat } from '../helpers/misc-utils'; +import { topUpNonPayableWithEther } from './helpers/utils/funds'; makeSuite('AaveProtocolDataProvider: Edge cases', (testEnv: TestEnv) => { const MKR_ADDRESS = '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2'; @@ -15,10 +18,23 @@ makeSuite('AaveProtocolDataProvider: Edge cases', (testEnv: TestEnv) => { // Deploy a mock Pool const mockPool = await hre.deployments.deploy('MockPool', { from: deployer }); + // Impersonate PoolAddressesProvider + await impersonateAccountsHardhat([addressesProvider.address]); + const addressesProviderSigner = await hre.ethers.getSigner(addressesProvider.address); + + const poolProxyAddress = await addressesProvider.getPool(); + const poolProxy = (await hre.ethers.getContractAt( + 'InitializableImmutableAdminUpgradeabilityProxy', + poolProxyAddress, + addressesProviderSigner + )) as InitializableImmutableAdminUpgradeabilityProxy; + + const oldPoolImpl = await poolProxy.callStatic.implementation(); + // Update the addressesProvider with a mock pool expect(await addressesProvider.connect(poolAdmin.signer).setPoolImpl(mockPool.address)) .to.emit(addressesProvider, 'PoolUpdated') - .withArgs(mockPool.address); + .withArgs(oldPoolImpl, mockPool.address); // Add MKR and ETH addresses const proxiedMockPoolAddress = await addressesProvider.getPool(); diff --git a/test-suites/addresses-provider-registry.spec.ts b/test-suites/addresses-provider-registry.spec.ts index ed54d5708..7e6355a21 100644 --- a/test-suites/addresses-provider-registry.spec.ts +++ b/test-suites/addresses-provider-registry.spec.ts @@ -34,7 +34,7 @@ makeSuite('AddressesProviderRegistry', (testEnv: TestEnv) => { // Simulating an addresses provider using the users[1] wallet address expect(await registry.registerAddressesProvider(users[1].address, NEW_ADDRESSES_PROVIDER_ID)) .to.emit(registry, 'AddressesProviderRegistered') - .withArgs(users[1].address); + .withArgs(users[1].address, NEW_ADDRESSES_PROVIDER_ID); const providers = await registry.getAddressesProvidersList(); @@ -57,7 +57,7 @@ makeSuite('AddressesProviderRegistry', (testEnv: TestEnv) => { expect(await registry.unregisterAddressesProvider(users[1].address)) .to.emit(registry, 'AddressesProviderUnregistered') - .withArgs(users[1].address); + .withArgs(users[1].address, id); const providers = await registry.getAddressesProvidersList(); @@ -94,7 +94,7 @@ makeSuite('AddressesProviderRegistry', (testEnv: TestEnv) => { await registry.registerAddressesProvider(addressesProvider.address, NEW_ADDRESSES_PROVIDER_ID) ) .to.emit(registry, 'AddressesProviderRegistered') - .withArgs(addressesProvider.address); + .withArgs(addressesProvider.address, NEW_ADDRESSES_PROVIDER_ID); const providers = await registry.getAddressesProvidersList(); diff --git a/test-suites/atoken-edge.spec.ts b/test-suites/atoken-edge.spec.ts index 5f8684673..9d760064b 100644 --- a/test-suites/atoken-edge.spec.ts +++ b/test-suites/atoken-edge.spec.ts @@ -126,7 +126,9 @@ makeSuite('AToken: Edge cases', (testEnv: TestEnv) => { const poolSigner = await hre.ethers.getSigner(pool.address); await expect( - aDai.connect(poolSigner).mint(users[0].address, 0, utils.parseUnits('1', 27)) + aDai + .connect(poolSigner) + .mint(users[0].address, users[0].address, 0, utils.parseUnits('1', 27)) ).to.be.revertedWith(INVALID_MINT_AMOUNT); }); @@ -139,7 +141,11 @@ makeSuite('AToken: Edge cases', (testEnv: TestEnv) => { const poolSigner = await hre.ethers.getSigner(pool.address); const mintingAmount = await convertToCurrencyDecimals(aDai.address, '100'); - expect(aDai.connect(poolSigner).mint(ZERO_ADDRESS, mintingAmount, utils.parseUnits('1', 27))) + expect( + aDai + .connect(poolSigner) + .mint(ZERO_ADDRESS, ZERO_ADDRESS, mintingAmount, utils.parseUnits('1', 27)) + ) .to.emit(aDai, 'Transfer') .withArgs(ZERO_ADDRESS, ZERO_ADDRESS, mintingAmount); }); diff --git a/test-suites/atoken-event-accounting.spec.ts b/test-suites/atoken-event-accounting.spec.ts index 904026fe8..b2b99feb0 100644 --- a/test-suites/atoken-event-accounting.spec.ts +++ b/test-suites/atoken-event-accounting.spec.ts @@ -25,7 +25,7 @@ makeSuite('AToken Mint and Burn Event Accounting', (testEnv) => { ); const aTokenMintEventSignature = utils.keccak256( - utils.toUtf8Bytes('Mint(address,uint256,uint256,uint256)') + utils.toUtf8Bytes('Mint(address,address,uint256,uint256,uint256)') ); const aTokenBurnEventSignature = utils.keccak256( utils.toUtf8Bytes('Burn(address,address,uint256,uint256,uint256)') @@ -72,6 +72,7 @@ makeSuite('AToken Mint and Burn Event Accounting', (testEnv) => { ) .to.emit(aDai, 'Mint') .withArgs( + depositor.address, depositor.address, firstDaiDeposit, expectedBalanceIncrease, @@ -82,6 +83,45 @@ makeSuite('AToken Mint and Burn Event Accounting', (testEnv) => { expect(aDaiBalance).to.be.equal(firstDaiDeposit); }); + it('User 1 - Deposit dai on behalf of user 2', async () => { + const { + dai, + aDai, + users: [depositor, receiver], + pool, + helpersContract, + } = testEnv; + + // mints DAI to depositor + await waitForTx( + await dai + .connect(depositor.signer) + ['mint(uint256)'](await convertToCurrencyDecimals(dai.address, '10000')) + ); + + // approve protocol to access depositor wallet + await waitForTx(await dai.connect(depositor.signer).approve(pool.address, MAX_UINT_AMOUNT)); + + const daiReserveData = await helpersContract.getReserveData(dai.address); + + const expectedBalanceIncrease = 0; + + await expect( + pool.connect(depositor.signer).deposit(dai.address, firstDaiDeposit, receiver.address, '0') + ) + .to.emit(aDai, 'Mint') + .withArgs( + depositor.address, + receiver.address, + firstDaiDeposit, + expectedBalanceIncrease, + daiReserveData.liquidityIndex + ); + + const aDaiBalance = await aDai.balanceOf(receiver.address); + expect(aDaiBalance).to.be.equal(firstDaiDeposit); + }); + it('User 2 - deposit ETH, borrow Dai', async () => { const { dai, @@ -170,6 +210,7 @@ makeSuite('AToken Mint and Burn Event Accounting', (testEnv) => { // check mint event parameters expect(parsedMintEvent.args.from).to.equal(borrower.address); + expect(parsedMintEvent.args.onBehalfOf).to.equal(borrower.address); expect(parsedMintEvent.args.value).to.be.closeTo(totalMinted, 2); expect(parsedMintEvent.args.balanceIncrease).to.be.closeTo(accruedDebt1, 2); }); @@ -224,7 +265,8 @@ makeSuite('AToken Mint and Burn Event Accounting', (testEnv) => { expect(parsedTransferEvent.args.value).to.be.closeTo(totalMinted, 2); // check mint event parameters - expect(parsedMintEvent.args.from).to.equal(depositor.address); + expect(parsedMintEvent.args.caller).to.equal(depositor.address); + expect(parsedMintEvent.args.onBehalfOf).to.equal(depositor.address); expect(parsedMintEvent.args.value).to.be.closeTo(totalMinted, 2); expect(parsedMintEvent.args.balanceIncrease).to.be.closeTo(accruedInterest1, 2); }); @@ -282,7 +324,8 @@ makeSuite('AToken Mint and Burn Event Accounting', (testEnv) => { expect(parsedTransferEvent.args.value).to.be.closeTo(totalMinted, 2); // check mint event - expect(parsedMintEvent.args.from).to.equal(depositor.address); + expect(parsedMintEvent.args.caller).to.equal(depositor.address); + expect(parsedMintEvent.args.onBehalfOf).to.equal(depositor.address); expect(parsedMintEvent.args.value).to.be.closeTo(totalMinted, 2); expect(parsedMintEvent.args.balanceIncrease).to.be.closeTo(accruedInterest2, 2); expect(parsedMintEvent.args.index).to.equal(daiReserveData.liquidityIndex); @@ -518,7 +561,8 @@ makeSuite('AToken Mint and Burn Event Accounting', (testEnv) => { expect(parsedTransferEvent.args.value).to.be.closeTo(totalMinted, 2); // check mint event - expect(parsedMintEvent.args.from).to.equal(depositor.address); + expect(parsedMintEvent.args.caller).to.equal(depositor.address); + expect(parsedMintEvent.args.onBehalfOf).to.equal(depositor.address); expect(parsedMintEvent.args.value).to.be.closeTo(totalMinted, 2); expect(parsedMintEvent.args.balanceIncrease).to.be.closeTo(totalMinted.add(smallWithdrawal), 2); expect(parsedMintEvent.args.index).to.equal(daiReserveData.liquidityIndex); diff --git a/test-suites/atoken-modifiers.spec.ts b/test-suites/atoken-modifiers.spec.ts index a064ed0a5..68771e524 100644 --- a/test-suites/atoken-modifiers.spec.ts +++ b/test-suites/atoken-modifiers.spec.ts @@ -7,7 +7,9 @@ makeSuite('AToken: Modifiers', (testEnv: TestEnv) => { it('Tries to invoke mint not being the Pool (revert expected)', async () => { const { deployer, aDai } = testEnv; - await expect(aDai.mint(deployer.address, '1', '1')).to.be.revertedWith(CALLER_MUST_BE_POOL); + await expect(aDai.mint(deployer.address, deployer.address, '1', '1')).to.be.revertedWith( + CALLER_MUST_BE_POOL + ); }); it('Tries to invoke burn not being the Pool (revert expected)', async () => { diff --git a/test-suites/configurator-liquidation-protocol-fee.spec.ts b/test-suites/configurator-liquidation-protocol-fee.spec.ts index 71db8e8ac..d239d31a7 100644 --- a/test-suites/configurator-liquidation-protocol-fee.spec.ts +++ b/test-suites/configurator-liquidation-protocol-fee.spec.ts @@ -30,7 +30,7 @@ makeSuite('PoolConfigurator: Liquidation Protocol Fee', (testEnv: TestEnv) => { expect(daiLiquidationProtocolFee).to.be.equal('0'); }); - it('Sets the protocol liquidation fee to 1000 (10.00co%)', async () => { + it('Sets the protocol liquidation fee to 1000 (10.00%)', async () => { const { configurator, dai, usdc, helpersContract } = testEnv; const liquidationProtocolFee = 1000; @@ -51,7 +51,28 @@ makeSuite('PoolConfigurator: Liquidation Protocol Fee', (testEnv: TestEnv) => { expect(daiLiquidationProtocolFee).to.be.equal(liquidationProtocolFee); }); - it('Tries to set the protocol liquidation fee to 10001 and reverts', async () => { + it('Sets the protocol liquidation fee to 10000 (100.00%) equal to PERCENTAGE_FACTOR', async () => { + const { configurator, dai, usdc, helpersContract } = testEnv; + + const liquidationProtocolFee = 10000; + + expect(await configurator.setLiquidationProtocolFee(usdc.address, liquidationProtocolFee)) + .to.emit(configurator, 'LiquidationProtocolFeeChanged') + .withArgs(usdc.address, liquidationProtocolFee); + expect(await configurator.setLiquidationProtocolFee(dai.address, liquidationProtocolFee)) + .to.emit(configurator, 'LiquidationProtocolFeeChanged') + .withArgs(dai.address, liquidationProtocolFee); + + const usdcLiquidationProtocolFee = await helpersContract.getLiquidationProtocolFee( + usdc.address + ); + const daiLiquidationProtocolFee = await helpersContract.getLiquidationProtocolFee(dai.address); + + expect(usdcLiquidationProtocolFee).to.be.equal(liquidationProtocolFee); + expect(daiLiquidationProtocolFee).to.be.equal(liquidationProtocolFee); + }); + + it('Tries to set the protocol liquidation fee to 10001 (100.01%) > PERCENTAGE_FACTOR (revert expected)', async () => { const { configurator, dai, usdc } = testEnv; const liquidationProtocolFee = 10001; diff --git a/test-suites/debt-token-delegation-permit.spec.ts b/test-suites/debt-token-delegation-permit.spec.ts index cb5522a1a..59613825d 100644 --- a/test-suites/debt-token-delegation-permit.spec.ts +++ b/test-suites/debt-token-delegation-permit.spec.ts @@ -261,6 +261,47 @@ makeSuite('DebtToken: Permit Delegation', (testEnv: TestEnv) => { ).to.be.equal('0'); }); + it('Stable debt delegation with wrong delegator', async () => { + const { + stableDebtDai, + deployer: user1, + users: [user2, user3], + } = testEnv; + + const chainId = hre.network.config.chainId || HARDHAT_CHAINID; + const expiration = MAX_UINT_AMOUNT; + const nonce = (await stableDebtDai.nonces(user2.address)).toNumber(); + const EIP712_REVISION = await stableDebtDai.EIP712_REVISION(); + const permitAmount = daiMintedAmount.div(3); + const msgParams = buildDelegationWithSigParams( + chainId, + stableDebtDai.address, + EIP712_REVISION, + await stableDebtDai.name(), + user3.address, + nonce, + expiration, + permitAmount.toString() + ); + + const user2PrivateKey = testWallets[1].secretKey; + expect( + (await stableDebtDai.borrowAllowance(user2.address, user3.address)).toString() + ).to.be.equal('0'); + + const { v, r, s } = getSignatureFromTypedData(user2PrivateKey, msgParams); + + await expect( + stableDebtDai + .connect(user1.signer) + .delegationWithSig(user1.address, user3.address, permitAmount, expiration, v, r, s) + ).to.be.revertedWith(ProtocolErrors.INVALID_SIGNATURE); + + expect( + (await stableDebtDai.borrowAllowance(user2.address, user3.address)).toString() + ).to.be.equal('0'); + }); + it('Variable debt delegation with delegator == address(0)', async () => { const { variableDebtDai, @@ -340,4 +381,44 @@ makeSuite('DebtToken: Permit Delegation', (testEnv: TestEnv) => { (await variableDebtDai.borrowAllowance(user2.address, user3.address)).toString() ).to.be.equal('0'); }); + + it('Variable debt delegation with wrong delegator', async () => { + const { + variableDebtDai, + deployer: user1, + users: [user2, user3], + } = testEnv; + + const chainId = hre.network.config.chainId || HARDHAT_CHAINID; + const expiration = MAX_UINT_AMOUNT; + const nonce = (await variableDebtDai.nonces(user2.address)).toNumber(); + const permitAmount = daiMintedAmount.div(3); + const msgParams = buildDelegationWithSigParams( + chainId, + variableDebtDai.address, + EIP712_REVISION, + await variableDebtDai.name(), + user3.address, + nonce, + expiration, + permitAmount.toString() + ); + + const user2PrivateKey = testWallets[1].secretKey; + expect( + (await variableDebtDai.borrowAllowance(user2.address, user3.address)).toString() + ).to.be.equal('0'); + + const { v, r, s } = getSignatureFromTypedData(user2PrivateKey, msgParams); + + await expect( + variableDebtDai + .connect(user1.signer) + .delegationWithSig(user1.address, user3.address, permitAmount, expiration, v, r, s) + ).to.be.revertedWith(ProtocolErrors.INVALID_SIGNATURE); + + expect( + (await variableDebtDai.borrowAllowance(user2.address, user3.address)).toString() + ).to.be.equal('0'); + }); }); diff --git a/test-suites/pausable-pool.spec.ts b/test-suites/pausable-pool.spec.ts index 05bf528b9..fe2a5233f 100644 --- a/test-suites/pausable-pool.spec.ts +++ b/test-suites/pausable-pool.spec.ts @@ -365,7 +365,7 @@ makeSuite('PausablePool', (testEnv: TestEnv) => { ).deployed(); expect(await poolAddressesProvider.setACLManager(aclManager.address)) .to.emit(poolAddressesProvider, 'ACLManagerUpdated') - .withArgs(aclManager.address); + .withArgs(ZERO_ADDRESS, aclManager.address); // Set role of EmergencyAdmin const emergencyAdminRole = await aclManager.EMERGENCY_ADMIN_ROLE(); @@ -376,7 +376,7 @@ makeSuite('PausablePool', (testEnv: TestEnv) => { // Update the Pool impl with a MockPool expect(await poolAddressesProvider.setPoolImpl(mockPool.address)) .to.emit(poolAddressesProvider, 'PoolUpdated') - .withArgs(mockPool.address); + .withArgs(ZERO_ADDRESS, mockPool.address); // Add ZERO_ADDRESS as a reserve const proxiedMockPoolAddress = await poolAddressesProvider.getPool(); @@ -386,7 +386,7 @@ makeSuite('PausablePool', (testEnv: TestEnv) => { // Update the PoolConfigurator impl with the PoolConfigurator expect(await poolAddressesProvider.setPoolConfiguratorImpl(poolConfigurator.address)) .to.emit(poolAddressesProvider, 'PoolConfiguratorUpdated') - .withArgs(poolConfigurator.address); + .withArgs(ZERO_ADDRESS, poolConfigurator.address); const proxiedPoolConfiguratorAddress = await poolAddressesProvider.getPoolConfigurator(); const proxiedPoolConfigurator = await getPoolConfiguratorProxy(proxiedPoolConfiguratorAddress); diff --git a/test-suites/pool-addresses-provider.spec.ts b/test-suites/pool-addresses-provider.spec.ts index 5b92b69a0..4c032d2aa 100644 --- a/test-suites/pool-addresses-provider.spec.ts +++ b/test-suites/pool-addresses-provider.spec.ts @@ -1,11 +1,28 @@ +import hre from 'hardhat'; import { expect } from 'chai'; import { utils } from 'ethers'; -import { createRandomAddress } from '../helpers/misc-utils'; +import { createRandomAddress, impersonateAccountsHardhat } from '../helpers/misc-utils'; import { ProtocolErrors } from '../helpers/types'; import { ZERO_ADDRESS } from '../helpers/constants'; import { makeSuite, TestEnv } from './helpers/make-suite'; import { deployPool, deployMockPool } from '@aave/deploy-v3/dist/helpers/contract-deployments'; import { evmSnapshot, evmRevert } from '@aave/deploy-v3'; +import { InitializableImmutableAdminUpgradeabilityProxy } from '../types'; + +const getProxyImplementation = async (addressesProviderAddress: string, proxyAddress: string) => { + // Impersonate PoolAddressesProvider + await impersonateAccountsHardhat([addressesProviderAddress]); + const addressesProviderSigner = await hre.ethers.getSigner(addressesProviderAddress); + + const proxy = (await hre.ethers.getContractAt( + 'InitializableImmutableAdminUpgradeabilityProxy', + proxyAddress, + addressesProviderSigner + )) as InitializableImmutableAdminUpgradeabilityProxy; + + const implementationAddress = await proxy.callStatic.implementation(); + return implementationAddress; +}; makeSuite('PoolAddressesProvider', (testEnv: TestEnv) => { const { OWNABLE_ONLY_OWNER } = ProtocolErrors; @@ -48,16 +65,19 @@ makeSuite('PoolAddressesProvider', (testEnv: TestEnv) => { const currentAddressesProviderOwner = users[1]; const mockPool = await deployPool(); - const proxiedAddressId = utils.keccak256(utils.toUtf8Bytes('RANDOM_PROXIED')); + const proxiedAddressId = utils.formatBytes32String('RANDOM_PROXIED'); expect( await addressesProvider .connect(currentAddressesProviderOwner.signer) .setAddressAsProxy(proxiedAddressId, mockPool.address) ) - .to.emit(addressesProvider, 'AddressSet') - .withArgs(proxiedAddressId, mockPool.address, true) + .to.emit(addressesProvider, 'AddressSetAsProxy') .to.emit(addressesProvider, 'ProxyCreated'); + + const proxyAddress = await addressesProvider.getAddress(proxiedAddressId); + const implAddress = await getProxyImplementation(addressesProvider.address, proxyAddress); + expect(implAddress).to.be.eq(mockPool.address); }); it('Owner adds a new address with no proxy', async () => { @@ -65,19 +85,170 @@ makeSuite('PoolAddressesProvider', (testEnv: TestEnv) => { const currentAddressesProviderOwner = users[1]; const mockNonProxiedAddress = createRandomAddress(); - const nonProxiedAddressId = utils.keccak256(utils.toUtf8Bytes('RANDOM_NON_PROXIED')); + const nonProxiedAddressId = utils.formatBytes32String('RANDOM_NON_PROXIED'); + const oldAddress = await addressesProvider.getAddress(nonProxiedAddressId); expect( await addressesProvider .connect(currentAddressesProviderOwner.signer) .setAddress(nonProxiedAddressId, mockNonProxiedAddress) ) .to.emit(addressesProvider, 'AddressSet') - .withArgs(nonProxiedAddressId, mockNonProxiedAddress, false); + .withArgs(nonProxiedAddressId, oldAddress, mockNonProxiedAddress); expect((await addressesProvider.getAddress(nonProxiedAddressId)).toLowerCase()).to.be.eq( mockNonProxiedAddress.toLowerCase() ); + + const proxyAddress = await addressesProvider.getAddress(nonProxiedAddressId); + await expect(getProxyImplementation(addressesProvider.address, proxyAddress)).to.be.reverted; + }); + + it('Owner adds a new address with no proxy and turns it into a proxy', async () => { + const { addressesProvider, users } = testEnv; + + const currentAddressesProviderOwner = users[1]; + const mockPool = await deployPool(); + const mockConvertibleAddress = mockPool.address; + const convertibleAddressId = utils.formatBytes32String('CONVERTIBLE_ADDRESS'); + + expect(await addressesProvider.getAddress(convertibleAddressId)).to.be.eq(ZERO_ADDRESS); + + const oldNonProxiedAddress = await addressesProvider.getAddress(convertibleAddressId); + + // Add address as non proxy + expect( + await addressesProvider + .connect(currentAddressesProviderOwner.signer) + .setAddress(convertibleAddressId, mockConvertibleAddress) + ) + .to.emit(addressesProvider, 'AddressSet') + .withArgs(convertibleAddressId, oldNonProxiedAddress, mockConvertibleAddress); + + let registeredAddress = await addressesProvider.getAddress(convertibleAddressId); + expect(registeredAddress).to.be.eq(mockConvertibleAddress); + await expect(getProxyImplementation(addressesProvider.address, registeredAddress)).to.be + .reverted; + + // Unregister address as non proxy + expect( + await addressesProvider + .connect(currentAddressesProviderOwner.signer) + .setAddress(convertibleAddressId, ZERO_ADDRESS) + ) + .to.emit(addressesProvider, 'AddressSet') + .withArgs(convertibleAddressId, mockConvertibleAddress, ZERO_ADDRESS); + + // Add address as proxy + expect( + await addressesProvider + .connect(currentAddressesProviderOwner.signer) + .setAddressAsProxy(convertibleAddressId, mockConvertibleAddress) + ) + .to.emit(addressesProvider, 'AddressSetAsProxy') + .to.emit(addressesProvider, 'ProxyCreated'); + + const proxyAddress = await addressesProvider.getAddress(convertibleAddressId); + const implAddress = await getProxyImplementation(addressesProvider.address, proxyAddress); + expect(implAddress).to.be.eq(mockConvertibleAddress); + }); + + it('Unregister a proxy address', async () => { + const { addressesProvider, users } = testEnv; + + const currentAddressesProviderOwner = users[1]; + + const convertibleAddressId = utils.formatBytes32String('CONVERTIBLE_ADDRESS'); + + const proxyAddress = await addressesProvider.getAddress(convertibleAddressId); + + expect( + await addressesProvider + .connect(currentAddressesProviderOwner.signer) + .setAddress(convertibleAddressId, ZERO_ADDRESS) + ) + .to.emit(addressesProvider, 'AddressSet') + .withArgs(convertibleAddressId, proxyAddress, ZERO_ADDRESS); + + const proxyAddressAfter = await addressesProvider.getAddress(convertibleAddressId); + expect(proxyAddressAfter).to.be.eq(ZERO_ADDRESS); + expect(proxyAddressAfter).to.be.not.eq(proxyAddress); + await expect(getProxyImplementation(addressesProvider.address, proxyAddressAfter)).to.be + .reverted; + }); + + it('Owner adds a new address with proxy and turns it into a no proxy', async () => { + const { addressesProvider, users } = testEnv; + + const currentAddressesProviderOwner = users[1]; + const mockPool = await deployPool(); + const mockConvertibleAddress = mockPool.address; + const convertibleAddressId = utils.formatBytes32String('CONVERTIBLE_ADDRESS2'); + + expect(await addressesProvider.getAddress(convertibleAddressId)).to.be.eq(ZERO_ADDRESS); + + // Add address as proxy + expect( + await addressesProvider + .connect(currentAddressesProviderOwner.signer) + .setAddressAsProxy(convertibleAddressId, mockConvertibleAddress) + ) + .to.emit(addressesProvider, 'AddressSetAsProxy') + .to.emit(addressesProvider, 'ProxyCreated'); + + const proxyAddress = await addressesProvider.getAddress(convertibleAddressId); + const implAddress = await getProxyImplementation(addressesProvider.address, proxyAddress); + expect(implAddress).to.be.eq(mockConvertibleAddress); + + // Unregister address as proxy + expect( + await addressesProvider + .connect(currentAddressesProviderOwner.signer) + .setAddress(convertibleAddressId, ZERO_ADDRESS) + ) + .to.emit(addressesProvider, 'AddressSet') + .withArgs(convertibleAddressId, proxyAddress, ZERO_ADDRESS); + + // Add address as non proxy + expect( + await addressesProvider + .connect(currentAddressesProviderOwner.signer) + .setAddress(convertibleAddressId, mockConvertibleAddress) + ) + .to.emit(addressesProvider, 'AddressSet') + .withArgs(convertibleAddressId, ZERO_ADDRESS, mockConvertibleAddress); + + const registeredAddressAfter = await addressesProvider.getAddress(convertibleAddressId); + expect(registeredAddressAfter).to.be.not.eq(proxyAddress); + expect(registeredAddressAfter).to.be.eq(mockConvertibleAddress); + await expect(getProxyImplementation(addressesProvider.address, registeredAddressAfter)).to.be + .reverted; + }); + + it('Unregister a no proxy address', async () => { + const { addressesProvider, users } = testEnv; + + const currentAddressesProviderOwner = users[1]; + + const convertibleAddressId = utils.formatBytes32String('CONVERTIBLE_ADDRESS2'); + + const registeredAddress = await addressesProvider.getAddress(convertibleAddressId); + await expect(getProxyImplementation(addressesProvider.address, registeredAddress)).to.be + .reverted; + + expect( + await addressesProvider + .connect(currentAddressesProviderOwner.signer) + .setAddress(convertibleAddressId, ZERO_ADDRESS) + ) + .to.emit(addressesProvider, 'AddressSet') + .withArgs(convertibleAddressId, registeredAddress, ZERO_ADDRESS); + + const registeredAddressAfter = await addressesProvider.getAddress(convertibleAddressId); + expect(registeredAddressAfter).to.be.eq(ZERO_ADDRESS); + expect(registeredAddressAfter).to.be.not.eq(registeredAddress); + await expect(getProxyImplementation(addressesProvider.address, registeredAddress)).to.be + .reverted; }); it('Owner updates the implementation of a proxy which is already initialized', async () => { @@ -92,6 +263,13 @@ makeSuite('PoolAddressesProvider', (testEnv: TestEnv) => { const poolAddress = await addressesProvider.getPool(); expect(poolAddress).to.be.not.eq(ZERO_ADDRESS); + const poolAddressId = utils.formatBytes32String('POOL'); + const proxyAddress = await addressesProvider.getAddress(poolAddressId); + const implementationAddress = await getProxyImplementation( + addressesProvider.address, + proxyAddress + ); + // Update the Pool proxy expect( await addressesProvider @@ -99,7 +277,7 @@ makeSuite('PoolAddressesProvider', (testEnv: TestEnv) => { .setPoolImpl(mockPool.address) ) .to.emit(addressesProvider, 'PoolUpdated') - .withArgs(mockPool.address); + .withArgs(implementationAddress, mockPool.address); // Pool address should not change expect(await addressesProvider.getPool()).to.be.eq(poolAddress); @@ -125,7 +303,7 @@ makeSuite('PoolAddressesProvider', (testEnv: TestEnv) => { .setMarketId(NEW_MARKET_ID) ) .to.emit(addressesProvider, 'MarketIdSet') - .withArgs(NEW_MARKET_ID); + .withArgs(oldMarketId, NEW_MARKET_ID); expect(await addressesProvider.getMarketId()).to.be.not.eq(oldMarketId); expect(await addressesProvider.getMarketId()).to.be.eq(NEW_MARKET_ID); @@ -133,7 +311,148 @@ makeSuite('PoolAddressesProvider', (testEnv: TestEnv) => { await evmRevert(snapId); }); - it('Owner updates the data provider', async () => { + it('Owner updates the PoolConfigurator', async () => { + const snapId = await evmSnapshot(); + + const { addressesProvider, configurator, users } = testEnv; + const currentAddressesProviderOwner = users[1]; + + const newPoolConfiguratorImpl = (await deployMockPool()).address; + + expect(await addressesProvider.getPoolConfigurator(), configurator.address); + + const poolConfiguratorAddressId = utils.formatBytes32String('POOL_CONFIGURATOR'); + const proxyAddress = await addressesProvider.getAddress(poolConfiguratorAddressId); + const implementationAddress = await getProxyImplementation( + addressesProvider.address, + proxyAddress + ); + + expect( + await addressesProvider + .connect(currentAddressesProviderOwner.signer) + .setPoolConfiguratorImpl(newPoolConfiguratorImpl) + ) + .to.emit(addressesProvider, 'PoolConfiguratorUpdated') + .withArgs(implementationAddress, newPoolConfiguratorImpl); + + expect(await addressesProvider.getPoolConfigurator()).to.be.eq(configurator.address); + const implementationAddressAfter = await getProxyImplementation( + addressesProvider.address, + proxyAddress + ); + expect(implementationAddressAfter).to.be.not.eq(implementationAddress); + expect(implementationAddressAfter).to.be.eq(newPoolConfiguratorImpl); + + await evmRevert(snapId); + }); + + it('Owner updates the PriceOracle', async () => { + const snapId = await evmSnapshot(); + + const { addressesProvider, oracle, users } = testEnv; + const currentAddressesProviderOwner = users[1]; + + const newPriceOracleAddress = createRandomAddress(); + + expect(await addressesProvider.getPriceOracle(), oracle.address); + + const priceOracleAddressId = utils.formatBytes32String('PRICE_ORACLE'); + const registeredAddress = await addressesProvider.getAddress(priceOracleAddressId); + expect( + await addressesProvider + .connect(currentAddressesProviderOwner.signer) + .setPriceOracle(newPriceOracleAddress) + ) + .to.emit(addressesProvider, 'PriceOracleUpdated') + .withArgs(registeredAddress, newPriceOracleAddress); + + expect(await addressesProvider.getPriceOracle()).to.be.not.eq(oracle.address); + expect(await addressesProvider.getPriceOracle()).to.be.eq(newPriceOracleAddress); + + await evmRevert(snapId); + }); + + it('Owner updates the ACLManager', async () => { + const snapId = await evmSnapshot(); + + const { addressesProvider, users, aclManager } = testEnv; + const currentAddressesProviderOwner = users[1]; + + const newACLManagerAddress = createRandomAddress(); + + expect(await addressesProvider.getACLManager(), aclManager.address); + + const aclManagerAddressId = utils.formatBytes32String('ACL_MANAGER'); + const registeredAddress = await addressesProvider.getAddress(aclManagerAddressId); + expect( + await addressesProvider + .connect(currentAddressesProviderOwner.signer) + .setACLManager(newACLManagerAddress) + ) + .to.emit(addressesProvider, 'ACLManagerUpdated') + .withArgs(registeredAddress, newACLManagerAddress); + + expect(await addressesProvider.getACLManager()).to.be.not.eq(aclManager.address); + expect(await addressesProvider.getACLManager()).to.be.eq(newACLManagerAddress); + + await evmRevert(snapId); + }); + + it('Owner updates the ACLAdmin', async () => { + const snapId = await evmSnapshot(); + + const { addressesProvider, users } = testEnv; + const { aclAdmin: aclAdminAddress } = await hre.getNamedAccounts(); + const currentAddressesProviderOwner = users[1]; + + const newACLAdminAddress = createRandomAddress(); + + expect(await addressesProvider.getACLAdmin(), aclAdminAddress); + + const aclAdminAddressId = utils.formatBytes32String('ACL_ADMIN'); + const registeredAddress = await addressesProvider.getAddress(aclAdminAddressId); + expect( + await addressesProvider + .connect(currentAddressesProviderOwner.signer) + .setACLAdmin(newACLAdminAddress) + ) + .to.emit(addressesProvider, 'ACLAdminUpdated') + .withArgs(registeredAddress, newACLAdminAddress); + + expect(await addressesProvider.getACLAdmin()).to.be.not.eq(aclAdminAddress); + expect(await addressesProvider.getACLAdmin()).to.be.eq(newACLAdminAddress); + + await evmRevert(snapId); + }); + + it('Owner updates the PriceOracleSentinel', async () => { + const snapId = await evmSnapshot(); + + const { addressesProvider, users } = testEnv; + const currentAddressesProviderOwner = users[1]; + + const newPriceOracleSentinelAddress = createRandomAddress(); + + const priceOracleSentinelAddressId = utils.formatBytes32String('PRICE_ORACLE_SENTINEL'); + const registeredAddress = await addressesProvider.getAddress(priceOracleSentinelAddressId); + expect( + await addressesProvider + .connect(currentAddressesProviderOwner.signer) + .setPriceOracleSentinel(newPriceOracleSentinelAddress) + ) + .to.emit(addressesProvider, 'PriceOracleSentinelUpdated') + .withArgs(registeredAddress, newPriceOracleSentinelAddress); + + expect(await addressesProvider.getPriceOracleSentinel()).to.be.not.eq(registeredAddress); + expect(await addressesProvider.getPriceOracleSentinel()).to.be.eq( + newPriceOracleSentinelAddress + ); + + await evmRevert(snapId); + }); + + it('Owner updates the DataProvider', async () => { const snapId = await evmSnapshot(); const { addressesProvider, helpersContract, users } = testEnv; @@ -141,16 +460,20 @@ makeSuite('PoolAddressesProvider', (testEnv: TestEnv) => { expect(await addressesProvider.getPoolDataProvider(), helpersContract.address); + const newDataProviderAddress = createRandomAddress(); + + const dataProviderAddressId = utils.formatBytes32String('DATA_PROVIDER'); + const registeredAddress = await addressesProvider.getAddress(dataProviderAddressId); expect( await addressesProvider .connect(currentAddressesProviderOwner.signer) - .setPoolDataProvider(ZERO_ADDRESS) + .setPoolDataProvider(newDataProviderAddress) ) .to.emit(addressesProvider, 'PoolDataProviderUpdated') - .withArgs(ZERO_ADDRESS); + .withArgs(registeredAddress, newDataProviderAddress); expect(await addressesProvider.getPoolDataProvider()).to.be.not.eq(helpersContract.address); - expect(await addressesProvider.getPoolDataProvider()).to.be.eq(ZERO_ADDRESS); + expect(await addressesProvider.getPoolDataProvider()).to.be.eq(newDataProviderAddress); await evmRevert(snapId); }); diff --git a/test-suites/pool-edge.spec.ts b/test-suites/pool-edge.spec.ts index 71539825a..517f86308 100644 --- a/test-suites/pool-edge.spec.ts +++ b/test-suites/pool-edge.spec.ts @@ -16,6 +16,7 @@ import { VariableDebtToken__factory, AToken__factory, Pool__factory, + InitializableImmutableAdminUpgradeabilityProxy, } from '../types'; declare var hre: HardhatRuntimeEnvironment; @@ -37,6 +38,8 @@ makeSuite('Pool: Edge cases', (testEnv: TestEnv) => { const MAX_STABLE_RATE_BORROW_SIZE_PERCENT = '2500'; const MAX_NUMBER_RESERVES = '128'; + const POOL_ID = utils.formatBytes32String('POOL'); + let snap: string; beforeEach(async () => { @@ -360,12 +363,25 @@ makeSuite('Pool: Edge cases', (testEnv: TestEnv) => { log: false, }); + // Impersonate PoolAddressesProvider + await impersonateAccountsHardhat([addressesProvider.address]); + const addressesProviderSigner = await hre.ethers.getSigner(addressesProvider.address); + + const poolProxyAddress = await addressesProvider.getPool(); + const poolProxy = (await hre.ethers.getContractAt( + 'InitializableImmutableAdminUpgradeabilityProxy', + poolProxyAddress, + addressesProviderSigner + )) as InitializableImmutableAdminUpgradeabilityProxy; + + const oldPoolImpl = await poolProxy.callStatic.implementation(); + // Upgrade the Pool expect( await addressesProvider.connect(poolAdmin.signer).setPoolImpl(NEW_POOL_IMPL_ARTIFACT.address) ) .to.emit(addressesProvider, 'PoolUpdated') - .withArgs(NEW_POOL_IMPL_ARTIFACT.address); + .withArgs(oldPoolImpl, NEW_POOL_IMPL_ARTIFACT.address); // Get the Pool instance const mockPoolAddress = await addressesProvider.getPool(); @@ -526,12 +542,25 @@ makeSuite('Pool: Edge cases', (testEnv: TestEnv) => { log: false, }); + // Impersonate PoolAddressesProvider + await impersonateAccountsHardhat([addressesProvider.address]); + const addressesProviderSigner = await hre.ethers.getSigner(addressesProvider.address); + + const proxyAddress = await addressesProvider.getAddress(POOL_ID); + const proxy = (await hre.ethers.getContractAt( + 'InitializableImmutableAdminUpgradeabilityProxy', + proxyAddress, + addressesProviderSigner + )) as InitializableImmutableAdminUpgradeabilityProxy; + + const implementationAddress = await proxy.callStatic.implementation(); + // Upgrade the Pool expect( await addressesProvider.connect(poolAdmin.signer).setPoolImpl(NEW_POOL_IMPL_ARTIFACT.address) ) .to.emit(addressesProvider, 'PoolUpdated') - .withArgs(NEW_POOL_IMPL_ARTIFACT.address); + .withArgs(implementationAddress, NEW_POOL_IMPL_ARTIFACT.address); // Get the Pool instance const mockPoolAddress = await addressesProvider.getPool(); diff --git a/test-suites/price-oracle-sentinel.spec.ts b/test-suites/price-oracle-sentinel.spec.ts index 50c99f352..cc9b94833 100644 --- a/test-suites/price-oracle-sentinel.spec.ts +++ b/test-suites/price-oracle-sentinel.spec.ts @@ -66,7 +66,7 @@ makeSuite('PriceOracleSentinel', (testEnv: TestEnv) => { .setPriceOracleSentinel(priceOracleSentinel.address) ) .to.emit(addressesProvider, 'PriceOracleSentinelUpdated') - .withArgs(priceOracleSentinel.address); + .withArgs(ZERO_ADDRESS, priceOracleSentinel.address); expect(await addressesProvider.getPriceOracleSentinel()).to.be.eq(priceOracleSentinel.address); diff --git a/test-suites/reserve-configuration.spec.ts b/test-suites/reserve-configuration.spec.ts index 37ef2d1c5..bdce2c4ca 100644 --- a/test-suites/reserve-configuration.spec.ts +++ b/test-suites/reserve-configuration.spec.ts @@ -1,9 +1,9 @@ import { expect } from 'chai'; import { BigNumber } from 'ethers'; import { deployMockReserveConfiguration } from '@aave/deploy-v3/dist/helpers/contract-deployments'; -import { MockReserveConfiguration } from '@aave/deploy-v3/dist/types/typechain'; import { ProtocolErrors } from '../helpers/types'; import { evmSnapshot, evmRevert } from '@aave/deploy-v3'; +import { MockReserveConfiguration } from '../types'; describe('ReserveConfiguration', async () => { let snap: string; @@ -31,6 +31,8 @@ describe('ReserveConfiguration', async () => { const MAX_VALID_LIQUIDATION_THRESHOLD = BigNumber.from(65535); const MAX_VALID_DECIMALS = BigNumber.from(255); const MAX_VALID_EMODE_CATEGORY = BigNumber.from(255); + const MAX_VALID_RESERVE_FACTOR = BigNumber.from(65535); + const MAX_VALID_LIQUIDATION_PROTOCOL_FEE = BigNumber.from(65535); before(async () => { configMock = await deployMockReserveConfiguration(); @@ -164,6 +166,28 @@ describe('ReserveConfiguration', async () => { expect(await configMock.getReserveFactor()).to.be.eq(ZERO); }); + it('setReserveFactor() with reserveFactor == MAX_VALID_RESERVE_FACTOR', async () => { + expect(bigNumbersToArrayString(await configMock.getParams())).to.be.eql( + bigNumbersToArrayString([ZERO, ZERO, ZERO, ZERO, ZERO, ZERO]) + ); + expect(await configMock.setReserveFactor(MAX_VALID_RESERVE_FACTOR)); + expect(bigNumbersToArrayString(await configMock.getParams())).to.be.eql( + bigNumbersToArrayString([ZERO, ZERO, ZERO, ZERO, MAX_VALID_RESERVE_FACTOR, ZERO]) + ); + }); + + it('setReserveFactor() with reserveFactor > MAX_VALID_RESERVE_FACTOR', async () => { + expect(bigNumbersToArrayString(await configMock.getParams())).to.be.eql( + bigNumbersToArrayString([ZERO, ZERO, ZERO, ZERO, ZERO, ZERO]) + ); + await expect(configMock.setReserveFactor(MAX_VALID_RESERVE_FACTOR.add(1))).to.be.revertedWith( + ProtocolErrors.INVALID_RESERVE_FACTOR + ); + expect(bigNumbersToArrayString(await configMock.getParams())).to.be.eql( + bigNumbersToArrayString([ZERO, ZERO, ZERO, ZERO, ZERO, ZERO]) + ); + }); + it('getBorrowCap()', async () => { expect(bigNumbersToArrayString(await configMock.getCaps())).to.be.eql( bigNumbersToArrayString([ZERO, ZERO]) @@ -314,4 +338,20 @@ describe('ReserveConfiguration', async () => { ); expect(await configMock.getEModeCategory()).to.be.eq(ZERO); }); + + it('setLiquidationProtocolFee() with liquidationProtocolFee == MAX_VALID_LIQUIDATION_PROTOCOL_FEE', async () => { + expect(await configMock.getLiquidationProtocolFee()).to.be.eq(ZERO); + expect(await configMock.setLiquidationProtocolFee(MAX_VALID_LIQUIDATION_PROTOCOL_FEE)); + expect(await configMock.getLiquidationProtocolFee()).to.be.eq( + MAX_VALID_LIQUIDATION_PROTOCOL_FEE + ); + }); + + it('setLiquidationProtocolFee() with liquidationProtocolFee > MAX_VALID_LIQUIDATION_PROTOCOL_FEE', async () => { + expect(await configMock.getLiquidationProtocolFee()).to.be.eq(ZERO); + await expect( + configMock.setLiquidationProtocolFee(MAX_VALID_LIQUIDATION_PROTOCOL_FEE.add(1)) + ).to.be.revertedWith(ProtocolErrors.INVALID_LIQUIDATION_PROTOCOL_FEE); + expect(await configMock.getLiquidationProtocolFee()).to.be.eq(ZERO); + }); });