From a57e24a6089a57c54e7cf4a53bd68c6a63d89c73 Mon Sep 17 00:00:00 2001 From: Alexandru Niculae <43644109+avniculae@users.noreply.github.com> Date: Thu, 11 Jun 2026 17:25:38 +0300 Subject: [PATCH 01/15] feat: add Addresses Provider --- .../V4AddressesProvider.sol | 166 +++++ .../V4AddressesProviderStorage.sol | 23 + .../instances/V4AddressesProviderInstance.sol | 23 + .../interfaces/IV4AddressesProvider.sol | 162 +++++ .../V4AddressesProvider.Upgradeable.t.sol | 150 +++++ .../V4AddressesProvider.t.sol | 626 ++++++++++++++++++ .../mocks/MockV4AddressesProviderInstance.sol | 26 + 7 files changed, 1176 insertions(+) create mode 100644 src/addresses-provider/V4AddressesProvider.sol create mode 100644 src/addresses-provider/V4AddressesProviderStorage.sol create mode 100644 src/addresses-provider/instances/V4AddressesProviderInstance.sol create mode 100644 src/addresses-provider/interfaces/IV4AddressesProvider.sol create mode 100644 tests/contracts/addresses-provider/V4AddressesProvider.Upgradeable.t.sol create mode 100644 tests/contracts/addresses-provider/V4AddressesProvider.t.sol create mode 100644 tests/helpers/mocks/MockV4AddressesProviderInstance.sol diff --git a/src/addresses-provider/V4AddressesProvider.sol b/src/addresses-provider/V4AddressesProvider.sol new file mode 100644 index 000000000..fd2e199f1 --- /dev/null +++ b/src/addresses-provider/V4AddressesProvider.sol @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: LicenseRef-BUSL +pragma solidity 0.8.28; + +import {Ownable2StepUpgradeable} from 'src/dependencies/openzeppelin-upgradeable/Ownable2StepUpgradeable.sol'; +import {EnumerableSet} from 'src/dependencies/openzeppelin/EnumerableSet.sol'; +import {V4AddressesProviderStorage} from 'src/addresses-provider/V4AddressesProviderStorage.sol'; +import {IV4AddressesProvider} from 'src/addresses-provider/interfaces/IV4AddressesProvider.sol'; + +/// @title V4AddressesProvider +/// @author Aave Labs +/// @notice Main registry of Aave V4 contract addresses. +abstract contract V4AddressesProvider is + V4AddressesProviderStorage, + Ownable2StepUpgradeable, + IV4AddressesProvider +{ + using EnumerableSet for *; + + /// @inheritdoc IV4AddressesProvider + string public constant CANONICAL_HUB_TAG = 'CANONICAL_HUB'; + + /// @inheritdoc IV4AddressesProvider + string public constant CANONICAL_SPOKE_TAG = 'CANONICAL_SPOKE'; + + /// @inheritdoc IV4AddressesProvider + string public constant TOKENIZATION_SPOKE_TAG = 'TOKENIZATION_SPOKE'; + + /// @inheritdoc IV4AddressesProvider + string public constant TREASURY_SPOKE_TAG = 'TREASURY_SPOKE'; + + /// @dev To be overridden by the inheriting V4AddressesProvider instance contract. + function initialize(address owner) external virtual; + + /// @inheritdoc IV4AddressesProvider + function setAddress( + string memory name, + string memory tag, + address newAddress + ) external onlyOwner { + _setAddress({name: name, tag: tag, newAddress: newAddress}); + } + + /// @inheritdoc IV4AddressesProvider + function setCanonicalHub(string memory name, address hub) external onlyOwner { + _setAddress({name: name, tag: CANONICAL_HUB_TAG, newAddress: hub}); + } + + /// @inheritdoc IV4AddressesProvider + function setCanonicalSpoke(string memory name, address spoke) external onlyOwner { + _setAddress({name: name, tag: CANONICAL_SPOKE_TAG, newAddress: spoke}); + } + + /// @inheritdoc IV4AddressesProvider + function setTokenizationSpoke(string memory name, address spoke) external onlyOwner { + _setAddress({name: name, tag: TOKENIZATION_SPOKE_TAG, newAddress: spoke}); + } + + /// @inheritdoc IV4AddressesProvider + function setTreasurySpoke(string memory name, address spoke) external onlyOwner { + _setAddress({name: name, tag: TREASURY_SPOKE_TAG, newAddress: spoke}); + } + + /// @inheritdoc IV4AddressesProvider + function getAddressEntry(bytes32 id) external view returns (AddressEntry memory) { + return _addressEntries[id]; + } + + /// @inheritdoc IV4AddressesProvider + function getIds(string memory tag) external view returns (bytes32[] memory) { + return _taggedIds[tag].values(); + } + + /// @inheritdoc IV4AddressesProvider + function getTags() external view returns (string[] memory) { + return _tags.values(); + } + + /// @inheritdoc IV4AddressesProvider + function getCanonicalHub(string memory name) external view returns (address) { + return getAddress({name: name, tag: CANONICAL_HUB_TAG}); + } + + /// @inheritdoc IV4AddressesProvider + function getCanonicalHubs() external view returns (address[] memory) { + return getAddresses(CANONICAL_HUB_TAG); + } + + /// @inheritdoc IV4AddressesProvider + function getCanonicalSpoke(string memory name) external view returns (address) { + return getAddress({name: name, tag: CANONICAL_SPOKE_TAG}); + } + + /// @inheritdoc IV4AddressesProvider + function getCanonicalSpokes() external view returns (address[] memory) { + return getAddresses(CANONICAL_SPOKE_TAG); + } + + /// @inheritdoc IV4AddressesProvider + function getTokenizationSpoke(string memory name) external view returns (address) { + return getAddress({name: name, tag: TOKENIZATION_SPOKE_TAG}); + } + + /// @inheritdoc IV4AddressesProvider + function getTokenizationSpokes() external view returns (address[] memory) { + return getAddresses(TOKENIZATION_SPOKE_TAG); + } + + /// @inheritdoc IV4AddressesProvider + function getTreasurySpoke(string memory name) external view returns (address) { + return getAddress({name: name, tag: TREASURY_SPOKE_TAG}); + } + + /// @inheritdoc IV4AddressesProvider + function getTreasurySpokes() external view returns (address[] memory) { + return getAddresses(TREASURY_SPOKE_TAG); + } + + /// @inheritdoc IV4AddressesProvider + function getAddress(bytes32 id) public view returns (address) { + return _addressEntries[id].addr; + } + + /// @inheritdoc IV4AddressesProvider + function getAddress(string memory name, string memory tag) public view returns (address) { + return getAddress(getId({name: name, tag: tag})); + } + + /// @inheritdoc IV4AddressesProvider + function getAddresses(string memory tag) public view returns (address[] memory) { + bytes32[] memory ids = _taggedIds[tag].values(); + address[] memory addresses = new address[](ids.length); + for (uint256 i = 0; i < ids.length; i++) { + addresses[i] = _addressEntries[ids[i]].addr; + } + return addresses; + } + + /// @inheritdoc IV4AddressesProvider + function getId(string memory name, string memory tag) public pure returns (bytes32) { + return keccak256(bytes(string.concat(name, '_', tag))); + } + + function _setAddress(string memory name, string memory tag, address newAddress) internal { + require(bytes(name).length > 0, InvalidName()); + require(bytes(tag).length > 0, InvalidTag()); + + bytes32 id = getId({name: name, tag: tag}); + AddressEntry memory oldEntry = _addressEntries[id]; + + if (newAddress == address(0)) { + require(oldEntry.addr != address(0), AddressNotSet(id)); + _taggedIds[oldEntry.tag].remove(id); + if (_taggedIds[oldEntry.tag].length() == 0) { + _tags.remove(oldEntry.tag); + } + delete _addressEntries[id]; + } else { + require(oldEntry.addr == address(0), AddressAlreadySet(id)); + _addressEntries[id] = AddressEntry({addr: newAddress, tag: tag}); + _taggedIds[tag].add(id); + _tags.add(tag); + } + + emit AddressSet(id, name, tag, oldEntry.addr, newAddress); + } +} diff --git a/src/addresses-provider/V4AddressesProviderStorage.sol b/src/addresses-provider/V4AddressesProviderStorage.sol new file mode 100644 index 000000000..5d299f665 --- /dev/null +++ b/src/addresses-provider/V4AddressesProviderStorage.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: LicenseRef-BUSL +pragma solidity 0.8.28; + +import {EnumerableSet} from 'src/dependencies/openzeppelin/EnumerableSet.sol'; +import {IV4AddressesProvider} from 'src/addresses-provider/interfaces/IV4AddressesProvider.sol'; + +/// @title V4AddressesProviderStorage +/// @author Aave Labs +/// @notice Storage layout for the V4AddressesProvider contract. +/// @dev This contract defines all storage variables used by the V4AddressesProvider. +abstract contract V4AddressesProviderStorage { + /// @dev Map of entry identifiers to address entries. + mapping(bytes32 id => IV4AddressesProvider.AddressEntry) internal _addressEntries; + + /// @dev Map of tags to set of entry identifiers. + mapping(string tag => EnumerableSet.Bytes32Set ids) internal _taggedIds; + + /// @dev Set of all tags with at least one registered entry. + EnumerableSet.StringSet internal _tags; + + /// @dev Reserved storage space to allow for future layout updates. + uint256[50] private __gap; +} diff --git a/src/addresses-provider/instances/V4AddressesProviderInstance.sol b/src/addresses-provider/instances/V4AddressesProviderInstance.sol new file mode 100644 index 000000000..1d563eb0b --- /dev/null +++ b/src/addresses-provider/instances/V4AddressesProviderInstance.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: LicenseRef-BUSL +pragma solidity 0.8.28; + +import {V4AddressesProvider} from 'src/addresses-provider/V4AddressesProvider.sol'; + +/// @title V4AddressesProviderInstance +/// @author Aave Labs +/// @notice Implementation contract for the V4AddressesProvider. +contract V4AddressesProviderInstance is V4AddressesProvider { + uint64 public constant ADDRESSES_PROVIDER_REVISION = 1; + + /// @dev Constructor. + constructor() { + _disableInitializers(); + } + + /// @notice Initializer. + /// @param owner The address of the owner. + function initialize(address owner) external override reinitializer(ADDRESSES_PROVIDER_REVISION) { + __Ownable_init(owner); + __Ownable2Step_init(); + } +} diff --git a/src/addresses-provider/interfaces/IV4AddressesProvider.sol b/src/addresses-provider/interfaces/IV4AddressesProvider.sol new file mode 100644 index 000000000..1b3a4fc07 --- /dev/null +++ b/src/addresses-provider/interfaces/IV4AddressesProvider.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: LicenseRef-BUSL +pragma solidity ^0.8.0; + +/// @title IV4AddressesProvider +/// @author Aave Labs +/// @notice Main registry of the Hub and Spoke addresses of an Aave V4 instance. +interface IV4AddressesProvider { + /// @notice Address entry registered under an identifier. + /// @param addr The registered address. + /// @param tag The tag grouping the entry. + struct AddressEntry { + address addr; + string tag; + } + + /// @notice Emitted when the address associated with a name and tag is updated. + /// @param id The identifier of the entry. + /// @param name The name of the entry. + /// @param tag The tag grouping the entry. + /// @param oldAddress The previous address of the entry. + /// @param newAddress The new address of the entry. + event AddressSet( + bytes32 indexed id, + string name, + string tag, + address indexed oldAddress, + address indexed newAddress + ); + + /// @notice Thrown when an empty tag is supplied. + error InvalidTag(); + + /// @notice Thrown when an empty name is supplied. + error InvalidName(); + + /// @notice Thrown when an address is already registered under the identifier. + error AddressAlreadySet(bytes32 id); + + /// @notice Thrown when no address is registered under the identifier. + error AddressNotSet(bytes32 id); + + /// @notice Returns the tag grouping all canonical Hubs. + function CANONICAL_HUB_TAG() external view returns (string memory); + + /// @notice Returns the tag grouping all canonical Spokes. + function CANONICAL_SPOKE_TAG() external view returns (string memory); + + /// @notice Returns the tag grouping all tokenization Spokes. + function TOKENIZATION_SPOKE_TAG() external view returns (string memory); + + /// @notice Returns the tag grouping all treasury Spokes. + function TREASURY_SPOKE_TAG() external view returns (string memory); + + /// @notice Associates an address with a name, grouped under a tag. + /// @dev Associating the zero address removes the entry and its identifier from enumeration, it reverts if no address is registered. + /// @dev Reverts if an address is already registered under the identifier, it must be removed first. + /// @param name The name of the entry. + /// @param tag The tag grouping the entry. + /// @param newAddress The address to associate with the name and tag. + function setAddress(string memory name, string memory tag, address newAddress) external; + + /// @notice Registers the canonical Hub associated with a name. + /// @dev Registering the zero address removes the entry and its identifier from enumeration, it reverts if no address is registered. + /// @dev Reverts if an address is already registered under the identifier, it must be removed first. + /// @param name The name of the Hub. + /// @param hub The address of the Hub. + function setCanonicalHub(string memory name, address hub) external; + + /// @notice Registers the canonical Spoke associated with a name. + /// @dev Registering the zero address removes the entry and its identifier from enumeration, it reverts if no address is registered. + /// @dev Reverts if an address is already registered under the identifier, it must be removed first. + /// @param name The name of the Spoke. + /// @param spoke The address of the Spoke. + function setCanonicalSpoke(string memory name, address spoke) external; + + /// @notice Registers the tokenization Spoke associated with a name. + /// @dev Registering the zero address removes the entry and its identifier from enumeration, it reverts if no address is registered. + /// @dev Reverts if an address is already registered under the identifier, it must be removed first. + /// @param name The name of the Spoke. + /// @param spoke The address of the Spoke. + function setTokenizationSpoke(string memory name, address spoke) external; + + /// @notice Registers the treasury Spoke associated with a name. + /// @dev Registering the zero address removes the entry and its identifier from enumeration, it reverts if no address is registered. + /// @dev Reverts if an address is already registered under the identifier, it must be removed first. + /// @param name The name of the Spoke. + /// @param spoke The address of the Spoke. + function setTreasurySpoke(string memory name, address spoke) external; + + /// @notice Returns the address associated with an identifier. + /// @param id The identifier of the entry. + /// @return The address of the entry, the zero address if none is registered. + function getAddress(bytes32 id) external view returns (address); + + /// @notice Returns the address associated with a name and tag. + /// @param name The name of the entry. + /// @param tag The tag grouping the entry. + /// @return The address of the entry, the zero address if none is registered. + function getAddress(string memory name, string memory tag) external view returns (address); + + /// @notice Returns the address entry associated with an identifier. + /// @param id The identifier of the entry. + /// @return The address entry associated with the identifier. + function getAddressEntry(bytes32 id) external view returns (AddressEntry memory); + + /// @notice Returns the identifiers of all entries grouped under a tag. + /// @param tag The tag grouping the entries. + /// @return The list of identifiers. + function getIds(string memory tag) external view returns (bytes32[] memory); + + /// @notice Returns the addresses of all entries grouped under a tag. + /// @param tag The tag grouping the entries. + /// @return The list of addresses. + function getAddresses(string memory tag) external view returns (address[] memory); + + /// @notice Returns all tags with at least one registered entry. + /// @return The list of tags. + function getTags() external view returns (string[] memory); + + /// @notice Returns the canonical Hub associated with a name. + /// @param name The name of the Hub. + /// @return The address of the Hub, the zero address if none is registered. + function getCanonicalHub(string memory name) external view returns (address); + + /// @notice Returns the addresses of all registered canonical Hubs. + /// @return The list of canonical Hub addresses. + function getCanonicalHubs() external view returns (address[] memory); + + /// @notice Returns the canonical Spoke associated with a name. + /// @param name The name of the Spoke. + /// @return The address of the Spoke, the zero address if none is registered. + function getCanonicalSpoke(string memory name) external view returns (address); + + /// @notice Returns the addresses of all registered canonical Spokes. + /// @return The list of canonical Spoke addresses. + function getCanonicalSpokes() external view returns (address[] memory); + + /// @notice Returns the tokenization Spoke associated with a name. + /// @param name The name of the Spoke. + /// @return The address of the Spoke, the zero address if none is registered. + function getTokenizationSpoke(string memory name) external view returns (address); + + /// @notice Returns the addresses of all registered tokenization Spokes. + /// @return The list of tokenization Spoke addresses. + function getTokenizationSpokes() external view returns (address[] memory); + + /// @notice Returns the treasury Spoke associated with a name. + /// @param name The name of the Spoke. + /// @return The address of the Spoke, the zero address if none is registered. + function getTreasurySpoke(string memory name) external view returns (address); + + /// @notice Returns the addresses of all registered treasury Spokes. + /// @return The list of treasury Spoke addresses. + function getTreasurySpokes() external view returns (address[] memory); + + /// @notice Returns the identifier of the entry associated with a name and tag. + /// @dev The identifier is the hash of `_` (e.g. `CORE_CANONICAL_HUB`). + /// @param name The name of the entry. + /// @param tag The tag grouping the entry. + /// @return The identifier of the entry. + function getId(string memory name, string memory tag) external pure returns (bytes32); +} diff --git a/tests/contracts/addresses-provider/V4AddressesProvider.Upgradeable.t.sol b/tests/contracts/addresses-provider/V4AddressesProvider.Upgradeable.t.sol new file mode 100644 index 000000000..441134dbe --- /dev/null +++ b/tests/contracts/addresses-provider/V4AddressesProvider.Upgradeable.t.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Test} from 'forge-std/Test.sol'; +import {Initializable} from 'src/dependencies/openzeppelin-upgradeable/Initializable.sol'; +import {OwnableUpgradeable} from 'src/dependencies/openzeppelin-upgradeable/OwnableUpgradeable.sol'; +import {Ownable2StepUpgradeable} from 'src/dependencies/openzeppelin-upgradeable/Ownable2StepUpgradeable.sol'; +import {IERC1967} from 'src/dependencies/openzeppelin/IERC1967.sol'; +import { + TransparentUpgradeableProxy, + ITransparentUpgradeableProxy +} from 'src/dependencies/openzeppelin/TransparentUpgradeableProxy.sol'; +import {V4AddressesProviderInstance} from 'src/addresses-provider/instances/V4AddressesProviderInstance.sol'; +import {MockV4AddressesProviderInstance} from 'tests/helpers/mocks/MockV4AddressesProviderInstance.sol'; +import {ProxyHelpers} from 'tests/helpers/commons/ProxyHelpers.sol'; + +contract V4AddressesProviderUpgradeableTest is Test, ProxyHelpers { + address internal OWNER = makeAddr('OWNER'); + address internal proxyAdminOwner = makeAddr('proxyAdminOwner'); + + function test_implementation_constructor_fuzz(uint64 revision) public { + address implAddress = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + vm.expectEmit(implAddress); + emit Initializable.Initialized(type(uint64).max); + + MockV4AddressesProviderInstance impl = new MockV4AddressesProviderInstance(revision); + + assertEq(address(impl), implAddress); + assertEq(impl.ADDRESSES_PROVIDER_REVISION(), revision); + assertEq(_getProxyInitializedVersion(implAddress), type(uint64).max); + + vm.expectRevert(Initializable.InvalidInitialization.selector); + impl.initialize(OWNER); + } + + function test_proxy_constructor_fuzz(uint64 revision) public { + revision = uint64(bound(revision, 1, type(uint64).max)); + + MockV4AddressesProviderInstance impl = new MockV4AddressesProviderInstance(revision); + address proxyAddress = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); + address proxyAdminAddress = vm.computeCreateAddress(proxyAddress, 1); + + vm.expectEmit(proxyAddress); + emit IERC1967.Upgraded(address(impl)); + vm.expectEmit(proxyAddress); + emit OwnableUpgradeable.OwnershipTransferred(address(0), OWNER); + vm.expectEmit(proxyAddress); + emit Initializable.Initialized(revision); + vm.expectEmit(proxyAdminAddress); + emit OwnableUpgradeable.OwnershipTransferred(address(0), proxyAdminOwner); + vm.expectEmit(proxyAddress); + emit IERC1967.AdminChanged(address(0), proxyAdminAddress); + + address proxy = _proxify(address(impl)); + + assertEq(proxy, proxyAddress); + assertEq(_getProxyAdminAddress(proxy), proxyAdminAddress); + assertEq(_getImplementationAddress(proxy), address(impl)); + + assertEq(_getProxyInitializedVersion(proxy), revision); + assertEq(Ownable2StepUpgradeable(proxy).owner(), OWNER); + } + + function test_proxy_reinitialization_fuzz(uint64 initialRevision) public { + initialRevision = uint64(bound(initialRevision, 1, type(uint64).max - 1)); + MockV4AddressesProviderInstance impl = new MockV4AddressesProviderInstance(initialRevision); + ITransparentUpgradeableProxy proxy = ITransparentUpgradeableProxy(_proxify(address(impl))); + + uint64 secondRevision = uint64(vm.randomUint(initialRevision + 1, type(uint64).max)); + MockV4AddressesProviderInstance impl2 = new MockV4AddressesProviderInstance(secondRevision); + + vm.expectEmit(address(proxy)); + emit OwnableUpgradeable.OwnershipTransferred(OWNER, OWNER); + vm.prank(_getProxyAdminAddress(address(proxy))); + proxy.upgradeToAndCall( + address(impl2), + abi.encodeCall(MockV4AddressesProviderInstance.initialize, (OWNER)) + ); + + assertEq(Ownable2StepUpgradeable(address(proxy)).owner(), OWNER); + } + + function test_proxy_constructor_revertsWith_InvalidInitialization_ZeroRevision() public { + MockV4AddressesProviderInstance impl = new MockV4AddressesProviderInstance(0); + + vm.expectRevert(Initializable.InvalidInitialization.selector); + _proxify(address(impl)); + } + + function test_proxy_constructor_fuzz_revertsWith_InvalidInitialization( + uint64 initialRevision + ) public { + initialRevision = uint64(bound(initialRevision, 1, type(uint64).max)); + + MockV4AddressesProviderInstance impl = new MockV4AddressesProviderInstance(initialRevision); + ITransparentUpgradeableProxy proxy = ITransparentUpgradeableProxy(_proxify(address(impl))); + + vm.expectRevert(Initializable.InvalidInitialization.selector); + vm.prank(_getProxyAdminAddress(address(proxy))); + proxy.upgradeToAndCall( + address(impl), + abi.encodeCall(MockV4AddressesProviderInstance.initialize, (OWNER)) + ); + + uint64 secondRevision = uint64(vm.randomUint(0, initialRevision)); + MockV4AddressesProviderInstance impl2 = new MockV4AddressesProviderInstance(secondRevision); + vm.expectRevert(Initializable.InvalidInitialization.selector); + vm.prank(_getProxyAdminAddress(address(proxy))); + proxy.upgradeToAndCall( + address(impl2), + abi.encodeCall(MockV4AddressesProviderInstance.initialize, (OWNER)) + ); + } + + function test_proxy_constructor_revertsWith_InvalidAddress() public { + V4AddressesProviderInstance impl = new V4AddressesProviderInstance(); + vm.expectRevert( + abi.encodeWithSelector(OwnableUpgradeable.OwnableInvalidOwner.selector, address(0)) + ); + new TransparentUpgradeableProxy( + address(impl), + proxyAdminOwner, + abi.encodeCall(V4AddressesProviderInstance.initialize, (address(0))) + ); + } + + function test_proxy_reinitialization_revertsWith_CallerNotProxyAdmin() public { + V4AddressesProviderInstance impl = new V4AddressesProviderInstance(); + ITransparentUpgradeableProxy proxy = ITransparentUpgradeableProxy(_proxify(address(impl))); + + V4AddressesProviderInstance impl2 = new V4AddressesProviderInstance(); + vm.expectRevert(); + vm.prank(makeAddr('user')); + proxy.upgradeToAndCall( + address(impl2), + abi.encodeCall(V4AddressesProviderInstance.initialize, (OWNER)) + ); + } + + function _proxify(address impl) internal returns (address) { + return + address( + new TransparentUpgradeableProxy( + impl, + proxyAdminOwner, + abi.encodeCall(V4AddressesProviderInstance.initialize, (OWNER)) + ) + ); + } +} diff --git a/tests/contracts/addresses-provider/V4AddressesProvider.t.sol b/tests/contracts/addresses-provider/V4AddressesProvider.t.sol new file mode 100644 index 000000000..8fd57cf8a --- /dev/null +++ b/tests/contracts/addresses-provider/V4AddressesProvider.t.sol @@ -0,0 +1,626 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Test} from 'forge-std/Test.sol'; +import {OwnableUpgradeable} from 'src/dependencies/openzeppelin-upgradeable/OwnableUpgradeable.sol'; +import {TransparentUpgradeableProxy} from 'src/dependencies/openzeppelin/TransparentUpgradeableProxy.sol'; +import {V4AddressesProvider} from 'src/addresses-provider/V4AddressesProvider.sol'; +import {V4AddressesProviderInstance} from 'src/addresses-provider/instances/V4AddressesProviderInstance.sol'; +import {IV4AddressesProvider} from 'src/addresses-provider/interfaces/IV4AddressesProvider.sol'; + +contract V4AddressesProviderTest is Test { + address internal OWNER = makeAddr('OWNER'); + address internal PROXY_ADMIN_OWNER = makeAddr('PROXY_ADMIN_OWNER'); + + V4AddressesProvider internal provider; + + function setUp() public { + provider = V4AddressesProvider( + address( + new TransparentUpgradeableProxy( + address(new V4AddressesProviderInstance()), + PROXY_ADMIN_OWNER, + abi.encodeCall(V4AddressesProviderInstance.initialize, (OWNER)) + ) + ) + ); + } + + function test_initialize() public view { + assertEq(provider.owner(), OWNER); + assertEq(provider.CANONICAL_HUB_TAG(), 'CANONICAL_HUB'); + assertEq(provider.CANONICAL_SPOKE_TAG(), 'CANONICAL_SPOKE'); + assertEq(provider.TOKENIZATION_SPOKE_TAG(), 'TOKENIZATION_SPOKE'); + assertEq(provider.TREASURY_SPOKE_TAG(), 'TREASURY_SPOKE'); + } + + function test_transferOwnership_twoStep() public { + address newOwner = makeAddr('NEW_OWNER'); + + vm.prank(OWNER); + provider.transferOwnership(newOwner); + + assertEq(provider.owner(), OWNER); + assertEq(provider.pendingOwner(), newOwner); + + vm.prank(newOwner); + provider.acceptOwnership(); + + assertEq(provider.owner(), newOwner); + assertEq(provider.pendingOwner(), address(0)); + } + + function test_getId() public view { + assertEq( + provider.getId({name: 'CORE', tag: provider.CANONICAL_HUB_TAG()}), + keccak256(bytes('CORE_CANONICAL_HUB')) + ); + assertEq( + provider.getId({name: 'MAIN', tag: provider.CANONICAL_SPOKE_TAG()}), + keccak256(bytes('MAIN_CANONICAL_SPOKE')) + ); + assertEq( + provider.getId({name: 'CORE_WETH', tag: provider.TOKENIZATION_SPOKE_TAG()}), + keccak256(bytes('CORE_WETH_TOKENIZATION_SPOKE')) + ); + assertEq( + provider.getId({name: 'MAIN', tag: provider.TREASURY_SPOKE_TAG()}), + keccak256(bytes('MAIN_TREASURY_SPOKE')) + ); + } + + function test_setAddress() public { + bytes32 id = provider.getId({name: 'CONFIG_ENGINE', tag: 'PERIPHERY'}); + address configEngine = makeAddr('CONFIG_ENGINE'); + + vm.expectEmit(address(provider)); + emit IV4AddressesProvider.AddressSet( + id, + 'CONFIG_ENGINE', + 'PERIPHERY', + address(0), + configEngine + ); + + vm.prank(OWNER); + provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: configEngine}); + + assertEq(provider.getAddress(id), configEngine); + assertEq(provider.getAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY'}), configEngine); + + IV4AddressesProvider.AddressEntry memory entry = provider.getAddressEntry(id); + assertEq(entry.addr, configEngine); + assertEq(entry.tag, 'PERIPHERY'); + + bytes32[] memory ids = provider.getIds('PERIPHERY'); + assertEq(ids.length, 1); + assertEq(ids[0], id); + + string[] memory tags = provider.getTags(); + assertEq(tags.length, 1); + assertEq(tags[0], 'PERIPHERY'); + } + + function test_setAddress_remove() public { + bytes32 id = provider.getId({name: 'CONFIG_ENGINE', tag: 'PERIPHERY'}); + + vm.startPrank(OWNER); + provider.setAddress({ + name: 'CONFIG_ENGINE', + tag: 'PERIPHERY', + newAddress: makeAddr('CONFIG_ENGINE') + }); + provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); + vm.stopPrank(); + + assertEq(provider.getAddress(id), address(0)); + assertEq(provider.getAddressEntry(id).tag, ''); + assertEq(provider.getIds('PERIPHERY').length, 0); + assertEq(provider.getTags().length, 0); + } + + function test_setAddress_removeThenSet() public { + bytes32 id = provider.getId({name: 'CONFIG_ENGINE', tag: 'PERIPHERY'}); + address newConfigEngine = makeAddr('NEW_CONFIG_ENGINE'); + + vm.startPrank(OWNER); + provider.setAddress({ + name: 'CONFIG_ENGINE', + tag: 'PERIPHERY', + newAddress: makeAddr('CONFIG_ENGINE') + }); + provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); + provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: newConfigEngine}); + vm.stopPrank(); + + assertEq(provider.getAddress(id), newConfigEngine); + + bytes32[] memory ids = provider.getIds('PERIPHERY'); + assertEq(ids.length, 1); + assertEq(ids[0], id); + } + + function test_setAddress_revertsWith_AddressAlreadySet() public { + bytes32 id = provider.getId({name: 'CONFIG_ENGINE', tag: 'PERIPHERY'}); + address configEngine = makeAddr('CONFIG_ENGINE'); + + vm.startPrank(OWNER); + provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: configEngine}); + + vm.expectRevert(abi.encodeWithSelector(IV4AddressesProvider.AddressAlreadySet.selector, id)); + provider.setAddress({ + name: 'CONFIG_ENGINE', + tag: 'PERIPHERY', + newAddress: makeAddr('NEW_CONFIG_ENGINE') + }); + + vm.expectRevert(abi.encodeWithSelector(IV4AddressesProvider.AddressAlreadySet.selector, id)); + provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: configEngine}); + vm.stopPrank(); + } + + function test_setAddress_idCollision_revertsWith_AddressAlreadySet() public { + bytes32 id = provider.getId({name: 'A', tag: 'B_C'}); + assertEq(id, provider.getId({name: 'A_B', tag: 'C'})); + + vm.startPrank(OWNER); + provider.setAddress({name: 'A', tag: 'B_C', newAddress: makeAddr('A')}); + + vm.expectRevert(abi.encodeWithSelector(IV4AddressesProvider.AddressAlreadySet.selector, id)); + provider.setAddress({name: 'A_B', tag: 'C', newAddress: makeAddr('A_B')}); + vm.stopPrank(); + } + + function test_setAddress_sameAddressUnderMultipleIds() public { + address configEngine = makeAddr('CONFIG_ENGINE'); + + vm.startPrank(OWNER); + provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: configEngine}); + provider.setAddress({name: 'ENGINE', tag: 'PERIPHERY', newAddress: configEngine}); + provider.setAddress({name: 'CONFIG_ENGINE', tag: 'ENGINE', newAddress: configEngine}); + provider.setAddress({name: 'V3_CONFIG_ENGINE', tag: 'V3_PERIPHERY', newAddress: configEngine}); + vm.stopPrank(); + + assertEq(provider.getAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY'}), configEngine); + assertEq(provider.getAddress({name: 'ENGINE', tag: 'PERIPHERY'}), configEngine); + assertEq(provider.getAddress({name: 'CONFIG_ENGINE', tag: 'ENGINE'}), configEngine); + assertEq(provider.getAddress({name: 'V3_CONFIG_ENGINE', tag: 'V3_PERIPHERY'}), configEngine); + + bytes32[] memory peripheryIds = provider.getIds('PERIPHERY'); + assertEq(peripheryIds.length, 2); + assertEq(peripheryIds[0], provider.getId({name: 'CONFIG_ENGINE', tag: 'PERIPHERY'})); + assertEq(peripheryIds[1], provider.getId({name: 'ENGINE', tag: 'PERIPHERY'})); + + string[] memory tags = provider.getTags(); + assertEq(tags.length, 3); + assertEq(tags[0], 'PERIPHERY'); + assertEq(tags[1], 'ENGINE'); + assertEq(tags[2], 'V3_PERIPHERY'); + + // removing one entry does not affect the other entries of the same address + vm.prank(OWNER); + provider.setAddress({name: 'ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); + + assertEq(provider.getAddress({name: 'ENGINE', tag: 'PERIPHERY'}), address(0)); + assertEq(provider.getAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY'}), configEngine); + assertEq(provider.getAddress({name: 'CONFIG_ENGINE', tag: 'ENGINE'}), configEngine); + assertEq(provider.getIds('PERIPHERY').length, 1); + } + + function test_setHubAndSpoke_sameAddressAcrossTags() public { + address sharedSpoke = makeAddr('SHARED_SPOKE'); + + vm.startPrank(OWNER); + provider.setCanonicalSpoke('MAIN', sharedSpoke); + provider.setTokenizationSpoke('MAIN', sharedSpoke); + provider.setTreasurySpoke('MAIN', sharedSpoke); + vm.stopPrank(); + + assertEq(provider.getCanonicalSpoke('MAIN'), sharedSpoke); + assertEq(provider.getTokenizationSpoke('MAIN'), sharedSpoke); + assertEq(provider.getTreasurySpoke('MAIN'), sharedSpoke); + + address[] memory canonicalSpokes = provider.getCanonicalSpokes(); + assertEq(canonicalSpokes.length, 1); + assertEq(canonicalSpokes[0], sharedSpoke); + + address[] memory tokenizationSpokes = provider.getTokenizationSpokes(); + assertEq(tokenizationSpokes.length, 1); + assertEq(tokenizationSpokes[0], sharedSpoke); + + address[] memory treasurySpokes = provider.getTreasurySpokes(); + assertEq(treasurySpokes.length, 1); + assertEq(treasurySpokes[0], sharedSpoke); + } + + function test_setAddress_remove_revertsWith_AddressNotSet() public { + bytes32 id = provider.getId({name: 'CONFIG_ENGINE', tag: 'PERIPHERY'}); + + vm.startPrank(OWNER); + vm.expectRevert(abi.encodeWithSelector(IV4AddressesProvider.AddressNotSet.selector, id)); + provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); + + provider.setAddress({ + name: 'CONFIG_ENGINE', + tag: 'PERIPHERY', + newAddress: makeAddr('CONFIG_ENGINE') + }); + provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); + + vm.expectRevert(abi.encodeWithSelector(IV4AddressesProvider.AddressNotSet.selector, id)); + provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); + vm.stopPrank(); + } + + function test_setAddress_revertsWith_InvalidName() public { + vm.expectRevert(IV4AddressesProvider.InvalidName.selector); + vm.prank(OWNER); + provider.setAddress({name: '', tag: 'PERIPHERY', newAddress: makeAddr('CONFIG_ENGINE')}); + } + + function test_setAddress_revertsWith_InvalidTag() public { + vm.expectRevert(IV4AddressesProvider.InvalidTag.selector); + vm.prank(OWNER); + provider.setAddress({name: 'CONFIG_ENGINE', tag: '', newAddress: makeAddr('CONFIG_ENGINE')}); + } + + function test_setAddress_revertsWith_OwnableUnauthorizedAccount() public { + address caller = makeAddr('caller'); + + vm.expectRevert( + abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, caller) + ); + vm.prank(caller); + provider.setAddress({ + name: 'CONFIG_ENGINE', + tag: 'PERIPHERY', + newAddress: makeAddr('CONFIG_ENGINE') + }); + } + + function test_setCanonicalHub() public { + address coreHub = makeAddr('CORE_HUB'); + address plusHub = makeAddr('PLUS_HUB'); + address primeHub = makeAddr('PRIME_HUB'); + + vm.startPrank(OWNER); + vm.expectEmit(address(provider)); + emit IV4AddressesProvider.AddressSet( + keccak256(bytes('CORE_CANONICAL_HUB')), + 'CORE', + 'CANONICAL_HUB', + address(0), + coreHub + ); + provider.setCanonicalHub('CORE', coreHub); + provider.setCanonicalHub('PLUS', plusHub); + provider.setCanonicalHub('PRIME', primeHub); + vm.stopPrank(); + + assertEq(provider.getCanonicalHub('CORE'), coreHub); + assertEq(provider.getCanonicalHub('PLUS'), plusHub); + assertEq(provider.getCanonicalHub('PRIME'), primeHub); + assertEq(provider.getAddress(keccak256(bytes('CORE_CANONICAL_HUB'))), coreHub); + assertEq(provider.getAddressEntry(keccak256(bytes('CORE_CANONICAL_HUB'))).tag, 'CANONICAL_HUB'); + + bytes32[] memory hubIds = provider.getIds('CANONICAL_HUB'); + assertEq(hubIds.length, 3); + assertEq(hubIds[0], keccak256(bytes('CORE_CANONICAL_HUB'))); + assertEq(hubIds[1], keccak256(bytes('PLUS_CANONICAL_HUB'))); + assertEq(hubIds[2], keccak256(bytes('PRIME_CANONICAL_HUB'))); + + address[] memory hubs = provider.getCanonicalHubs(); + assertEq(hubs.length, 3); + assertEq(hubs[0], coreHub); + assertEq(hubs[1], plusHub); + assertEq(hubs[2], primeHub); + assertEq(provider.getAddresses(provider.CANONICAL_HUB_TAG()), hubs); + } + + function test_setCanonicalHub_removeThenSet() public { + address coreHub = makeAddr('CORE_HUB'); + address newCoreHub = makeAddr('NEW_CORE_HUB'); + + vm.startPrank(OWNER); + provider.setCanonicalHub('CORE', coreHub); + provider.setCanonicalHub('CORE', address(0)); + + vm.expectEmit(address(provider)); + emit IV4AddressesProvider.AddressSet( + keccak256(bytes('CORE_CANONICAL_HUB')), + 'CORE', + 'CANONICAL_HUB', + address(0), + newCoreHub + ); + provider.setCanonicalHub('CORE', newCoreHub); + vm.stopPrank(); + + assertEq(provider.getCanonicalHub('CORE'), newCoreHub); + assertEq(provider.getCanonicalHubs().length, 1); + } + + function test_setCanonicalHub_revertsWith_AddressAlreadySet() public { + vm.startPrank(OWNER); + provider.setCanonicalHub('CORE', makeAddr('CORE_HUB')); + + vm.expectRevert( + abi.encodeWithSelector( + IV4AddressesProvider.AddressAlreadySet.selector, + keccak256(bytes('CORE_CANONICAL_HUB')) + ) + ); + provider.setCanonicalHub('CORE', makeAddr('NEW_CORE_HUB')); + vm.stopPrank(); + } + + function test_setCanonicalHub_remove() public { + vm.startPrank(OWNER); + provider.setCanonicalHub('CORE', makeAddr('CORE_HUB')); + provider.setCanonicalHub('PLUS', makeAddr('PLUS_HUB')); + provider.setCanonicalHub('CORE', address(0)); + vm.stopPrank(); + + assertEq(provider.getCanonicalHub('CORE'), address(0)); + + address[] memory hubs = provider.getCanonicalHubs(); + assertEq(hubs.length, 1); + assertEq(hubs[0], provider.getCanonicalHub('PLUS')); + } + + function test_setCanonicalHub_remove_revertsWith_AddressNotSet() public { + vm.expectRevert( + abi.encodeWithSelector( + IV4AddressesProvider.AddressNotSet.selector, + keccak256(bytes('CORE_CANONICAL_HUB')) + ) + ); + vm.prank(OWNER); + provider.setCanonicalHub('CORE', address(0)); + } + + function test_setCanonicalHub_revertsWith_InvalidName() public { + vm.expectRevert(IV4AddressesProvider.InvalidName.selector); + vm.prank(OWNER); + provider.setCanonicalHub('', makeAddr('CORE_HUB')); + } + + function test_setCanonicalHub_revertsWith_OwnableUnauthorizedAccount() public { + address caller = makeAddr('caller'); + + vm.expectRevert( + abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, caller) + ); + vm.prank(caller); + provider.setCanonicalHub('CORE', makeAddr('CORE_HUB')); + } + + function test_setCanonicalHub_fuzz(string memory name, address hub) public { + vm.assume(bytes(name).length > 0); + vm.assume(hub != address(0)); + + vm.prank(OWNER); + provider.setCanonicalHub(name, hub); + + assertEq(provider.getCanonicalHub(name), hub); + + address[] memory hubs = provider.getCanonicalHubs(); + assertEq(hubs.length, 1); + assertEq(hubs[0], hub); + } + + function test_setCanonicalSpoke() public { + address mainSpoke = makeAddr('MAIN_SPOKE'); + address bluechipSpoke = makeAddr('BLUECHIP_SPOKE'); + address forexSpoke = makeAddr('FOREX_SPOKE'); + + vm.startPrank(OWNER); + vm.expectEmit(address(provider)); + emit IV4AddressesProvider.AddressSet( + keccak256(bytes('MAIN_CANONICAL_SPOKE')), + 'MAIN', + 'CANONICAL_SPOKE', + address(0), + mainSpoke + ); + provider.setCanonicalSpoke('MAIN', mainSpoke); + provider.setCanonicalSpoke('BLUECHIP', bluechipSpoke); + provider.setCanonicalSpoke('FOREX', forexSpoke); + vm.stopPrank(); + + assertEq(provider.getCanonicalSpoke('MAIN'), mainSpoke); + assertEq(provider.getCanonicalSpoke('BLUECHIP'), bluechipSpoke); + assertEq(provider.getCanonicalSpoke('FOREX'), forexSpoke); + assertEq(provider.getAddress(keccak256(bytes('MAIN_CANONICAL_SPOKE'))), mainSpoke); + + address[] memory canonicalSpokes = provider.getCanonicalSpokes(); + assertEq(canonicalSpokes.length, 3); + assertEq(canonicalSpokes[0], mainSpoke); + assertEq(canonicalSpokes[1], bluechipSpoke); + assertEq(canonicalSpokes[2], forexSpoke); + } + + function test_setTokenizationSpoke() public { + address coreWethSpoke = makeAddr('CORE_WETH_TOKENIZATION_SPOKE'); + address primeGhoSpoke = makeAddr('PRIME_GHO_TOKENIZATION_SPOKE'); + + vm.startPrank(OWNER); + provider.setTokenizationSpoke('CORE_WETH', coreWethSpoke); + provider.setTokenizationSpoke('PRIME_GHO', primeGhoSpoke); + vm.stopPrank(); + + assertEq(provider.getTokenizationSpoke('CORE_WETH'), coreWethSpoke); + assertEq(provider.getTokenizationSpoke('PRIME_GHO'), primeGhoSpoke); + assertEq(provider.getAddress(keccak256(bytes('CORE_WETH_TOKENIZATION_SPOKE'))), coreWethSpoke); + + address[] memory tokenizationSpokes = provider.getTokenizationSpokes(); + assertEq(tokenizationSpokes.length, 2); + assertEq(tokenizationSpokes[0], coreWethSpoke); + assertEq(tokenizationSpokes[1], primeGhoSpoke); + } + + function test_setTreasurySpoke() public { + address treasurySpoke = makeAddr('TREASURY_SPOKE'); + + vm.prank(OWNER); + provider.setTreasurySpoke('MAIN', treasurySpoke); + + assertEq(provider.getTreasurySpoke('MAIN'), treasurySpoke); + assertEq(provider.getAddress(keccak256(bytes('MAIN_TREASURY_SPOKE'))), treasurySpoke); + + address[] memory treasurySpokes = provider.getTreasurySpokes(); + assertEq(treasurySpokes.length, 1); + assertEq(treasurySpokes[0], treasurySpoke); + } + + function test_setSpoke_sameNameAcrossTags() public { + address mainSpoke = makeAddr('MAIN_SPOKE'); + address treasurySpoke = makeAddr('TREASURY_SPOKE'); + + vm.startPrank(OWNER); + provider.setCanonicalSpoke('MAIN', mainSpoke); + provider.setTreasurySpoke('MAIN', treasurySpoke); + vm.stopPrank(); + + assertEq(provider.getCanonicalSpoke('MAIN'), mainSpoke); + assertEq(provider.getTreasurySpoke('MAIN'), treasurySpoke); + } + + function test_setSpoke_removeThenSet() public { + address mainSpoke = makeAddr('MAIN_SPOKE'); + address newMainSpoke = makeAddr('NEW_MAIN_SPOKE'); + + vm.startPrank(OWNER); + provider.setCanonicalSpoke('MAIN', mainSpoke); + provider.setCanonicalSpoke('MAIN', address(0)); + provider.setCanonicalSpoke('MAIN', newMainSpoke); + vm.stopPrank(); + + assertEq(provider.getCanonicalSpoke('MAIN'), newMainSpoke); + assertEq(provider.getCanonicalSpokes().length, 1); + } + + function test_setSpoke_revertsWith_AddressAlreadySet() public { + vm.startPrank(OWNER); + provider.setCanonicalSpoke('MAIN', makeAddr('MAIN_SPOKE')); + + vm.expectRevert( + abi.encodeWithSelector( + IV4AddressesProvider.AddressAlreadySet.selector, + keccak256(bytes('MAIN_CANONICAL_SPOKE')) + ) + ); + provider.setCanonicalSpoke('MAIN', makeAddr('NEW_MAIN_SPOKE')); + vm.stopPrank(); + } + + function test_setSpoke_remove() public { + vm.startPrank(OWNER); + provider.setCanonicalSpoke('MAIN', makeAddr('MAIN_SPOKE')); + provider.setCanonicalSpoke('BLUECHIP', makeAddr('BLUECHIP_SPOKE')); + provider.setTreasurySpoke('MAIN', makeAddr('TREASURY_SPOKE')); + + provider.setCanonicalSpoke('MAIN', address(0)); + vm.stopPrank(); + + assertEq(provider.getCanonicalSpoke('MAIN'), address(0)); + + address[] memory canonicalSpokes = provider.getCanonicalSpokes(); + assertEq(canonicalSpokes.length, 1); + assertEq(canonicalSpokes[0], provider.getCanonicalSpoke('BLUECHIP')); + assertEq(provider.getTreasurySpokes().length, 1); + } + + function test_setSpoke_removeLastIdOfTag() public { + vm.startPrank(OWNER); + provider.setCanonicalSpoke('MAIN', makeAddr('MAIN_SPOKE')); + provider.setTreasurySpoke('MAIN', makeAddr('TREASURY_SPOKE')); + + provider.setTreasurySpoke('MAIN', address(0)); + vm.stopPrank(); + + assertEq(provider.getTreasurySpokes().length, 0); + + string[] memory tags = provider.getTags(); + assertEq(tags.length, 1); + assertEq(tags[0], 'CANONICAL_SPOKE'); + } + + function test_setSpoke_remove_revertsWith_AddressNotSet() public { + vm.expectRevert( + abi.encodeWithSelector( + IV4AddressesProvider.AddressNotSet.selector, + keccak256(bytes('MAIN_CANONICAL_SPOKE')) + ) + ); + vm.prank(OWNER); + provider.setCanonicalSpoke('MAIN', address(0)); + } + + function test_setSpoke_revertsWith_InvalidName() public { + vm.startPrank(OWNER); + + vm.expectRevert(IV4AddressesProvider.InvalidName.selector); + provider.setCanonicalSpoke('', makeAddr('MAIN_SPOKE')); + + vm.expectRevert(IV4AddressesProvider.InvalidName.selector); + provider.setTokenizationSpoke('', makeAddr('CORE_WETH_TOKENIZATION_SPOKE')); + + vm.expectRevert(IV4AddressesProvider.InvalidName.selector); + provider.setTreasurySpoke('', makeAddr('TREASURY_SPOKE')); + + vm.stopPrank(); + } + + function test_setSpoke_revertsWith_OwnableUnauthorizedAccount() public { + address caller = makeAddr('caller'); + vm.startPrank(caller); + + vm.expectRevert( + abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, caller) + ); + provider.setCanonicalSpoke('MAIN', makeAddr('MAIN_SPOKE')); + + vm.expectRevert( + abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, caller) + ); + provider.setTokenizationSpoke('CORE_WETH', makeAddr('CORE_WETH_TOKENIZATION_SPOKE')); + + vm.expectRevert( + abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, caller) + ); + provider.setTreasurySpoke('MAIN', makeAddr('TREASURY_SPOKE')); + + vm.stopPrank(); + } + + function test_setSpoke_fuzz(string memory name, address spoke) public { + vm.assume(bytes(name).length > 0); + vm.assume(spoke != address(0)); + + vm.prank(OWNER); + provider.setCanonicalSpoke(name, spoke); + + assertEq(provider.getCanonicalSpoke(name), spoke); + + address[] memory canonicalSpokes = provider.getCanonicalSpokes(); + assertEq(canonicalSpokes.length, 1); + assertEq(canonicalSpokes[0], spoke); + } + + function test_getTags() public { + vm.startPrank(OWNER); + provider.setCanonicalHub('CORE', makeAddr('CORE_HUB')); + provider.setCanonicalSpoke('MAIN', makeAddr('MAIN_SPOKE')); + provider.setTokenizationSpoke('CORE_WETH', makeAddr('CORE_WETH_TOKENIZATION_SPOKE')); + provider.setTreasurySpoke('MAIN', makeAddr('TREASURY_SPOKE')); + vm.stopPrank(); + + string[] memory tags = provider.getTags(); + assertEq(tags.length, 4); + assertEq(tags[0], 'CANONICAL_HUB'); + assertEq(tags[1], 'CANONICAL_SPOKE'); + assertEq(tags[2], 'TOKENIZATION_SPOKE'); + assertEq(tags[3], 'TREASURY_SPOKE'); + } +} diff --git a/tests/helpers/mocks/MockV4AddressesProviderInstance.sol b/tests/helpers/mocks/MockV4AddressesProviderInstance.sol new file mode 100644 index 000000000..e8b3a5e77 --- /dev/null +++ b/tests/helpers/mocks/MockV4AddressesProviderInstance.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {V4AddressesProvider} from 'src/addresses-provider/V4AddressesProvider.sol'; + +contract MockV4AddressesProviderInstance is V4AddressesProvider { + bool public constant IS_TEST = true; + + uint64 public immutable ADDRESSES_PROVIDER_REVISION; + + /** + * @dev Constructor. + * @dev It sets the addresses provider revision and disables the initializers. + * @param addressesProviderRevision_ The revision of the addresses provider contract. + */ + constructor(uint64 addressesProviderRevision_) { + ADDRESSES_PROVIDER_REVISION = addressesProviderRevision_; + _disableInitializers(); + } + + /// @inheritdoc V4AddressesProvider + function initialize(address owner) external override reinitializer(ADDRESSES_PROVIDER_REVISION) { + __Ownable_init(owner); + __Ownable2Step_init(); + } +} From a826a62f578f9156e0a38973b6733723789f9e39 Mon Sep 17 00:00:00 2001 From: Alexandru Niculae <43644109+avniculae@users.noreply.github.com> Date: Thu, 11 Jun 2026 18:24:04 +0300 Subject: [PATCH 02/15] chore: point gh workflows to aave-dao --- .github/workflows/comment.yml | 2 +- .github/workflows/tests-merge.yml | 8 ++++---- .github/workflows/tests-pr.yml | 12 ++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/comment.yml b/.github/workflows/comment.yml index eb90db07b..f29cc5fc0 100644 --- a/.github/workflows/comment.yml +++ b/.github/workflows/comment.yml @@ -16,6 +16,6 @@ permissions: jobs: comment: - uses: bgd-labs/github-workflows/.github/workflows/comment.yml@main + uses: aave-dao/github-workflows/.github/workflows/comment.yml@main secrets: READ_ONLY_PAT: ${{ secrets.READ_ONLY_PAT }} diff --git a/.github/workflows/tests-merge.yml b/.github/workflows/tests-merge.yml index af755eeda..0cb529db5 100644 --- a/.github/workflows/tests-merge.yml +++ b/.github/workflows/tests-merge.yml @@ -9,7 +9,7 @@ on: jobs: lint: name: Prettier lint check - uses: bgd-labs/github-workflows/.github/workflows/foundry-lint-prettier.yml@main + uses: aave-dao/github-workflows/.github/workflows/foundry-lint-prettier.yml@main test: name: Foundry build n test runs-on: ubuntu-latest @@ -20,15 +20,15 @@ jobs: token: ${{ secrets.READ_ONLY_PAT || github.token }} - name: Run Foundry setup - uses: bgd-labs/github-workflows/.github/actions/foundry-setup@main + uses: aave-dao/github-workflows/.github/actions/foundry-setup@main with: FOUNDRY_VERSION: stable - name: Run Forge size - uses: bgd-labs/github-workflows/.github/actions/foundry-size@main + uses: aave-dao/github-workflows/.github/actions/foundry-size@main - name: Run Forge tests id: test - uses: bgd-labs/github-workflows/.github/actions/foundry-test@main + uses: aave-dao/github-workflows/.github/actions/foundry-test@main with: FOUNDRY_PROFILE: ci diff --git a/.github/workflows/tests-pr.yml b/.github/workflows/tests-pr.yml index cb1ff90f1..498874e4c 100644 --- a/.github/workflows/tests-pr.yml +++ b/.github/workflows/tests-pr.yml @@ -6,7 +6,7 @@ on: jobs: lint: name: Prettier lint check - uses: bgd-labs/github-workflows/.github/workflows/foundry-lint-prettier.yml@main + uses: aave-dao/github-workflows/.github/workflows/foundry-lint-prettier.yml@main test: name: Foundry build n test runs-on: ubuntu-latest @@ -17,23 +17,23 @@ jobs: token: ${{ secrets.READ_ONLY_PAT || github.token }} - name: Run Foundry setup - uses: bgd-labs/github-workflows/.github/actions/foundry-setup@main + uses: aave-dao/github-workflows/.github/actions/foundry-setup@main with: FOUNDRY_VERSION: stable - name: Run Forge size - uses: bgd-labs/github-workflows/.github/actions/foundry-size@main + uses: aave-dao/github-workflows/.github/actions/foundry-size@main - name: Run Gas report - uses: bgd-labs/github-workflows/.github/actions/foundry-gas-report@main + uses: aave-dao/github-workflows/.github/actions/foundry-gas-report@main - name: Run Forge tests - uses: bgd-labs/github-workflows/.github/actions/foundry-test@main + uses: aave-dao/github-workflows/.github/actions/foundry-test@main with: FOUNDRY_PROFILE: pr FORGE_SNAPSHOT_CHECK: true - name: Upload & Trigger Comment artifact - uses: bgd-labs/github-workflows/.github/actions/comment-artifact@main + uses: aave-dao/github-workflows/.github/actions/comment-artifact@main with: nameSuffix: "default" From a82f500113092a1f39c6e077ce8b2a7f304b2c16 Mon Sep 17 00:00:00 2001 From: Alexandru Niculae <43644109+avniculae@users.noreply.github.com> Date: Thu, 11 Jun 2026 19:14:56 +0300 Subject: [PATCH 03/15] feat: add deployment procedures --- .../batches/AaveV4AddressesProviderBatch.sol | 31 ++++++++++++++++ src/deployments/libraries/BatchReports.sol | 7 ++++ ...AaveV4AddressesProviderDeployProcedure.sol | 34 ++++++++++++++++++ .../procedures/ProceduresBase.t.sol | 1 + ...veV4AddressesProviderDeployProcedure.t.sol | 36 +++++++++++++++++++ ...ddressesProviderDeployProcedureWrapper.sol | 15 ++++++++ 6 files changed, 124 insertions(+) create mode 100644 src/deployments/batches/AaveV4AddressesProviderBatch.sol create mode 100644 src/deployments/procedures/deploy/addresses-provider/AaveV4AddressesProviderDeployProcedure.sol create mode 100644 tests/deployments/procedures/deploy/addresses-provider/AaveV4AddressesProviderDeployProcedure.t.sol create mode 100644 tests/helpers/mocks/deployments/procedures/AaveV4AddressesProviderDeployProcedureWrapper.sol diff --git a/src/deployments/batches/AaveV4AddressesProviderBatch.sol b/src/deployments/batches/AaveV4AddressesProviderBatch.sol new file mode 100644 index 000000000..f8de27ddb --- /dev/null +++ b/src/deployments/batches/AaveV4AddressesProviderBatch.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: LicenseRef-BUSL +pragma solidity ^0.8.0; + +import {BatchReports} from 'src/deployments/libraries/BatchReports.sol'; +import {AaveV4AddressesProviderDeployProcedure} from 'src/deployments/procedures/deploy/addresses-provider/AaveV4AddressesProviderDeployProcedure.sol'; + +/// @title AaveV4AddressesProviderBatch +/// @author Aave Labs +/// @notice Deploys the V4AddressesProvider contract, producing a batch report. +contract AaveV4AddressesProviderBatch is AaveV4AddressesProviderDeployProcedure { + BatchReports.AddressesProviderBatchReport internal _report; + + /// @dev Constructor. + /// @param owner_ The owner of the V4AddressesProvider proxy admin and initializer. + /// @param salt_ The CREATE2 salt for deterministic deployment. + constructor(address owner_, bytes32 salt_) { + ( + address addressesProviderProxy, + address addressesProviderImplementation + ) = _deployAddressesProvider({owner: owner_, salt: salt_}); + _report = BatchReports.AddressesProviderBatchReport({ + addressesProviderProxy: addressesProviderProxy, + addressesProviderImplementation: addressesProviderImplementation + }); + } + + /// @notice Returns the batch deployment report. + function getReport() external view returns (BatchReports.AddressesProviderBatchReport memory) { + return _report; + } +} diff --git a/src/deployments/libraries/BatchReports.sol b/src/deployments/libraries/BatchReports.sol index b05070568..a90ec0cc0 100644 --- a/src/deployments/libraries/BatchReports.sol +++ b/src/deployments/libraries/BatchReports.sol @@ -40,6 +40,13 @@ library BatchReports { address treasurySpoke; } + /// @dev addressesProviderProxy The deployed V4AddressesProvider proxy contract address. + /// @dev addressesProviderImplementation The deployed V4AddressesProvider implementation contract address. + struct AddressesProviderBatchReport { + address addressesProviderProxy; + address addressesProviderImplementation; + } + /// @dev signatureGateway The deployed SignatureGateway contract address. /// @dev nativeGateway The deployed NativeTokenGateway contract address. struct GatewaysBatchReport { diff --git a/src/deployments/procedures/deploy/addresses-provider/AaveV4AddressesProviderDeployProcedure.sol b/src/deployments/procedures/deploy/addresses-provider/AaveV4AddressesProviderDeployProcedure.sol new file mode 100644 index 000000000..b02d0ee05 --- /dev/null +++ b/src/deployments/procedures/deploy/addresses-provider/AaveV4AddressesProviderDeployProcedure.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: LicenseRef-BUSL +pragma solidity ^0.8.0; + +import {AaveV4DeployProcedureBase} from 'src/deployments/procedures/AaveV4DeployProcedureBase.sol'; +import {Create2Utils} from 'src/deployments/utils/libraries/Create2Utils.sol'; +import {V4AddressesProviderInstance} from 'src/addresses-provider/instances/V4AddressesProviderInstance.sol'; + +/// @title AaveV4AddressesProviderDeployProcedure +/// @author Aave Labs +/// @notice Deploys the V4AddressesProvider contract behind a transparent proxy. +contract AaveV4AddressesProviderDeployProcedure is AaveV4DeployProcedureBase { + /// @notice Deploys a V4AddressesProvider instance via CREATE2 and sets up a transparent proxy. + /// @param owner The owner of the proxy admin and the V4AddressesProvider initializer. + /// @param salt The CREATE2 salt for deterministic deployment. + /// @return addressesProviderProxy The address of the deployed transparent proxy. + /// @return addressesProviderImplementation The address of the deployed V4AddressesProvider implementation contract. + function _deployAddressesProvider( + address owner, + bytes32 salt + ) internal returns (address addressesProviderProxy, address addressesProviderImplementation) { + require(owner != address(0), 'invalid owner'); + addressesProviderImplementation = Create2Utils.create2Deploy({ + salt: salt, + bytecode: type(V4AddressesProviderInstance).creationCode + }); + addressesProviderProxy = Create2Utils.proxify({ + salt: salt, + logic: addressesProviderImplementation, + initialOwner: owner, + data: abi.encodeCall(V4AddressesProviderInstance.initialize, (owner)) + }); + return (addressesProviderProxy, addressesProviderImplementation); + } +} diff --git a/tests/deployments/procedures/ProceduresBase.t.sol b/tests/deployments/procedures/ProceduresBase.t.sol index 1e21a15e5..fe3be1db3 100644 --- a/tests/deployments/procedures/ProceduresBase.t.sol +++ b/tests/deployments/procedures/ProceduresBase.t.sol @@ -16,6 +16,7 @@ import {AaveV4AccessManagerEnumerableDeployProcedureWrapper} from 'tests/helpers import {AaveV4AaveOracleDeployProcedureWrapper} from 'tests/helpers/mocks/deployments/procedures/AaveV4AaveOracleDeployProcedureWrapper.sol'; import {AaveV4SpokeDeployProcedureWrapper} from 'tests/helpers/mocks/deployments/procedures/AaveV4SpokeDeployProcedureWrapper.sol'; import {AaveV4TreasurySpokeDeployProcedureWrapper} from 'tests/helpers/mocks/deployments/procedures/AaveV4TreasurySpokeDeployProcedureWrapper.sol'; +import {AaveV4AddressesProviderDeployProcedureWrapper} from 'tests/helpers/mocks/deployments/procedures/AaveV4AddressesProviderDeployProcedureWrapper.sol'; import {AaveV4SpokeConfiguratorDeployProcedureWrapper} from 'tests/helpers/mocks/deployments/procedures/AaveV4SpokeConfiguratorDeployProcedureWrapper.sol'; import {AaveV4AccessManagerRolesProcedureWrapper} from 'tests/helpers/mocks/deployments/procedures/AaveV4AccessManagerRolesProcedureWrapper.sol'; import {AaveV4SpokeRolesProcedureWrapper} from 'tests/helpers/mocks/deployments/procedures/AaveV4SpokeRolesProcedureWrapper.sol'; diff --git a/tests/deployments/procedures/deploy/addresses-provider/AaveV4AddressesProviderDeployProcedure.t.sol b/tests/deployments/procedures/deploy/addresses-provider/AaveV4AddressesProviderDeployProcedure.t.sol new file mode 100644 index 000000000..e8a95ba71 --- /dev/null +++ b/tests/deployments/procedures/deploy/addresses-provider/AaveV4AddressesProviderDeployProcedure.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import 'tests/deployments/procedures/ProceduresBase.t.sol'; + +contract AaveV4AddressesProviderDeployProcedureTest is ProceduresBase { + AaveV4AddressesProviderDeployProcedureWrapper + public aaveV4AddressesProviderDeployProcedureWrapper; + + function setUp() public override { + super.setUp(); + aaveV4AddressesProviderDeployProcedureWrapper = new AaveV4AddressesProviderDeployProcedureWrapper(); + } + + function test_deployAddressesProvider() public { + ( + address addressesProviderProxy, + address addressesProviderImplementation + ) = aaveV4AddressesProviderDeployProcedureWrapper.deployAddressesProvider(owner, salt); + assertEq(Ownable(addressesProviderProxy).owner(), owner); + assertEq(Ownable(ProxyHelper.getProxyAdmin(addressesProviderProxy)).owner(), owner); + assertNotEq(addressesProviderImplementation, address(0)); + assertEq( + ProxyHelper.getImplementation(addressesProviderProxy), + addressesProviderImplementation + ); + } + + function test_deployAddressesProvider_reverts() public { + vm.expectRevert('invalid owner'); + aaveV4AddressesProviderDeployProcedureWrapper.deployAddressesProvider({ + owner: address(0), + salt: salt + }); + } +} diff --git a/tests/helpers/mocks/deployments/procedures/AaveV4AddressesProviderDeployProcedureWrapper.sol b/tests/helpers/mocks/deployments/procedures/AaveV4AddressesProviderDeployProcedureWrapper.sol new file mode 100644 index 000000000..c1922722f --- /dev/null +++ b/tests/helpers/mocks/deployments/procedures/AaveV4AddressesProviderDeployProcedureWrapper.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveV4AddressesProviderDeployProcedure} from 'src/deployments/procedures/deploy/addresses-provider/AaveV4AddressesProviderDeployProcedure.sol'; + +contract AaveV4AddressesProviderDeployProcedureWrapper is AaveV4AddressesProviderDeployProcedure { + bool public IS_TEST = true; + + function deployAddressesProvider( + address owner, + bytes32 salt + ) external returns (address, address) { + return _deployAddressesProvider(owner, salt); + } +} From c012fac5d9084b8436e6cd2269fbcd65dcf01bde Mon Sep 17 00:00:00 2001 From: Alexandru Niculae <43644109+avniculae@users.noreply.github.com> Date: Tue, 16 Jun 2026 23:23:58 +0300 Subject: [PATCH 04/15] fix: address comments --- .../V4AddressesProvider.sol | 165 +++++++++---- .../V4AddressesProviderStorage.sol | 5 +- .../interfaces/IV4AddressesProvider.sol | 110 +++++++-- .../V4AddressesProvider.t.sol | 216 +++++++++++++++--- 4 files changed, 401 insertions(+), 95 deletions(-) diff --git a/src/addresses-provider/V4AddressesProvider.sol b/src/addresses-provider/V4AddressesProvider.sol index fd2e199f1..bccb31e01 100644 --- a/src/addresses-provider/V4AddressesProvider.sol +++ b/src/addresses-provider/V4AddressesProvider.sol @@ -33,41 +33,51 @@ abstract contract V4AddressesProvider is /// @inheritdoc IV4AddressesProvider function setAddress( - string memory name, - string memory tag, + string calldata name, + string calldata tag, address newAddress ) external onlyOwner { _setAddress({name: name, tag: tag, newAddress: newAddress}); } /// @inheritdoc IV4AddressesProvider - function setCanonicalHub(string memory name, address hub) external onlyOwner { + function setCanonicalHub(string calldata name, address hub) external onlyOwner { _setAddress({name: name, tag: CANONICAL_HUB_TAG, newAddress: hub}); } /// @inheritdoc IV4AddressesProvider - function setCanonicalSpoke(string memory name, address spoke) external onlyOwner { + function setCanonicalSpoke(string calldata name, address spoke) external onlyOwner { _setAddress({name: name, tag: CANONICAL_SPOKE_TAG, newAddress: spoke}); } /// @inheritdoc IV4AddressesProvider - function setTokenizationSpoke(string memory name, address spoke) external onlyOwner { + function setTokenizationSpoke(string calldata name, address spoke) external onlyOwner { _setAddress({name: name, tag: TOKENIZATION_SPOKE_TAG, newAddress: spoke}); } /// @inheritdoc IV4AddressesProvider - function setTreasurySpoke(string memory name, address spoke) external onlyOwner { + function setTreasurySpoke(string calldata name, address spoke) external onlyOwner { _setAddress({name: name, tag: TREASURY_SPOKE_TAG, newAddress: spoke}); } + /// @inheritdoc IV4AddressesProvider + function getAddress(bytes32 id) external view returns (address) { + return _addressEntries[id].addr; + } + + /// @inheritdoc IV4AddressesProvider + function getAddress(string calldata name, string calldata tag) external view returns (address) { + return _getAddress({name: name, tag: tag}); + } + /// @inheritdoc IV4AddressesProvider function getAddressEntry(bytes32 id) external view returns (AddressEntry memory) { return _addressEntries[id]; } /// @inheritdoc IV4AddressesProvider - function getIds(string memory tag) external view returns (bytes32[] memory) { - return _taggedIds[tag].values(); + function getTagCount() external view returns (uint256) { + return _tags.length(); } /// @inheritdoc IV4AddressesProvider @@ -76,75 +86,126 @@ abstract contract V4AddressesProvider is } /// @inheritdoc IV4AddressesProvider - function getCanonicalHub(string memory name) external view returns (address) { - return getAddress({name: name, tag: CANONICAL_HUB_TAG}); + function getTags(uint256 start, uint256 end) external view returns (string[] memory) { + return _tags.values(start, end); } /// @inheritdoc IV4AddressesProvider - function getCanonicalHubs() external view returns (address[] memory) { - return getAddresses(CANONICAL_HUB_TAG); + function getIdCount(string calldata tag) external view returns (uint256) { + return _taggedIds[tag].length(); } /// @inheritdoc IV4AddressesProvider - function getCanonicalSpoke(string memory name) external view returns (address) { - return getAddress({name: name, tag: CANONICAL_SPOKE_TAG}); + function getIds(string calldata tag) external view returns (bytes32[] memory) { + return _taggedIds[tag].values(); } /// @inheritdoc IV4AddressesProvider - function getCanonicalSpokes() external view returns (address[] memory) { - return getAddresses(CANONICAL_SPOKE_TAG); + function getIds( + string calldata tag, + uint256 start, + uint256 end + ) external view returns (bytes32[] memory) { + return _taggedIds[tag].values(start, end); } /// @inheritdoc IV4AddressesProvider - function getTokenizationSpoke(string memory name) external view returns (address) { - return getAddress({name: name, tag: TOKENIZATION_SPOKE_TAG}); + function getAddresses(string calldata tag) external view returns (address[] memory) { + return _toAddresses(_taggedIds[tag].values()); } /// @inheritdoc IV4AddressesProvider - function getTokenizationSpokes() external view returns (address[] memory) { - return getAddresses(TOKENIZATION_SPOKE_TAG); + function getAddresses( + string calldata tag, + uint256 start, + uint256 end + ) external view returns (address[] memory) { + return _toAddresses(_taggedIds[tag].values(start, end)); } /// @inheritdoc IV4AddressesProvider - function getTreasurySpoke(string memory name) external view returns (address) { - return getAddress({name: name, tag: TREASURY_SPOKE_TAG}); + function getAddressIdCount(address addr) external view returns (uint256) { + return _addressIds[addr].length(); } /// @inheritdoc IV4AddressesProvider - function getTreasurySpokes() external view returns (address[] memory) { - return getAddresses(TREASURY_SPOKE_TAG); + function getAddressIds(address addr) external view returns (bytes32[] memory) { + return _addressIds[addr].values(); } /// @inheritdoc IV4AddressesProvider - function getAddress(bytes32 id) public view returns (address) { - return _addressEntries[id].addr; + function getAddressIds( + address addr, + uint256 start, + uint256 end + ) external view returns (bytes32[] memory) { + return _addressIds[addr].values(start, end); } /// @inheritdoc IV4AddressesProvider - function getAddress(string memory name, string memory tag) public view returns (address) { - return getAddress(getId({name: name, tag: tag})); + function getAddressEntries(address addr) external view returns (AddressEntry[] memory) { + return _toEntries(_addressIds[addr].values()); } /// @inheritdoc IV4AddressesProvider - function getAddresses(string memory tag) public view returns (address[] memory) { - bytes32[] memory ids = _taggedIds[tag].values(); - address[] memory addresses = new address[](ids.length); - for (uint256 i = 0; i < ids.length; i++) { - addresses[i] = _addressEntries[ids[i]].addr; - } - return addresses; + function getAddressEntries( + address addr, + uint256 start, + uint256 end + ) external view returns (AddressEntry[] memory) { + return _toEntries(_addressIds[addr].values(start, end)); + } + + /// @inheritdoc IV4AddressesProvider + function getCanonicalHub(string calldata name) external view returns (address) { + return _getAddress({name: name, tag: CANONICAL_HUB_TAG}); + } + + /// @inheritdoc IV4AddressesProvider + function getCanonicalHubs() external view returns (address[] memory) { + return _toAddresses(_taggedIds[CANONICAL_HUB_TAG].values()); + } + + /// @inheritdoc IV4AddressesProvider + function getCanonicalSpoke(string calldata name) external view returns (address) { + return _getAddress({name: name, tag: CANONICAL_SPOKE_TAG}); + } + + /// @inheritdoc IV4AddressesProvider + function getCanonicalSpokes() external view returns (address[] memory) { + return _toAddresses(_taggedIds[CANONICAL_SPOKE_TAG].values()); + } + + /// @inheritdoc IV4AddressesProvider + function getTokenizationSpoke(string calldata name) external view returns (address) { + return _getAddress({name: name, tag: TOKENIZATION_SPOKE_TAG}); + } + + /// @inheritdoc IV4AddressesProvider + function getTokenizationSpokes() external view returns (address[] memory) { + return _toAddresses(_taggedIds[TOKENIZATION_SPOKE_TAG].values()); + } + + /// @inheritdoc IV4AddressesProvider + function getTreasurySpoke(string calldata name) external view returns (address) { + return _getAddress({name: name, tag: TREASURY_SPOKE_TAG}); + } + + /// @inheritdoc IV4AddressesProvider + function getTreasurySpokes() external view returns (address[] memory) { + return _toAddresses(_taggedIds[TREASURY_SPOKE_TAG].values()); } /// @inheritdoc IV4AddressesProvider - function getId(string memory name, string memory tag) public pure returns (bytes32) { - return keccak256(bytes(string.concat(name, '_', tag))); + function getId(string calldata name, string calldata tag) external pure returns (bytes32) { + return _getId({name: name, tag: tag}); } function _setAddress(string memory name, string memory tag, address newAddress) internal { require(bytes(name).length > 0, InvalidName()); require(bytes(tag).length > 0, InvalidTag()); - bytes32 id = getId({name: name, tag: tag}); + bytes32 id = _getId({name: name, tag: tag}); AddressEntry memory oldEntry = _addressEntries[id]; if (newAddress == address(0)) { @@ -153,14 +214,40 @@ abstract contract V4AddressesProvider is if (_taggedIds[oldEntry.tag].length() == 0) { _tags.remove(oldEntry.tag); } + _addressIds[oldEntry.addr].remove(id); delete _addressEntries[id]; } else { require(oldEntry.addr == address(0), AddressAlreadySet(id)); - _addressEntries[id] = AddressEntry({addr: newAddress, tag: tag}); + _addressEntries[id] = AddressEntry({addr: newAddress, name: name, tag: tag}); _taggedIds[tag].add(id); _tags.add(tag); + _addressIds[newAddress].add(id); } emit AddressSet(id, name, tag, oldEntry.addr, newAddress); } + + function _getAddress(string memory name, string memory tag) internal view returns (address) { + return _addressEntries[_getId({name: name, tag: tag})].addr; + } + + function _getId(string memory name, string memory tag) internal pure returns (bytes32) { + return keccak256(abi.encode(name, tag)); + } + + function _toAddresses(bytes32[] memory ids) internal view returns (address[] memory) { + address[] memory addresses = new address[](ids.length); + for (uint256 i = 0; i < ids.length; i++) { + addresses[i] = _addressEntries[ids[i]].addr; + } + return addresses; + } + + function _toEntries(bytes32[] memory ids) internal view returns (AddressEntry[] memory) { + AddressEntry[] memory entries = new AddressEntry[](ids.length); + for (uint256 i = 0; i < ids.length; i++) { + entries[i] = _addressEntries[ids[i]]; + } + return entries; + } } diff --git a/src/addresses-provider/V4AddressesProviderStorage.sol b/src/addresses-provider/V4AddressesProviderStorage.sol index 5d299f665..18764db6d 100644 --- a/src/addresses-provider/V4AddressesProviderStorage.sol +++ b/src/addresses-provider/V4AddressesProviderStorage.sol @@ -18,6 +18,9 @@ abstract contract V4AddressesProviderStorage { /// @dev Set of all tags with at least one registered entry. EnumerableSet.StringSet internal _tags; + /// @dev Map of registered addresses to set of entry identifiers. + mapping(address addr => EnumerableSet.Bytes32Set ids) internal _addressIds; + /// @dev Reserved storage space to allow for future layout updates. - uint256[50] private __gap; + uint256[49] private __gap; } diff --git a/src/addresses-provider/interfaces/IV4AddressesProvider.sol b/src/addresses-provider/interfaces/IV4AddressesProvider.sol index 1b3a4fc07..eb8e7d254 100644 --- a/src/addresses-provider/interfaces/IV4AddressesProvider.sol +++ b/src/addresses-provider/interfaces/IV4AddressesProvider.sol @@ -7,9 +7,11 @@ pragma solidity ^0.8.0; interface IV4AddressesProvider { /// @notice Address entry registered under an identifier. /// @param addr The registered address. + /// @param name The name of the entry. /// @param tag The tag grouping the entry. struct AddressEntry { address addr; + string name; string tag; } @@ -57,35 +59,35 @@ interface IV4AddressesProvider { /// @param name The name of the entry. /// @param tag The tag grouping the entry. /// @param newAddress The address to associate with the name and tag. - function setAddress(string memory name, string memory tag, address newAddress) external; + function setAddress(string calldata name, string calldata tag, address newAddress) external; /// @notice Registers the canonical Hub associated with a name. /// @dev Registering the zero address removes the entry and its identifier from enumeration, it reverts if no address is registered. /// @dev Reverts if an address is already registered under the identifier, it must be removed first. /// @param name The name of the Hub. /// @param hub The address of the Hub. - function setCanonicalHub(string memory name, address hub) external; + function setCanonicalHub(string calldata name, address hub) external; /// @notice Registers the canonical Spoke associated with a name. /// @dev Registering the zero address removes the entry and its identifier from enumeration, it reverts if no address is registered. /// @dev Reverts if an address is already registered under the identifier, it must be removed first. /// @param name The name of the Spoke. /// @param spoke The address of the Spoke. - function setCanonicalSpoke(string memory name, address spoke) external; + function setCanonicalSpoke(string calldata name, address spoke) external; /// @notice Registers the tokenization Spoke associated with a name. /// @dev Registering the zero address removes the entry and its identifier from enumeration, it reverts if no address is registered. /// @dev Reverts if an address is already registered under the identifier, it must be removed first. /// @param name The name of the Spoke. /// @param spoke The address of the Spoke. - function setTokenizationSpoke(string memory name, address spoke) external; + function setTokenizationSpoke(string calldata name, address spoke) external; /// @notice Registers the treasury Spoke associated with a name. /// @dev Registering the zero address removes the entry and its identifier from enumeration, it reverts if no address is registered. /// @dev Reverts if an address is already registered under the identifier, it must be removed first. /// @param name The name of the Spoke. /// @param spoke The address of the Spoke. - function setTreasurySpoke(string memory name, address spoke) external; + function setTreasurySpoke(string calldata name, address spoke) external; /// @notice Returns the address associated with an identifier. /// @param id The identifier of the entry. @@ -96,31 +98,105 @@ interface IV4AddressesProvider { /// @param name The name of the entry. /// @param tag The tag grouping the entry. /// @return The address of the entry, the zero address if none is registered. - function getAddress(string memory name, string memory tag) external view returns (address); + function getAddress(string calldata name, string calldata tag) external view returns (address); /// @notice Returns the address entry associated with an identifier. /// @param id The identifier of the entry. /// @return The address entry associated with the identifier. function getAddressEntry(bytes32 id) external view returns (AddressEntry memory); + /// @notice Returns the number of tags with at least one registered entry. + /// @return The number of tags. + function getTagCount() external view returns (uint256); + + /// @notice Returns all tags with at least one registered entry. + /// @return The list of tags. + function getTags() external view returns (string[] memory); + + /// @notice Returns a slice of the tags with at least one registered entry. + /// @param start The start index of the slice. + /// @param end The end index of the slice, capped to the number of tags. + /// @return The list of tags in the slice. + function getTags(uint256 start, uint256 end) external view returns (string[] memory); + + /// @notice Returns the number of entries grouped under a tag. + /// @param tag The tag grouping the entries. + /// @return The number of entries. + function getIdCount(string calldata tag) external view returns (uint256); + /// @notice Returns the identifiers of all entries grouped under a tag. /// @param tag The tag grouping the entries. /// @return The list of identifiers. - function getIds(string memory tag) external view returns (bytes32[] memory); + function getIds(string calldata tag) external view returns (bytes32[] memory); + + /// @notice Returns a slice of the identifiers of the entries grouped under a tag. + /// @param tag The tag grouping the entries. + /// @param start The start index of the slice. + /// @param end The end index of the slice, capped to the number of entries. + /// @return The list of identifiers in the slice. + function getIds( + string calldata tag, + uint256 start, + uint256 end + ) external view returns (bytes32[] memory); /// @notice Returns the addresses of all entries grouped under a tag. /// @param tag The tag grouping the entries. /// @return The list of addresses. - function getAddresses(string memory tag) external view returns (address[] memory); + function getAddresses(string calldata tag) external view returns (address[] memory); - /// @notice Returns all tags with at least one registered entry. - /// @return The list of tags. - function getTags() external view returns (string[] memory); + /// @notice Returns a slice of the addresses of the entries grouped under a tag. + /// @param tag The tag grouping the entries. + /// @param start The start index of the slice. + /// @param end The end index of the slice, capped to the number of entries. + /// @return The list of addresses in the slice. + function getAddresses( + string calldata tag, + uint256 start, + uint256 end + ) external view returns (address[] memory); + + /// @notice Returns the number of entries registered for an address. + /// @param addr The registered address. + /// @return The number of entries. + function getAddressIdCount(address addr) external view returns (uint256); + + /// @notice Returns the identifiers of all entries registered for an address. + /// @param addr The registered address. + /// @return The list of identifiers. + function getAddressIds(address addr) external view returns (bytes32[] memory); + + /// @notice Returns a slice of the identifiers of the entries registered for an address. + /// @param addr The registered address. + /// @param start The start index of the slice. + /// @param end The end index of the slice, capped to the number of entries. + /// @return The list of identifiers in the slice. + function getAddressIds( + address addr, + uint256 start, + uint256 end + ) external view returns (bytes32[] memory); + + /// @notice Returns all entries registered for an address. + /// @param addr The registered address. + /// @return The list of entries. + function getAddressEntries(address addr) external view returns (AddressEntry[] memory); + + /// @notice Returns a slice of the entries registered for an address. + /// @param addr The registered address. + /// @param start The start index of the slice. + /// @param end The end index of the slice, capped to the number of entries. + /// @return The list of entries in the slice. + function getAddressEntries( + address addr, + uint256 start, + uint256 end + ) external view returns (AddressEntry[] memory); /// @notice Returns the canonical Hub associated with a name. /// @param name The name of the Hub. /// @return The address of the Hub, the zero address if none is registered. - function getCanonicalHub(string memory name) external view returns (address); + function getCanonicalHub(string calldata name) external view returns (address); /// @notice Returns the addresses of all registered canonical Hubs. /// @return The list of canonical Hub addresses. @@ -129,7 +205,7 @@ interface IV4AddressesProvider { /// @notice Returns the canonical Spoke associated with a name. /// @param name The name of the Spoke. /// @return The address of the Spoke, the zero address if none is registered. - function getCanonicalSpoke(string memory name) external view returns (address); + function getCanonicalSpoke(string calldata name) external view returns (address); /// @notice Returns the addresses of all registered canonical Spokes. /// @return The list of canonical Spoke addresses. @@ -138,7 +214,7 @@ interface IV4AddressesProvider { /// @notice Returns the tokenization Spoke associated with a name. /// @param name The name of the Spoke. /// @return The address of the Spoke, the zero address if none is registered. - function getTokenizationSpoke(string memory name) external view returns (address); + function getTokenizationSpoke(string calldata name) external view returns (address); /// @notice Returns the addresses of all registered tokenization Spokes. /// @return The list of tokenization Spoke addresses. @@ -147,16 +223,16 @@ interface IV4AddressesProvider { /// @notice Returns the treasury Spoke associated with a name. /// @param name The name of the Spoke. /// @return The address of the Spoke, the zero address if none is registered. - function getTreasurySpoke(string memory name) external view returns (address); + function getTreasurySpoke(string calldata name) external view returns (address); /// @notice Returns the addresses of all registered treasury Spokes. /// @return The list of treasury Spoke addresses. function getTreasurySpokes() external view returns (address[] memory); /// @notice Returns the identifier of the entry associated with a name and tag. - /// @dev The identifier is the hash of `_` (e.g. `CORE_CANONICAL_HUB`). + /// @dev The identifier is the hash of the ABI-encoded name and tag. /// @param name The name of the entry. /// @param tag The tag grouping the entry. /// @return The identifier of the entry. - function getId(string memory name, string memory tag) external pure returns (bytes32); + function getId(string calldata name, string calldata tag) external pure returns (bytes32); } diff --git a/tests/contracts/addresses-provider/V4AddressesProvider.t.sol b/tests/contracts/addresses-provider/V4AddressesProvider.t.sol index 8fd57cf8a..35a10b04e 100644 --- a/tests/contracts/addresses-provider/V4AddressesProvider.t.sol +++ b/tests/contracts/addresses-provider/V4AddressesProvider.t.sol @@ -26,6 +26,10 @@ contract V4AddressesProviderTest is Test { ); } + function _id(string memory name, string memory tag) internal pure returns (bytes32) { + return keccak256(abi.encode(name, tag)); + } + function test_initialize() public view { assertEq(provider.owner(), OWNER); assertEq(provider.CANONICAL_HUB_TAG(), 'CANONICAL_HUB'); @@ -53,24 +57,24 @@ contract V4AddressesProviderTest is Test { function test_getId() public view { assertEq( provider.getId({name: 'CORE', tag: provider.CANONICAL_HUB_TAG()}), - keccak256(bytes('CORE_CANONICAL_HUB')) + keccak256(abi.encode('CORE', 'CANONICAL_HUB')) ); assertEq( provider.getId({name: 'MAIN', tag: provider.CANONICAL_SPOKE_TAG()}), - keccak256(bytes('MAIN_CANONICAL_SPOKE')) + keccak256(abi.encode('MAIN', 'CANONICAL_SPOKE')) ); assertEq( provider.getId({name: 'CORE_WETH', tag: provider.TOKENIZATION_SPOKE_TAG()}), - keccak256(bytes('CORE_WETH_TOKENIZATION_SPOKE')) + keccak256(abi.encode('CORE_WETH', 'TOKENIZATION_SPOKE')) ); assertEq( provider.getId({name: 'MAIN', tag: provider.TREASURY_SPOKE_TAG()}), - keccak256(bytes('MAIN_TREASURY_SPOKE')) + keccak256(abi.encode('MAIN', 'TREASURY_SPOKE')) ); } function test_setAddress() public { - bytes32 id = provider.getId({name: 'CONFIG_ENGINE', tag: 'PERIPHERY'}); + bytes32 id = _id('CONFIG_ENGINE', 'PERIPHERY'); address configEngine = makeAddr('CONFIG_ENGINE'); vm.expectEmit(address(provider)); @@ -90,6 +94,7 @@ contract V4AddressesProviderTest is Test { IV4AddressesProvider.AddressEntry memory entry = provider.getAddressEntry(id); assertEq(entry.addr, configEngine); + assertEq(entry.name, 'CONFIG_ENGINE'); assertEq(entry.tag, 'PERIPHERY'); bytes32[] memory ids = provider.getIds('PERIPHERY'); @@ -99,28 +104,31 @@ contract V4AddressesProviderTest is Test { string[] memory tags = provider.getTags(); assertEq(tags.length, 1); assertEq(tags[0], 'PERIPHERY'); + + bytes32[] memory addressIds = provider.getAddressIds(configEngine); + assertEq(addressIds.length, 1); + assertEq(addressIds[0], id); } function test_setAddress_remove() public { - bytes32 id = provider.getId({name: 'CONFIG_ENGINE', tag: 'PERIPHERY'}); + bytes32 id = _id('CONFIG_ENGINE', 'PERIPHERY'); + address configEngine = makeAddr('CONFIG_ENGINE'); vm.startPrank(OWNER); - provider.setAddress({ - name: 'CONFIG_ENGINE', - tag: 'PERIPHERY', - newAddress: makeAddr('CONFIG_ENGINE') - }); + provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: configEngine}); provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); vm.stopPrank(); assertEq(provider.getAddress(id), address(0)); assertEq(provider.getAddressEntry(id).tag, ''); + assertEq(provider.getAddressEntry(id).name, ''); assertEq(provider.getIds('PERIPHERY').length, 0); assertEq(provider.getTags().length, 0); + assertEq(provider.getAddressIds(configEngine).length, 0); } function test_setAddress_removeThenSet() public { - bytes32 id = provider.getId({name: 'CONFIG_ENGINE', tag: 'PERIPHERY'}); + bytes32 id = _id('CONFIG_ENGINE', 'PERIPHERY'); address newConfigEngine = makeAddr('NEW_CONFIG_ENGINE'); vm.startPrank(OWNER); @@ -141,7 +149,7 @@ contract V4AddressesProviderTest is Test { } function test_setAddress_revertsWith_AddressAlreadySet() public { - bytes32 id = provider.getId({name: 'CONFIG_ENGINE', tag: 'PERIPHERY'}); + bytes32 id = _id('CONFIG_ENGINE', 'PERIPHERY'); address configEngine = makeAddr('CONFIG_ENGINE'); vm.startPrank(OWNER); @@ -159,16 +167,24 @@ contract V4AddressesProviderTest is Test { vm.stopPrank(); } - function test_setAddress_idCollision_revertsWith_AddressAlreadySet() public { - bytes32 id = provider.getId({name: 'A', tag: 'B_C'}); - assertEq(id, provider.getId({name: 'A_B', tag: 'C'})); + function test_setAddress_noIdCollision() public { + // With abi.encode, ('A_B', 'C') and ('A', 'B_C') resolve to distinct identifiers. + bytes32 firstId = _id('A_B', 'C'); + bytes32 secondId = _id('A', 'B_C'); + assertNotEq(firstId, secondId); + assertEq(provider.getId({name: 'A_B', tag: 'C'}), firstId); + assertEq(provider.getId({name: 'A', tag: 'B_C'}), secondId); - vm.startPrank(OWNER); - provider.setAddress({name: 'A', tag: 'B_C', newAddress: makeAddr('A')}); + address first = makeAddr('FIRST'); + address second = makeAddr('SECOND'); - vm.expectRevert(abi.encodeWithSelector(IV4AddressesProvider.AddressAlreadySet.selector, id)); - provider.setAddress({name: 'A_B', tag: 'C', newAddress: makeAddr('A_B')}); + vm.startPrank(OWNER); + provider.setAddress({name: 'A_B', tag: 'C', newAddress: first}); + provider.setAddress({name: 'A', tag: 'B_C', newAddress: second}); vm.stopPrank(); + + assertEq(provider.getAddress({name: 'A_B', tag: 'C'}), first); + assertEq(provider.getAddress({name: 'A', tag: 'B_C'}), second); } function test_setAddress_sameAddressUnderMultipleIds() public { @@ -188,8 +204,8 @@ contract V4AddressesProviderTest is Test { bytes32[] memory peripheryIds = provider.getIds('PERIPHERY'); assertEq(peripheryIds.length, 2); - assertEq(peripheryIds[0], provider.getId({name: 'CONFIG_ENGINE', tag: 'PERIPHERY'})); - assertEq(peripheryIds[1], provider.getId({name: 'ENGINE', tag: 'PERIPHERY'})); + assertEq(peripheryIds[0], _id('CONFIG_ENGINE', 'PERIPHERY')); + assertEq(peripheryIds[1], _id('ENGINE', 'PERIPHERY')); string[] memory tags = provider.getTags(); assertEq(tags.length, 3); @@ -197,6 +213,23 @@ contract V4AddressesProviderTest is Test { assertEq(tags[1], 'ENGINE'); assertEq(tags[2], 'V3_PERIPHERY'); + // the reverse map tracks every identifier the address is registered under + assertEq(provider.getAddressIdCount(configEngine), 4); + bytes32[] memory addressIds = provider.getAddressIds(configEngine); + assertEq(addressIds.length, 4); + assertEq(addressIds[0], _id('CONFIG_ENGINE', 'PERIPHERY')); + assertEq(addressIds[1], _id('ENGINE', 'PERIPHERY')); + assertEq(addressIds[2], _id('CONFIG_ENGINE', 'ENGINE')); + assertEq(addressIds[3], _id('V3_CONFIG_ENGINE', 'V3_PERIPHERY')); + + IV4AddressesProvider.AddressEntry[] memory entries = provider.getAddressEntries(configEngine); + assertEq(entries.length, 4); + assertEq(entries[0].name, 'CONFIG_ENGINE'); + assertEq(entries[0].tag, 'PERIPHERY'); + assertEq(entries[0].addr, configEngine); + assertEq(entries[3].name, 'V3_CONFIG_ENGINE'); + assertEq(entries[3].tag, 'V3_PERIPHERY'); + // removing one entry does not affect the other entries of the same address vm.prank(OWNER); provider.setAddress({name: 'ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); @@ -205,6 +238,7 @@ contract V4AddressesProviderTest is Test { assertEq(provider.getAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY'}), configEngine); assertEq(provider.getAddress({name: 'CONFIG_ENGINE', tag: 'ENGINE'}), configEngine); assertEq(provider.getIds('PERIPHERY').length, 1); + assertEq(provider.getAddressIdCount(configEngine), 3); } function test_setHubAndSpoke_sameAddressAcrossTags() public { @@ -231,10 +265,16 @@ contract V4AddressesProviderTest is Test { address[] memory treasurySpokes = provider.getTreasurySpokes(); assertEq(treasurySpokes.length, 1); assertEq(treasurySpokes[0], sharedSpoke); + + IV4AddressesProvider.AddressEntry[] memory entries = provider.getAddressEntries(sharedSpoke); + assertEq(entries.length, 3); + assertEq(entries[0].tag, 'CANONICAL_SPOKE'); + assertEq(entries[1].tag, 'TOKENIZATION_SPOKE'); + assertEq(entries[2].tag, 'TREASURY_SPOKE'); } function test_setAddress_remove_revertsWith_AddressNotSet() public { - bytes32 id = provider.getId({name: 'CONFIG_ENGINE', tag: 'PERIPHERY'}); + bytes32 id = _id('CONFIG_ENGINE', 'PERIPHERY'); vm.startPrank(OWNER); vm.expectRevert(abi.encodeWithSelector(IV4AddressesProvider.AddressNotSet.selector, id)); @@ -286,7 +326,7 @@ contract V4AddressesProviderTest is Test { vm.startPrank(OWNER); vm.expectEmit(address(provider)); emit IV4AddressesProvider.AddressSet( - keccak256(bytes('CORE_CANONICAL_HUB')), + _id('CORE', 'CANONICAL_HUB'), 'CORE', 'CANONICAL_HUB', address(0), @@ -300,14 +340,19 @@ contract V4AddressesProviderTest is Test { assertEq(provider.getCanonicalHub('CORE'), coreHub); assertEq(provider.getCanonicalHub('PLUS'), plusHub); assertEq(provider.getCanonicalHub('PRIME'), primeHub); - assertEq(provider.getAddress(keccak256(bytes('CORE_CANONICAL_HUB'))), coreHub); - assertEq(provider.getAddressEntry(keccak256(bytes('CORE_CANONICAL_HUB'))).tag, 'CANONICAL_HUB'); + assertEq(provider.getAddress(_id('CORE', 'CANONICAL_HUB')), coreHub); + + IV4AddressesProvider.AddressEntry memory entry = provider.getAddressEntry( + _id('CORE', 'CANONICAL_HUB') + ); + assertEq(entry.name, 'CORE'); + assertEq(entry.tag, 'CANONICAL_HUB'); bytes32[] memory hubIds = provider.getIds('CANONICAL_HUB'); assertEq(hubIds.length, 3); - assertEq(hubIds[0], keccak256(bytes('CORE_CANONICAL_HUB'))); - assertEq(hubIds[1], keccak256(bytes('PLUS_CANONICAL_HUB'))); - assertEq(hubIds[2], keccak256(bytes('PRIME_CANONICAL_HUB'))); + assertEq(hubIds[0], _id('CORE', 'CANONICAL_HUB')); + assertEq(hubIds[1], _id('PLUS', 'CANONICAL_HUB')); + assertEq(hubIds[2], _id('PRIME', 'CANONICAL_HUB')); address[] memory hubs = provider.getCanonicalHubs(); assertEq(hubs.length, 3); @@ -327,7 +372,7 @@ contract V4AddressesProviderTest is Test { vm.expectEmit(address(provider)); emit IV4AddressesProvider.AddressSet( - keccak256(bytes('CORE_CANONICAL_HUB')), + _id('CORE', 'CANONICAL_HUB'), 'CORE', 'CANONICAL_HUB', address(0), @@ -347,7 +392,7 @@ contract V4AddressesProviderTest is Test { vm.expectRevert( abi.encodeWithSelector( IV4AddressesProvider.AddressAlreadySet.selector, - keccak256(bytes('CORE_CANONICAL_HUB')) + _id('CORE', 'CANONICAL_HUB') ) ); provider.setCanonicalHub('CORE', makeAddr('NEW_CORE_HUB')); @@ -372,7 +417,7 @@ contract V4AddressesProviderTest is Test { vm.expectRevert( abi.encodeWithSelector( IV4AddressesProvider.AddressNotSet.selector, - keccak256(bytes('CORE_CANONICAL_HUB')) + _id('CORE', 'CANONICAL_HUB') ) ); vm.prank(OWNER); @@ -417,7 +462,7 @@ contract V4AddressesProviderTest is Test { vm.startPrank(OWNER); vm.expectEmit(address(provider)); emit IV4AddressesProvider.AddressSet( - keccak256(bytes('MAIN_CANONICAL_SPOKE')), + _id('MAIN', 'CANONICAL_SPOKE'), 'MAIN', 'CANONICAL_SPOKE', address(0), @@ -431,7 +476,7 @@ contract V4AddressesProviderTest is Test { assertEq(provider.getCanonicalSpoke('MAIN'), mainSpoke); assertEq(provider.getCanonicalSpoke('BLUECHIP'), bluechipSpoke); assertEq(provider.getCanonicalSpoke('FOREX'), forexSpoke); - assertEq(provider.getAddress(keccak256(bytes('MAIN_CANONICAL_SPOKE'))), mainSpoke); + assertEq(provider.getAddress(_id('MAIN', 'CANONICAL_SPOKE')), mainSpoke); address[] memory canonicalSpokes = provider.getCanonicalSpokes(); assertEq(canonicalSpokes.length, 3); @@ -451,7 +496,7 @@ contract V4AddressesProviderTest is Test { assertEq(provider.getTokenizationSpoke('CORE_WETH'), coreWethSpoke); assertEq(provider.getTokenizationSpoke('PRIME_GHO'), primeGhoSpoke); - assertEq(provider.getAddress(keccak256(bytes('CORE_WETH_TOKENIZATION_SPOKE'))), coreWethSpoke); + assertEq(provider.getAddress(_id('CORE_WETH', 'TOKENIZATION_SPOKE')), coreWethSpoke); address[] memory tokenizationSpokes = provider.getTokenizationSpokes(); assertEq(tokenizationSpokes.length, 2); @@ -466,7 +511,7 @@ contract V4AddressesProviderTest is Test { provider.setTreasurySpoke('MAIN', treasurySpoke); assertEq(provider.getTreasurySpoke('MAIN'), treasurySpoke); - assertEq(provider.getAddress(keccak256(bytes('MAIN_TREASURY_SPOKE'))), treasurySpoke); + assertEq(provider.getAddress(_id('MAIN', 'TREASURY_SPOKE')), treasurySpoke); address[] memory treasurySpokes = provider.getTreasurySpokes(); assertEq(treasurySpokes.length, 1); @@ -507,7 +552,7 @@ contract V4AddressesProviderTest is Test { vm.expectRevert( abi.encodeWithSelector( IV4AddressesProvider.AddressAlreadySet.selector, - keccak256(bytes('MAIN_CANONICAL_SPOKE')) + _id('MAIN', 'CANONICAL_SPOKE') ) ); provider.setCanonicalSpoke('MAIN', makeAddr('NEW_MAIN_SPOKE')); @@ -550,7 +595,7 @@ contract V4AddressesProviderTest is Test { vm.expectRevert( abi.encodeWithSelector( IV4AddressesProvider.AddressNotSet.selector, - keccak256(bytes('MAIN_CANONICAL_SPOKE')) + _id('MAIN', 'CANONICAL_SPOKE') ) ); vm.prank(OWNER); @@ -616,6 +661,8 @@ contract V4AddressesProviderTest is Test { provider.setTreasurySpoke('MAIN', makeAddr('TREASURY_SPOKE')); vm.stopPrank(); + assertEq(provider.getTagCount(), 4); + string[] memory tags = provider.getTags(); assertEq(tags.length, 4); assertEq(tags[0], 'CANONICAL_HUB'); @@ -623,4 +670,97 @@ contract V4AddressesProviderTest is Test { assertEq(tags[2], 'TOKENIZATION_SPOKE'); assertEq(tags[3], 'TREASURY_SPOKE'); } + + function test_getTags_bounded() public { + vm.startPrank(OWNER); + provider.setCanonicalHub('CORE', makeAddr('CORE_HUB')); + provider.setCanonicalSpoke('MAIN', makeAddr('MAIN_SPOKE')); + provider.setTokenizationSpoke('CORE_WETH', makeAddr('CORE_WETH_TOKENIZATION_SPOKE')); + provider.setTreasurySpoke('MAIN', makeAddr('TREASURY_SPOKE')); + vm.stopPrank(); + + string[] memory firstTwo = provider.getTags(0, 2); + assertEq(firstTwo.length, 2); + assertEq(firstTwo[0], 'CANONICAL_HUB'); + assertEq(firstTwo[1], 'CANONICAL_SPOKE'); + + string[] memory lastTwo = provider.getTags(2, 4); + assertEq(lastTwo.length, 2); + assertEq(lastTwo[0], 'TOKENIZATION_SPOKE'); + assertEq(lastTwo[1], 'TREASURY_SPOKE'); + + // end is capped to the number of tags + string[] memory clamped = provider.getTags(3, 100); + assertEq(clamped.length, 1); + assertEq(clamped[0], 'TREASURY_SPOKE'); + + // start beyond the number of tags yields an empty slice + assertEq(provider.getTags(10, 20).length, 0); + } + + function test_getIds_bounded() public { + vm.startPrank(OWNER); + provider.setCanonicalHub('CORE', makeAddr('CORE_HUB')); + provider.setCanonicalHub('PLUS', makeAddr('PLUS_HUB')); + provider.setCanonicalHub('PRIME', makeAddr('PRIME_HUB')); + vm.stopPrank(); + + assertEq(provider.getIdCount('CANONICAL_HUB'), 3); + + bytes32[] memory firstTwo = provider.getIds('CANONICAL_HUB', 0, 2); + assertEq(firstTwo.length, 2); + assertEq(firstTwo[0], _id('CORE', 'CANONICAL_HUB')); + assertEq(firstTwo[1], _id('PLUS', 'CANONICAL_HUB')); + + bytes32[] memory last = provider.getIds('CANONICAL_HUB', 2, 100); + assertEq(last.length, 1); + assertEq(last[0], _id('PRIME', 'CANONICAL_HUB')); + + assertEq(provider.getIds('CANONICAL_HUB', 5, 10).length, 0); + } + + function test_getAddresses_bounded() public { + address coreHub = makeAddr('CORE_HUB'); + address plusHub = makeAddr('PLUS_HUB'); + address primeHub = makeAddr('PRIME_HUB'); + + vm.startPrank(OWNER); + provider.setCanonicalHub('CORE', coreHub); + provider.setCanonicalHub('PLUS', plusHub); + provider.setCanonicalHub('PRIME', primeHub); + vm.stopPrank(); + + address[] memory firstTwo = provider.getAddresses('CANONICAL_HUB', 0, 2); + assertEq(firstTwo.length, 2); + assertEq(firstTwo[0], coreHub); + assertEq(firstTwo[1], plusHub); + + address[] memory last = provider.getAddresses('CANONICAL_HUB', 2, 100); + assertEq(last.length, 1); + assertEq(last[0], primeHub); + } + + function test_getAddressIds_bounded() public { + address shared = makeAddr('SHARED'); + + vm.startPrank(OWNER); + provider.setCanonicalHub('CORE', shared); + provider.setCanonicalSpoke('MAIN', shared); + provider.setTreasurySpoke('MAIN', shared); + vm.stopPrank(); + + assertEq(provider.getAddressIdCount(shared), 3); + + bytes32[] memory firstTwo = provider.getAddressIds(shared, 0, 2); + assertEq(firstTwo.length, 2); + assertEq(firstTwo[0], _id('CORE', 'CANONICAL_HUB')); + assertEq(firstTwo[1], _id('MAIN', 'CANONICAL_SPOKE')); + + IV4AddressesProvider.AddressEntry[] memory entries = provider.getAddressEntries(shared, 1, 3); + assertEq(entries.length, 2); + assertEq(entries[0].tag, 'CANONICAL_SPOKE'); + assertEq(entries[1].tag, 'TREASURY_SPOKE'); + + assertEq(provider.getAddressIds(shared, 5, 10).length, 0); + } } From 80e400bed82d3d5ece90ae9c5c701b3190b2c27d Mon Sep 17 00:00:00 2001 From: Alexandru Niculae <43644109+avniculae@users.noreply.github.com> Date: Wed, 17 Jun 2026 15:03:10 +0300 Subject: [PATCH 05/15] feat: addresses provider registration by config engine --- .../interfaces/IAaveV4ConfigEngine.sol | 20 ++ src/config-engine/libraries/HubEngine.sol | 43 +++- src/config-engine/libraries/SpokeEngine.sol | 26 +- tests/config-engine/AaveV4Payload.t.sol | 5 +- .../AddressesProviderRegistration.t.sol | 222 ++++++++++++++++++ tests/config-engine/BaseConfigEngine.t.sol | 17 +- tests/config-engine/SpokeEngine.t.sol | 15 +- 7 files changed, 336 insertions(+), 12 deletions(-) create mode 100644 tests/config-engine/AddressesProviderRegistration.t.sol diff --git a/src/config-engine/interfaces/IAaveV4ConfigEngine.sol b/src/config-engine/interfaces/IAaveV4ConfigEngine.sol index 2dacaac8e..546d2b8dc 100644 --- a/src/config-engine/interfaces/IAaveV4ConfigEngine.sol +++ b/src/config-engine/interfaces/IAaveV4ConfigEngine.sol @@ -6,6 +6,7 @@ import {ISpokeConfigurator} from 'src/spoke/interfaces/ISpokeConfigurator.sol'; import {IHub} from 'src/hub/interfaces/IHub.sol'; import {ISpoke} from 'src/spoke/interfaces/ISpoke.sol'; import {IAssetInterestRateStrategy} from 'src/hub/interfaces/IAssetInterestRateStrategy.sol'; +import {IV4AddressesProvider} from 'src/addresses-provider/interfaces/IV4AddressesProvider.sol'; /// @title IAaveV4ConfigEngine /// @author Aave Labs @@ -33,6 +34,13 @@ interface IAaveV4ConfigEngine { /// @dev irStrategy The address of the interest rate strategy contract. /// @dev irData The interest rate data to apply to the given asset. /// @dev tokenization The tokenization configuration for the asset. + /// @dev addressesProvider The V4AddressesProvider used for the registrations below (skipped if unset). + /// @dev registerHub Whether to register the Hub on the addressesProvider; only allowed when the + /// listed asset is the Hub's first (asset id 0), reverts otherwise. + /// @dev hubName The name to register the Hub under (as a canonical Hub). + /// @dev registerTokenizationSpoke Whether to register the deployed TokenizationSpoke on the + /// addressesProvider; only allowed when a TokenizationSpoke is deployed for this listing. + /// @dev tokenizationSpokeName The name to register the TokenizationSpoke under. struct AssetListing { IHubConfigurator hubConfigurator; address hub; @@ -42,6 +50,11 @@ interface IAaveV4ConfigEngine { address irStrategy; IAssetInterestRateStrategy.InterestRateData irData; TokenizationSpokeConfig tokenization; + IV4AddressesProvider addressesProvider; + bool registerHub; + string hubName; + bool registerTokenizationSpoke; + string tokenizationSpokeName; } /// @notice Parameters for updating asset config (fee, interest rate, reinvestment) on a Hub. @@ -166,6 +179,10 @@ interface IAaveV4ConfigEngine { /// @dev priceSource The address of the price source. /// @dev config The configuration of the reserve. /// @dev dynamicConfig The dynamic configuration of the reserve. + /// @dev addressesProvider The V4AddressesProvider used for the registration below (skipped if unset). + /// @dev registerSpoke Whether to register the Spoke on the addressesProvider; only allowed when the + /// listed reserve is the Spoke's first (reserve id 0), reverts otherwise. + /// @dev spokeName The name to register the Spoke under (as a canonical Spoke). struct ReserveListing { ISpokeConfigurator spokeConfigurator; address spoke; @@ -174,6 +191,9 @@ interface IAaveV4ConfigEngine { address priceSource; ISpoke.ReserveConfig config; ISpoke.DynamicReserveConfig dynamicConfig; + IV4AddressesProvider addressesProvider; + bool registerSpoke; + string spokeName; } /// @notice Parameters for updating reserve config on a Spoke. diff --git a/src/config-engine/libraries/HubEngine.sol b/src/config-engine/libraries/HubEngine.sol index 20b3d68c7..e163fb6d4 100644 --- a/src/config-engine/libraries/HubEngine.sol +++ b/src/config-engine/libraries/HubEngine.sol @@ -19,15 +19,21 @@ library HubEngine { /// KEEP_CURRENT sentinel. All fields must be explicitly set when the strategy changes. error InvalidIrDataWithNewStrategy(); + /// @dev Thrown when an addresses provider registration is requested for a listing that does not + /// support it: registering a Hub when the listed asset is not the Hub's first (asset id != 0), + /// registering a TokenizationSpoke that was not deployed, or no addresses provider supplied. + error InvalidAddressesProviderRegistration(); + /// @notice Lists new assets on Hubs via the HubConfigurator. /// @dev When `tokenization.name` & `tokenization.symbol` are defined, also deploys a TokenizationSpoke (impl + proxy) via /// CREATE2 and registers it on the Hub for the listed asset. + /// @dev Optionally registers the Hub and/or the deployed TokenizationSpoke on the AddressesProvider. /// @param listings The asset listings to execute. function executeHubAssetListings(IAaveV4ConfigEngine.AssetListing[] calldata listings) external { uint256 length = listings.length; for (uint256 i; i < length; ++i) { bytes memory irData = abi.encode(listings[i].irData); - listings[i].hubConfigurator.addAsset( + uint256 assetId = listings[i].hubConfigurator.addAsset( listings[i].hub, listings[i].underlying, listings[i].feeReceiver, @@ -36,7 +42,8 @@ library HubEngine { irData ); - _deployAndRegisterTokenizationSpoke(listings[i]); + _registerHub(listings[i], assetId); + _deployAndRegisterTokenizationSpoke(listings[i], assetId); } } @@ -215,14 +222,34 @@ library HubEngine { } } + /// @dev Registers the Hub on the AddressesProvider when requested. + /// @dev Only allowed when the listed asset is the Hub's first (asset id 0), to avoid registering an + /// already-configured Hub; reverts otherwise. + function _registerHub( + IAaveV4ConfigEngine.AssetListing calldata listing, + uint256 assetId + ) private { + if (!listing.registerHub) { + return; + } + require( + assetId == 0 && address(listing.addressesProvider) != address(0), + InvalidAddressesProviderRegistration() + ); + listing.addressesProvider.setCanonicalHub(listing.hubName, listing.hub); + } + /// @dev Deploys a TokenizationSpoke (impl + proxy) via CREATE2 and registers it on the Hub. + /// @dev Optionally registers the deployed TokenizationSpoke on the AddressesProvider. function _deployAndRegisterTokenizationSpoke( - IAaveV4ConfigEngine.AssetListing calldata listing + IAaveV4ConfigEngine.AssetListing calldata listing, + uint256 assetId ) private { // if not name and/or symbol given, we assume there is no intention to deploy a TokenizationSpoke, so we skip deployment and registration if ( bytes(listing.tokenization.name).length == 0 || bytes(listing.tokenization.symbol).length == 0 ) { + require(!listing.registerTokenizationSpoke, InvalidAddressesProviderRegistration()); return; } @@ -233,8 +260,6 @@ library HubEngine { listing.tokenization.symbol ); - uint256 assetId = IHubBase(listing.hub).getAssetId(listing.underlying); - listing.hubConfigurator.addSpoke( listing.hub, proxy, @@ -247,6 +272,14 @@ library HubEngine { halted: false }) ); + + if (listing.registerTokenizationSpoke) { + require( + address(listing.addressesProvider) != address(0), + InvalidAddressesProviderRegistration() + ); + listing.addressesProvider.setTokenizationSpoke(listing.tokenizationSpokeName, proxy); + } } /// @dev Merges non-sentinel fields from irData into the current on-chain IR data. diff --git a/src/config-engine/libraries/SpokeEngine.sol b/src/config-engine/libraries/SpokeEngine.sol index 03f2620dd..d4fded18a 100644 --- a/src/config-engine/libraries/SpokeEngine.sol +++ b/src/config-engine/libraries/SpokeEngine.sol @@ -13,7 +13,12 @@ import {IAaveV4ConfigEngine} from 'src/config-engine/interfaces/IAaveV4ConfigEng library SpokeEngine { using SafeCast for uint256; + /// @dev Thrown when a canonical Spoke registration is requested for a listing that does not support + /// it: the listed reserve is not the Spoke's first (reserve id != 0), or no addresses provider supplied. + error InvalidAddressesProviderRegistration(); + /// @notice Lists new reserves on Spokes. + /// @dev Optionally registers the Spoke on the AddressesProvider. /// @param listings The reserve listings to execute. function executeSpokeReserveListings( IAaveV4ConfigEngine.ReserveListing[] calldata listings @@ -21,7 +26,7 @@ library SpokeEngine { uint256 length = listings.length; for (uint256 i; i < length; ++i) { uint256 assetId = IHubBase(listings[i].hub).getAssetId(listings[i].underlying); - listings[i].spokeConfigurator.addReserve( + uint256 reserveId = listings[i].spokeConfigurator.addReserve( listings[i].spoke, listings[i].hub, assetId, @@ -29,6 +34,8 @@ library SpokeEngine { listings[i].config, listings[i].dynamicConfig ); + + _registerSpoke(listings[i], reserveId); } } @@ -225,4 +232,21 @@ library SpokeEngine { uint256 assetId = IHubBase(hub).getAssetId(underlying); return ISpoke(spoke).getReserveId(hub, assetId); } + + /// @dev Registers the Spoke on the AddressesProvider when requested. + /// @dev Only allowed when the listed reserve is the Spoke's first (reserve id 0), to avoid + /// registering an already-configured Spoke; reverts otherwise. + function _registerSpoke( + IAaveV4ConfigEngine.ReserveListing calldata listing, + uint256 reserveId + ) private { + if (!listing.registerSpoke) { + return; + } + require( + reserveId == 0 && address(listing.addressesProvider) != address(0), + InvalidAddressesProviderRegistration() + ); + listing.addressesProvider.setCanonicalSpoke(listing.spokeName, listing.spoke); + } } diff --git a/tests/config-engine/AaveV4Payload.t.sol b/tests/config-engine/AaveV4Payload.t.sol index 471319e41..1cd107ebf 100644 --- a/tests/config-engine/AaveV4Payload.t.sol +++ b/tests/config-engine/AaveV4Payload.t.sol @@ -668,7 +668,10 @@ contract AaveV4PayloadTest is BaseConfigEngineTest { collateralFactor: 80_00, maxLiquidationBonus: 105_00, liquidationFee: 10_00 - }) + }), + addressesProvider: IV4AddressesProvider(address(0)), + registerSpoke: false, + spokeName: '' }); payload.setSpokeReserveListings(listings); diff --git a/tests/config-engine/AddressesProviderRegistration.t.sol b/tests/config-engine/AddressesProviderRegistration.t.sol new file mode 100644 index 000000000..b4647ec0b --- /dev/null +++ b/tests/config-engine/AddressesProviderRegistration.t.sol @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import 'tests/config-engine/BaseConfigEngine.t.sol'; + +import {TransparentUpgradeableProxy} from 'src/dependencies/openzeppelin/TransparentUpgradeableProxy.sol'; +import {V4AddressesProviderInstance} from 'src/addresses-provider/instances/V4AddressesProviderInstance.sol'; + +/// @notice Tests the optional V4AddressesProvider registration during Hub asset and Spoke reserve +/// listings. The environment is intentionally left unseeded so the first listed asset/reserve has +/// id 0, which is the gate for registering a newly configured Hub/Spoke. +contract AddressesProviderRegistrationTest is BaseConfigEngineTest { + IV4AddressesProvider internal provider; + + function setUp() public override { + super.setUp(); + // The engine is the actor making the external calls in these tests, so it must own the provider. + provider = _deployAddressesProvider(address(engine)); + } + + function _deployAddressesProvider(address owner) internal returns (IV4AddressesProvider) { + return + IV4AddressesProvider( + address( + new TransparentUpgradeableProxy( + address(new V4AddressesProviderInstance()), + ADMIN, + abi.encodeCall(V4AddressesProviderInstance.initialize, (owner)) + ) + ) + ); + } + + // Hub registration + + function test_executeHubAssetListings_registersHub() public { + IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); + listing.underlying = address(weth); + listing.addressesProvider = provider; + listing.registerHub = true; + listing.hubName = 'CORE'; + + engine.executeHubAssetListings(_toAssetListingArray(listing)); + + assertEq(hub1().getAssetId(address(weth)), 0); + assertEq(provider.getCanonicalHub('CORE'), address(hub1())); + } + + function test_executeHubAssetListings_registerHub_revertsWhenNotFirstAsset() public { + IAaveV4ConfigEngine.AssetListing memory first = _defaultAssetListing(); + first.underlying = address(weth); + engine.executeHubAssetListings(_toAssetListingArray(first)); + + // usdx becomes asset id 1 on hub1, so registering the hub during its listing is rejected + IAaveV4ConfigEngine.AssetListing memory second = _defaultAssetListing(); + second.underlying = address(usdx); + second.addressesProvider = provider; + second.registerHub = true; + second.hubName = 'CORE'; + + vm.expectRevert(HubEngine.InvalidAddressesProviderRegistration.selector); + engine.executeHubAssetListings(_toAssetListingArray(second)); + } + + function test_executeHubAssetListings_registerHub_revertsWhenNoProvider() public { + IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); + listing.underlying = address(weth); + listing.registerHub = true; + listing.hubName = 'CORE'; + // addressesProvider left as address(0) + + vm.expectRevert(HubEngine.InvalidAddressesProviderRegistration.selector); + engine.executeHubAssetListings(_toAssetListingArray(listing)); + } + + function test_executeHubAssetListings_registerHub_revertsWhenAlreadyRegistered() public { + IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); + listing.underlying = address(weth); + listing.addressesProvider = provider; + listing.registerHub = true; + listing.hubName = 'CORE'; + engine.executeHubAssetListings(_toAssetListingArray(listing)); + + // listing on another fresh hub (asset id 0) but reusing the same name reverts in the provider + IAaveV4ConfigEngine.AssetListing memory second = _defaultAssetListing(); + second.hub = address(hub2()); + second.underlying = address(weth); + second.irStrategy = address(irStrategy2()); + second.addressesProvider = provider; + second.registerHub = true; + second.hubName = 'CORE'; + + vm.expectRevert( + abi.encodeWithSelector( + IV4AddressesProvider.AddressAlreadySet.selector, + provider.getId('CORE', provider.CANONICAL_HUB_TAG()) + ) + ); + engine.executeHubAssetListings(_toAssetListingArray(second)); + } + + // Tokenization spoke registration + + function test_executeHubAssetListings_registersTokenizationSpoke() public { + IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); + listing.underlying = address(weth); + listing.tokenization = IAaveV4ConfigEngine.TokenizationSpokeConfig({ + addCap: 1_000, + name: 'Aave WETH', + symbol: 'aWETH' + }); + listing.addressesProvider = provider; + listing.registerTokenizationSpoke = true; + listing.tokenizationSpokeName = 'CORE_WETH'; + + address expectedProxy = TokenizationSpokeDeployer.computeProxyAddress( + address(hub1()), + address(weth), + 'Aave WETH', + 'aWETH', + address(this) + ); + + engine.executeHubAssetListings(_toAssetListingArray(listing)); + + assertEq(provider.getTokenizationSpoke('CORE_WETH'), expectedProxy); + } + + function test_executeHubAssetListings_registerTokenizationSpoke_revertsWhenNotDeployed() public { + IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); + listing.underlying = address(weth); + // no tokenization name/symbol => no TokenizationSpoke deployed + listing.addressesProvider = provider; + listing.registerTokenizationSpoke = true; + listing.tokenizationSpokeName = 'CORE_WETH'; + + vm.expectRevert(HubEngine.InvalidAddressesProviderRegistration.selector); + engine.executeHubAssetListings(_toAssetListingArray(listing)); + } + + function test_executeHubAssetListings_registersHubAndTokenizationSpoke() public { + IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); + listing.underlying = address(weth); + listing.tokenization = IAaveV4ConfigEngine.TokenizationSpokeConfig({ + addCap: 1_000, + name: 'Aave WETH', + symbol: 'aWETH' + }); + listing.addressesProvider = provider; + listing.registerHub = true; + listing.hubName = 'CORE'; + listing.registerTokenizationSpoke = true; + listing.tokenizationSpokeName = 'CORE_WETH'; + + address expectedProxy = TokenizationSpokeDeployer.computeProxyAddress( + address(hub1()), + address(weth), + 'Aave WETH', + 'aWETH', + address(this) + ); + + engine.executeHubAssetListings(_toAssetListingArray(listing)); + + assertEq(provider.getCanonicalHub('CORE'), address(hub1())); + assertEq(provider.getTokenizationSpoke('CORE_WETH'), expectedProxy); + } + + // Canonical spoke registration + + function test_executeSpokeReserveListings_registersSpoke() public { + _seedAsset(hub1(), irStrategy1(), address(weth), 18); + address priceSource = _deployMockPriceFeed(spoke1(), tokenList[TOKEN_WETH].priceFeed); + + IAaveV4ConfigEngine.ReserveListing memory listing = _defaultReserveListing(); + listing.underlying = address(weth); + listing.priceSource = priceSource; + listing.addressesProvider = provider; + listing.registerSpoke = true; + listing.spokeName = 'MAIN'; + + engine.executeSpokeReserveListings(_toReserveListingArray(listing)); + + assertEq(spoke1().getReserveId(address(hub1()), 0), 0); + assertEq(provider.getCanonicalSpoke('MAIN'), address(spoke1())); + } + + function test_executeSpokeReserveListings_registerSpoke_revertsWhenNotFirstReserve() public { + _seedAsset(hub1(), irStrategy1(), address(weth), 18); + _seedAsset(hub1(), irStrategy1(), address(usdx), 6); + + IAaveV4ConfigEngine.ReserveListing memory first = _defaultReserveListing(); + first.underlying = address(weth); + first.priceSource = _deployMockPriceFeed(spoke1(), tokenList[TOKEN_WETH].priceFeed); + engine.executeSpokeReserveListings(_toReserveListingArray(first)); + + // usdx becomes reserve id 1 on spoke1, so registering the spoke during its listing is rejected + IAaveV4ConfigEngine.ReserveListing memory second = _defaultReserveListing(); + second.underlying = address(usdx); + second.priceSource = _deployMockPriceFeed(spoke1(), tokenList[TOKEN_USDX].priceFeed); + second.addressesProvider = provider; + second.registerSpoke = true; + second.spokeName = 'MAIN'; + + vm.expectRevert(SpokeEngine.InvalidAddressesProviderRegistration.selector); + engine.executeSpokeReserveListings(_toReserveListingArray(second)); + } + + function test_executeSpokeReserveListings_registerSpoke_revertsWhenNoProvider() public { + _seedAsset(hub1(), irStrategy1(), address(weth), 18); + + IAaveV4ConfigEngine.ReserveListing memory listing = _defaultReserveListing(); + listing.underlying = address(weth); + listing.priceSource = _deployMockPriceFeed(spoke1(), tokenList[TOKEN_WETH].priceFeed); + listing.registerSpoke = true; + listing.spokeName = 'MAIN'; + // addressesProvider left as address(0) + + vm.expectRevert(SpokeEngine.InvalidAddressesProviderRegistration.selector); + engine.executeSpokeReserveListings(_toReserveListingArray(listing)); + } +} diff --git a/tests/config-engine/BaseConfigEngine.t.sol b/tests/config-engine/BaseConfigEngine.t.sol index 50c8e64c6..6ed66530f 100644 --- a/tests/config-engine/BaseConfigEngine.t.sol +++ b/tests/config-engine/BaseConfigEngine.t.sol @@ -28,6 +28,7 @@ import {Create2TestHelper} from 'tests/utils/Create2TestHelper.sol'; import {AaveV4Payload} from 'src/config-engine/AaveV4Payload.sol'; import {AaveV4ConfigEngine} from 'src/config-engine/AaveV4ConfigEngine.sol'; import {IAaveV4ConfigEngine} from 'src/config-engine/interfaces/IAaveV4ConfigEngine.sol'; +import {IV4AddressesProvider} from 'src/addresses-provider/interfaces/IV4AddressesProvider.sol'; import {EngineFlags} from 'src/config-engine/libraries/EngineFlags.sol'; import {AccessManagerEngine} from 'src/config-engine/libraries/AccessManagerEngine.sol'; import {HubEngine} from 'src/config-engine/libraries/HubEngine.sol'; @@ -332,7 +333,16 @@ abstract contract BaseConfigEngineTest is Test, Create2TestHelper { liquidityFee: LIQUIDITY_FEE, irStrategy: address(irStrategy1()), irData: IR_DATA, - tokenization: IAaveV4ConfigEngine.TokenizationSpokeConfig({addCap: 0, name: '', symbol: ''}) + tokenization: IAaveV4ConfigEngine.TokenizationSpokeConfig({ + addCap: 0, + name: '', + symbol: '' + }), + addressesProvider: IV4AddressesProvider(address(0)), + registerHub: false, + hubName: '', + registerTokenizationSpoke: false, + tokenizationSpokeName: '' }); } @@ -463,7 +473,10 @@ abstract contract BaseConfigEngineTest is Test, Create2TestHelper { underlying: address(weth), priceSource: address(priceFeedWeth), config: _defaultReserveConfig(), - dynamicConfig: _defaultDynamicReserveConfig() + dynamicConfig: _defaultDynamicReserveConfig(), + addressesProvider: IV4AddressesProvider(address(0)), + registerSpoke: false, + spokeName: '' }); } diff --git a/tests/config-engine/SpokeEngine.t.sol b/tests/config-engine/SpokeEngine.t.sol index abfb1f8c7..8e6f67ad0 100644 --- a/tests/config-engine/SpokeEngine.t.sol +++ b/tests/config-engine/SpokeEngine.t.sol @@ -632,7 +632,10 @@ contract SpokeEngineTest is BaseConfigEngineTest { collateralFactor: 80_00, maxLiquidationBonus: 105_00, liquidationFee: 2_00 - }) + }), + addressesProvider: IV4AddressesProvider(address(0)), + registerSpoke: false, + spokeName: '' }); engine.executeSpokeReserveListings(_toReserveListingArray(listing)); @@ -925,7 +928,10 @@ contract SpokeEngineTest is BaseConfigEngineTest { underlying: address(tokenA), priceSource: priceFeedA, config: _defaultReserveConfig(), - dynamicConfig: _defaultDynamicReserveConfig() + dynamicConfig: _defaultDynamicReserveConfig(), + addressesProvider: IV4AddressesProvider(address(0)), + registerSpoke: false, + spokeName: '' }); listings[1] = IAaveV4ConfigEngine.ReserveListing({ @@ -935,7 +941,10 @@ contract SpokeEngineTest is BaseConfigEngineTest { underlying: address(tokenB), priceSource: priceFeedB, config: _defaultReserveConfig(), - dynamicConfig: _defaultDynamicReserveConfig() + dynamicConfig: _defaultDynamicReserveConfig(), + addressesProvider: IV4AddressesProvider(address(0)), + registerSpoke: false, + spokeName: '' }); engine.executeSpokeReserveListings(listings); From 8e0bf2aa8e1d106d1c630e55d2d3c22b3d557fe2 Mon Sep 17 00:00:00 2001 From: Alexandru Niculae <43644109+avniculae@users.noreply.github.com> Date: Wed, 24 Jun 2026 17:20:48 +0100 Subject: [PATCH 06/15] fix: add optional struct --- .../interfaces/IAaveV4ConfigEngine.sol | 39 ++++++------ src/config-engine/libraries/HubEngine.sol | 23 +++++--- src/config-engine/libraries/SpokeEngine.sol | 9 ++- tests/config-engine/AaveV4Payload.t.sol | 8 ++- .../AddressesProviderRegistration.t.sol | 59 ++++++++----------- tests/config-engine/BaseConfigEngine.t.sol | 23 +++++--- tests/config-engine/SpokeEngine.t.sol | 24 +++++--- 7 files changed, 101 insertions(+), 84 deletions(-) diff --git a/src/config-engine/interfaces/IAaveV4ConfigEngine.sol b/src/config-engine/interfaces/IAaveV4ConfigEngine.sol index 546d2b8dc..2d34587cd 100644 --- a/src/config-engine/interfaces/IAaveV4ConfigEngine.sol +++ b/src/config-engine/interfaces/IAaveV4ConfigEngine.sol @@ -25,6 +25,17 @@ interface IAaveV4ConfigEngine { string symbol; } + /// @notice Optional registration of a listed Hub or Spoke on the V4AddressesProvider. + /// @dev Left unset (the default), `register` is false and the registration is skipped. + /// @dev addressesProvider The V4AddressesProvider to register the entry on. + /// @dev register Whether to register the entry on the addressesProvider. + /// @dev name The name to register the entry under. + struct AddressesProviderRegistration { + IV4AddressesProvider addressesProvider; + bool register; + string name; + } + /// @notice Parameters for listing a new asset on a Hub. /// @dev hubConfigurator The HubConfigurator to use for this action. /// @dev hub The address of the Hub. @@ -34,13 +45,10 @@ interface IAaveV4ConfigEngine { /// @dev irStrategy The address of the interest rate strategy contract. /// @dev irData The interest rate data to apply to the given asset. /// @dev tokenization The tokenization configuration for the asset. - /// @dev addressesProvider The V4AddressesProvider used for the registrations below (skipped if unset). - /// @dev registerHub Whether to register the Hub on the addressesProvider; only allowed when the - /// listed asset is the Hub's first (asset id 0), reverts otherwise. - /// @dev hubName The name to register the Hub under (as a canonical Hub). - /// @dev registerTokenizationSpoke Whether to register the deployed TokenizationSpoke on the - /// addressesProvider; only allowed when a TokenizationSpoke is deployed for this listing. - /// @dev tokenizationSpokeName The name to register the TokenizationSpoke under. + /// @dev hubRegistration Optional registration of the Hub on the V4AddressesProvider; only allowed + /// when the listed asset is the Hub's first (asset id 0), reverts otherwise. + /// @dev tokenizationSpokeRegistration Optional registration of the deployed TokenizationSpoke on the + /// V4AddressesProvider; only allowed when a TokenizationSpoke is deployed for this listing. struct AssetListing { IHubConfigurator hubConfigurator; address hub; @@ -50,11 +58,8 @@ interface IAaveV4ConfigEngine { address irStrategy; IAssetInterestRateStrategy.InterestRateData irData; TokenizationSpokeConfig tokenization; - IV4AddressesProvider addressesProvider; - bool registerHub; - string hubName; - bool registerTokenizationSpoke; - string tokenizationSpokeName; + AddressesProviderRegistration hubRegistration; + AddressesProviderRegistration tokenizationSpokeRegistration; } /// @notice Parameters for updating asset config (fee, interest rate, reinvestment) on a Hub. @@ -179,10 +184,8 @@ interface IAaveV4ConfigEngine { /// @dev priceSource The address of the price source. /// @dev config The configuration of the reserve. /// @dev dynamicConfig The dynamic configuration of the reserve. - /// @dev addressesProvider The V4AddressesProvider used for the registration below (skipped if unset). - /// @dev registerSpoke Whether to register the Spoke on the addressesProvider; only allowed when the - /// listed reserve is the Spoke's first (reserve id 0), reverts otherwise. - /// @dev spokeName The name to register the Spoke under (as a canonical Spoke). + /// @dev spokeRegistration Optional registration of the Spoke on the V4AddressesProvider; only allowed + /// when the listed reserve is the Spoke's first (reserve id 0), reverts otherwise. struct ReserveListing { ISpokeConfigurator spokeConfigurator; address spoke; @@ -191,9 +194,7 @@ interface IAaveV4ConfigEngine { address priceSource; ISpoke.ReserveConfig config; ISpoke.DynamicReserveConfig dynamicConfig; - IV4AddressesProvider addressesProvider; - bool registerSpoke; - string spokeName; + AddressesProviderRegistration spokeRegistration; } /// @notice Parameters for updating reserve config on a Spoke. diff --git a/src/config-engine/libraries/HubEngine.sol b/src/config-engine/libraries/HubEngine.sol index e163fb6d4..3653b7a02 100644 --- a/src/config-engine/libraries/HubEngine.sol +++ b/src/config-engine/libraries/HubEngine.sol @@ -229,14 +229,17 @@ library HubEngine { IAaveV4ConfigEngine.AssetListing calldata listing, uint256 assetId ) private { - if (!listing.registerHub) { + if (!listing.hubRegistration.register) { return; } require( - assetId == 0 && address(listing.addressesProvider) != address(0), + assetId == 0 && address(listing.hubRegistration.addressesProvider) != address(0), InvalidAddressesProviderRegistration() ); - listing.addressesProvider.setCanonicalHub(listing.hubName, listing.hub); + listing.hubRegistration.addressesProvider.setCanonicalHub( + listing.hubRegistration.name, + listing.hub + ); } /// @dev Deploys a TokenizationSpoke (impl + proxy) via CREATE2 and registers it on the Hub. @@ -249,7 +252,10 @@ library HubEngine { if ( bytes(listing.tokenization.name).length == 0 || bytes(listing.tokenization.symbol).length == 0 ) { - require(!listing.registerTokenizationSpoke, InvalidAddressesProviderRegistration()); + require( + !listing.tokenizationSpokeRegistration.register, + InvalidAddressesProviderRegistration() + ); return; } @@ -273,12 +279,15 @@ library HubEngine { }) ); - if (listing.registerTokenizationSpoke) { + if (listing.tokenizationSpokeRegistration.register) { require( - address(listing.addressesProvider) != address(0), + address(listing.tokenizationSpokeRegistration.addressesProvider) != address(0), InvalidAddressesProviderRegistration() ); - listing.addressesProvider.setTokenizationSpoke(listing.tokenizationSpokeName, proxy); + listing.tokenizationSpokeRegistration.addressesProvider.setTokenizationSpoke( + listing.tokenizationSpokeRegistration.name, + proxy + ); } } diff --git a/src/config-engine/libraries/SpokeEngine.sol b/src/config-engine/libraries/SpokeEngine.sol index d4fded18a..8a50f66e0 100644 --- a/src/config-engine/libraries/SpokeEngine.sol +++ b/src/config-engine/libraries/SpokeEngine.sol @@ -240,13 +240,16 @@ library SpokeEngine { IAaveV4ConfigEngine.ReserveListing calldata listing, uint256 reserveId ) private { - if (!listing.registerSpoke) { + if (!listing.spokeRegistration.register) { return; } require( - reserveId == 0 && address(listing.addressesProvider) != address(0), + reserveId == 0 && address(listing.spokeRegistration.addressesProvider) != address(0), InvalidAddressesProviderRegistration() ); - listing.addressesProvider.setCanonicalSpoke(listing.spokeName, listing.spoke); + listing.spokeRegistration.addressesProvider.setCanonicalSpoke( + listing.spokeRegistration.name, + listing.spoke + ); } } diff --git a/tests/config-engine/AaveV4Payload.t.sol b/tests/config-engine/AaveV4Payload.t.sol index 1cd107ebf..b1800d36a 100644 --- a/tests/config-engine/AaveV4Payload.t.sol +++ b/tests/config-engine/AaveV4Payload.t.sol @@ -669,9 +669,11 @@ contract AaveV4PayloadTest is BaseConfigEngineTest { maxLiquidationBonus: 105_00, liquidationFee: 10_00 }), - addressesProvider: IV4AddressesProvider(address(0)), - registerSpoke: false, - spokeName: '' + spokeRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ + addressesProvider: IV4AddressesProvider(address(0)), + register: false, + name: '' + }) }); payload.setSpokeReserveListings(listings); diff --git a/tests/config-engine/AddressesProviderRegistration.t.sol b/tests/config-engine/AddressesProviderRegistration.t.sol index b4647ec0b..7dc79e0a0 100644 --- a/tests/config-engine/AddressesProviderRegistration.t.sol +++ b/tests/config-engine/AddressesProviderRegistration.t.sol @@ -18,6 +18,18 @@ contract AddressesProviderRegistrationTest is BaseConfigEngineTest { provider = _deployAddressesProvider(address(engine)); } + function _registration( + IV4AddressesProvider addressesProvider, + string memory name + ) internal pure returns (IAaveV4ConfigEngine.AddressesProviderRegistration memory) { + return + IAaveV4ConfigEngine.AddressesProviderRegistration({ + addressesProvider: addressesProvider, + register: true, + name: name + }); + } + function _deployAddressesProvider(address owner) internal returns (IV4AddressesProvider) { return IV4AddressesProvider( @@ -36,9 +48,7 @@ contract AddressesProviderRegistrationTest is BaseConfigEngineTest { function test_executeHubAssetListings_registersHub() public { IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); listing.underlying = address(weth); - listing.addressesProvider = provider; - listing.registerHub = true; - listing.hubName = 'CORE'; + listing.hubRegistration = _registration(provider, 'CORE'); engine.executeHubAssetListings(_toAssetListingArray(listing)); @@ -54,9 +64,7 @@ contract AddressesProviderRegistrationTest is BaseConfigEngineTest { // usdx becomes asset id 1 on hub1, so registering the hub during its listing is rejected IAaveV4ConfigEngine.AssetListing memory second = _defaultAssetListing(); second.underlying = address(usdx); - second.addressesProvider = provider; - second.registerHub = true; - second.hubName = 'CORE'; + second.hubRegistration = _registration(provider, 'CORE'); vm.expectRevert(HubEngine.InvalidAddressesProviderRegistration.selector); engine.executeHubAssetListings(_toAssetListingArray(second)); @@ -65,9 +73,7 @@ contract AddressesProviderRegistrationTest is BaseConfigEngineTest { function test_executeHubAssetListings_registerHub_revertsWhenNoProvider() public { IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); listing.underlying = address(weth); - listing.registerHub = true; - listing.hubName = 'CORE'; - // addressesProvider left as address(0) + listing.hubRegistration = _registration(IV4AddressesProvider(address(0)), 'CORE'); vm.expectRevert(HubEngine.InvalidAddressesProviderRegistration.selector); engine.executeHubAssetListings(_toAssetListingArray(listing)); @@ -76,9 +82,7 @@ contract AddressesProviderRegistrationTest is BaseConfigEngineTest { function test_executeHubAssetListings_registerHub_revertsWhenAlreadyRegistered() public { IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); listing.underlying = address(weth); - listing.addressesProvider = provider; - listing.registerHub = true; - listing.hubName = 'CORE'; + listing.hubRegistration = _registration(provider, 'CORE'); engine.executeHubAssetListings(_toAssetListingArray(listing)); // listing on another fresh hub (asset id 0) but reusing the same name reverts in the provider @@ -86,9 +90,7 @@ contract AddressesProviderRegistrationTest is BaseConfigEngineTest { second.hub = address(hub2()); second.underlying = address(weth); second.irStrategy = address(irStrategy2()); - second.addressesProvider = provider; - second.registerHub = true; - second.hubName = 'CORE'; + second.hubRegistration = _registration(provider, 'CORE'); vm.expectRevert( abi.encodeWithSelector( @@ -109,9 +111,7 @@ contract AddressesProviderRegistrationTest is BaseConfigEngineTest { name: 'Aave WETH', symbol: 'aWETH' }); - listing.addressesProvider = provider; - listing.registerTokenizationSpoke = true; - listing.tokenizationSpokeName = 'CORE_WETH'; + listing.tokenizationSpokeRegistration = _registration(provider, 'CORE_WETH'); address expectedProxy = TokenizationSpokeDeployer.computeProxyAddress( address(hub1()), @@ -130,9 +130,7 @@ contract AddressesProviderRegistrationTest is BaseConfigEngineTest { IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); listing.underlying = address(weth); // no tokenization name/symbol => no TokenizationSpoke deployed - listing.addressesProvider = provider; - listing.registerTokenizationSpoke = true; - listing.tokenizationSpokeName = 'CORE_WETH'; + listing.tokenizationSpokeRegistration = _registration(provider, 'CORE_WETH'); vm.expectRevert(HubEngine.InvalidAddressesProviderRegistration.selector); engine.executeHubAssetListings(_toAssetListingArray(listing)); @@ -146,11 +144,8 @@ contract AddressesProviderRegistrationTest is BaseConfigEngineTest { name: 'Aave WETH', symbol: 'aWETH' }); - listing.addressesProvider = provider; - listing.registerHub = true; - listing.hubName = 'CORE'; - listing.registerTokenizationSpoke = true; - listing.tokenizationSpokeName = 'CORE_WETH'; + listing.hubRegistration = _registration(provider, 'CORE'); + listing.tokenizationSpokeRegistration = _registration(provider, 'CORE_WETH'); address expectedProxy = TokenizationSpokeDeployer.computeProxyAddress( address(hub1()), @@ -175,9 +170,7 @@ contract AddressesProviderRegistrationTest is BaseConfigEngineTest { IAaveV4ConfigEngine.ReserveListing memory listing = _defaultReserveListing(); listing.underlying = address(weth); listing.priceSource = priceSource; - listing.addressesProvider = provider; - listing.registerSpoke = true; - listing.spokeName = 'MAIN'; + listing.spokeRegistration = _registration(provider, 'MAIN'); engine.executeSpokeReserveListings(_toReserveListingArray(listing)); @@ -198,9 +191,7 @@ contract AddressesProviderRegistrationTest is BaseConfigEngineTest { IAaveV4ConfigEngine.ReserveListing memory second = _defaultReserveListing(); second.underlying = address(usdx); second.priceSource = _deployMockPriceFeed(spoke1(), tokenList[TOKEN_USDX].priceFeed); - second.addressesProvider = provider; - second.registerSpoke = true; - second.spokeName = 'MAIN'; + second.spokeRegistration = _registration(provider, 'MAIN'); vm.expectRevert(SpokeEngine.InvalidAddressesProviderRegistration.selector); engine.executeSpokeReserveListings(_toReserveListingArray(second)); @@ -212,9 +203,7 @@ contract AddressesProviderRegistrationTest is BaseConfigEngineTest { IAaveV4ConfigEngine.ReserveListing memory listing = _defaultReserveListing(); listing.underlying = address(weth); listing.priceSource = _deployMockPriceFeed(spoke1(), tokenList[TOKEN_WETH].priceFeed); - listing.registerSpoke = true; - listing.spokeName = 'MAIN'; - // addressesProvider left as address(0) + listing.spokeRegistration = _registration(IV4AddressesProvider(address(0)), 'MAIN'); vm.expectRevert(SpokeEngine.InvalidAddressesProviderRegistration.selector); engine.executeSpokeReserveListings(_toReserveListingArray(listing)); diff --git a/tests/config-engine/BaseConfigEngine.t.sol b/tests/config-engine/BaseConfigEngine.t.sol index 6ed66530f..a78561270 100644 --- a/tests/config-engine/BaseConfigEngine.t.sol +++ b/tests/config-engine/BaseConfigEngine.t.sol @@ -338,11 +338,16 @@ abstract contract BaseConfigEngineTest is Test, Create2TestHelper { name: '', symbol: '' }), - addressesProvider: IV4AddressesProvider(address(0)), - registerHub: false, - hubName: '', - registerTokenizationSpoke: false, - tokenizationSpokeName: '' + hubRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ + addressesProvider: IV4AddressesProvider(address(0)), + register: false, + name: '' + }), + tokenizationSpokeRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ + addressesProvider: IV4AddressesProvider(address(0)), + register: false, + name: '' + }) }); } @@ -474,9 +479,11 @@ abstract contract BaseConfigEngineTest is Test, Create2TestHelper { priceSource: address(priceFeedWeth), config: _defaultReserveConfig(), dynamicConfig: _defaultDynamicReserveConfig(), - addressesProvider: IV4AddressesProvider(address(0)), - registerSpoke: false, - spokeName: '' + spokeRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ + addressesProvider: IV4AddressesProvider(address(0)), + register: false, + name: '' + }) }); } diff --git a/tests/config-engine/SpokeEngine.t.sol b/tests/config-engine/SpokeEngine.t.sol index 8e6f67ad0..47d82a778 100644 --- a/tests/config-engine/SpokeEngine.t.sol +++ b/tests/config-engine/SpokeEngine.t.sol @@ -633,9 +633,11 @@ contract SpokeEngineTest is BaseConfigEngineTest { maxLiquidationBonus: 105_00, liquidationFee: 2_00 }), - addressesProvider: IV4AddressesProvider(address(0)), - registerSpoke: false, - spokeName: '' + spokeRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ + addressesProvider: IV4AddressesProvider(address(0)), + register: false, + name: '' + }) }); engine.executeSpokeReserveListings(_toReserveListingArray(listing)); @@ -929,9 +931,11 @@ contract SpokeEngineTest is BaseConfigEngineTest { priceSource: priceFeedA, config: _defaultReserveConfig(), dynamicConfig: _defaultDynamicReserveConfig(), - addressesProvider: IV4AddressesProvider(address(0)), - registerSpoke: false, - spokeName: '' + spokeRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ + addressesProvider: IV4AddressesProvider(address(0)), + register: false, + name: '' + }) }); listings[1] = IAaveV4ConfigEngine.ReserveListing({ @@ -942,9 +946,11 @@ contract SpokeEngineTest is BaseConfigEngineTest { priceSource: priceFeedB, config: _defaultReserveConfig(), dynamicConfig: _defaultDynamicReserveConfig(), - addressesProvider: IV4AddressesProvider(address(0)), - registerSpoke: false, - spokeName: '' + spokeRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ + addressesProvider: IV4AddressesProvider(address(0)), + register: false, + name: '' + }) }); engine.executeSpokeReserveListings(listings); From b55faacb732afd056e17869044cc2c42361c033b Mon Sep 17 00:00:00 2001 From: Alexandru Niculae <43644109+avniculae@users.noreply.github.com> Date: Tue, 21 Jul 2026 17:50:30 +0300 Subject: [PATCH 07/15] fix: address review comments --- .../V4AddressesProvider.sol | 23 +++++ .../interfaces/IV4AddressesProvider.sol | 29 ++++++- .../interfaces/IAaveV4ConfigEngine.sol | 1 + src/config-engine/libraries/EngineUtils.sol | 22 +++++ src/config-engine/libraries/HubEngine.sol | 21 +++-- src/config-engine/libraries/SpokeEngine.sol | 13 +-- .../AddressesProviderRegistration.t.sol | 55 ++++++++++++ tests/config-engine/EngineUtils.t.sol | 83 +++++++++++++++++++ .../V4AddressesProvider.t.sol | 47 +++++++++++ 9 files changed, 278 insertions(+), 16 deletions(-) create mode 100644 src/config-engine/libraries/EngineUtils.sol create mode 100644 tests/config-engine/EngineUtils.t.sol diff --git a/src/addresses-provider/V4AddressesProvider.sol b/src/addresses-provider/V4AddressesProvider.sol index bccb31e01..a05904c16 100644 --- a/src/addresses-provider/V4AddressesProvider.sol +++ b/src/addresses-provider/V4AddressesProvider.sol @@ -166,6 +166,11 @@ abstract contract V4AddressesProvider is return _toAddresses(_taggedIds[CANONICAL_HUB_TAG].values()); } + /// @inheritdoc IV4AddressesProvider + function getCanonicalHubs(uint256 start, uint256 end) external view returns (address[] memory) { + return _toAddresses(_taggedIds[CANONICAL_HUB_TAG].values(start, end)); + } + /// @inheritdoc IV4AddressesProvider function getCanonicalSpoke(string calldata name) external view returns (address) { return _getAddress({name: name, tag: CANONICAL_SPOKE_TAG}); @@ -176,6 +181,11 @@ abstract contract V4AddressesProvider is return _toAddresses(_taggedIds[CANONICAL_SPOKE_TAG].values()); } + /// @inheritdoc IV4AddressesProvider + function getCanonicalSpokes(uint256 start, uint256 end) external view returns (address[] memory) { + return _toAddresses(_taggedIds[CANONICAL_SPOKE_TAG].values(start, end)); + } + /// @inheritdoc IV4AddressesProvider function getTokenizationSpoke(string calldata name) external view returns (address) { return _getAddress({name: name, tag: TOKENIZATION_SPOKE_TAG}); @@ -186,6 +196,14 @@ abstract contract V4AddressesProvider is return _toAddresses(_taggedIds[TOKENIZATION_SPOKE_TAG].values()); } + /// @inheritdoc IV4AddressesProvider + function getTokenizationSpokes( + uint256 start, + uint256 end + ) external view returns (address[] memory) { + return _toAddresses(_taggedIds[TOKENIZATION_SPOKE_TAG].values(start, end)); + } + /// @inheritdoc IV4AddressesProvider function getTreasurySpoke(string calldata name) external view returns (address) { return _getAddress({name: name, tag: TREASURY_SPOKE_TAG}); @@ -196,6 +214,11 @@ abstract contract V4AddressesProvider is return _toAddresses(_taggedIds[TREASURY_SPOKE_TAG].values()); } + /// @inheritdoc IV4AddressesProvider + function getTreasurySpokes(uint256 start, uint256 end) external view returns (address[] memory) { + return _toAddresses(_taggedIds[TREASURY_SPOKE_TAG].values(start, end)); + } + /// @inheritdoc IV4AddressesProvider function getId(string calldata name, string calldata tag) external pure returns (bytes32) { return _getId({name: name, tag: tag}); diff --git a/src/addresses-provider/interfaces/IV4AddressesProvider.sol b/src/addresses-provider/interfaces/IV4AddressesProvider.sol index eb8e7d254..7f3ba996d 100644 --- a/src/addresses-provider/interfaces/IV4AddressesProvider.sol +++ b/src/addresses-provider/interfaces/IV4AddressesProvider.sol @@ -106,11 +106,9 @@ interface IV4AddressesProvider { function getAddressEntry(bytes32 id) external view returns (AddressEntry memory); /// @notice Returns the number of tags with at least one registered entry. - /// @return The number of tags. function getTagCount() external view returns (uint256); /// @notice Returns all tags with at least one registered entry. - /// @return The list of tags. function getTags() external view returns (string[] memory); /// @notice Returns a slice of the tags with at least one registered entry. @@ -202,6 +200,12 @@ interface IV4AddressesProvider { /// @return The list of canonical Hub addresses. function getCanonicalHubs() external view returns (address[] memory); + /// @notice Returns a slice of the addresses of the registered canonical Hubs. + /// @param start The start index of the slice. + /// @param end The end index of the slice, capped to the number of entries. + /// @return The list of canonical Hub addresses in the slice. + function getCanonicalHubs(uint256 start, uint256 end) external view returns (address[] memory); + /// @notice Returns the canonical Spoke associated with a name. /// @param name The name of the Spoke. /// @return The address of the Spoke, the zero address if none is registered. @@ -211,6 +215,12 @@ interface IV4AddressesProvider { /// @return The list of canonical Spoke addresses. function getCanonicalSpokes() external view returns (address[] memory); + /// @notice Returns a slice of the addresses of the registered canonical Spokes. + /// @param start The start index of the slice. + /// @param end The end index of the slice, capped to the number of entries. + /// @return The list of canonical Spoke addresses in the slice. + function getCanonicalSpokes(uint256 start, uint256 end) external view returns (address[] memory); + /// @notice Returns the tokenization Spoke associated with a name. /// @param name The name of the Spoke. /// @return The address of the Spoke, the zero address if none is registered. @@ -220,6 +230,15 @@ interface IV4AddressesProvider { /// @return The list of tokenization Spoke addresses. function getTokenizationSpokes() external view returns (address[] memory); + /// @notice Returns a slice of the addresses of the registered tokenization Spokes. + /// @param start The start index of the slice. + /// @param end The end index of the slice, capped to the number of entries. + /// @return The list of tokenization Spoke addresses in the slice. + function getTokenizationSpokes( + uint256 start, + uint256 end + ) external view returns (address[] memory); + /// @notice Returns the treasury Spoke associated with a name. /// @param name The name of the Spoke. /// @return The address of the Spoke, the zero address if none is registered. @@ -229,6 +248,12 @@ interface IV4AddressesProvider { /// @return The list of treasury Spoke addresses. function getTreasurySpokes() external view returns (address[] memory); + /// @notice Returns a slice of the addresses of the registered treasury Spokes. + /// @param start The start index of the slice. + /// @param end The end index of the slice, capped to the number of entries. + /// @return The list of treasury Spoke addresses in the slice. + function getTreasurySpokes(uint256 start, uint256 end) external view returns (address[] memory); + /// @notice Returns the identifier of the entry associated with a name and tag. /// @dev The identifier is the hash of the ABI-encoded name and tag. /// @param name The name of the entry. diff --git a/src/config-engine/interfaces/IAaveV4ConfigEngine.sol b/src/config-engine/interfaces/IAaveV4ConfigEngine.sol index 2d34587cd..a793f443b 100644 --- a/src/config-engine/interfaces/IAaveV4ConfigEngine.sol +++ b/src/config-engine/interfaces/IAaveV4ConfigEngine.sol @@ -27,6 +27,7 @@ interface IAaveV4ConfigEngine { /// @notice Optional registration of a listed Hub or Spoke on the V4AddressesProvider. /// @dev Left unset (the default), `register` is false and the registration is skipped. + /// @dev All fields must be set when `register` is true, and left unset when false; reverts otherwise. /// @dev addressesProvider The V4AddressesProvider to register the entry on. /// @dev register Whether to register the entry on the addressesProvider. /// @dev name The name to register the entry under. diff --git a/src/config-engine/libraries/EngineUtils.sol b/src/config-engine/libraries/EngineUtils.sol new file mode 100644 index 000000000..abc3ad6d8 --- /dev/null +++ b/src/config-engine/libraries/EngineUtils.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: LicenseRef-BUSL +pragma solidity ^0.8.0; + +import {IAaveV4ConfigEngine} from 'src/config-engine/interfaces/IAaveV4ConfigEngine.sol'; + +/// @title EngineUtils +/// @author Aave Labs +/// @notice Library containing shared helpers for the AaveV4ConfigEngine libraries. +library EngineUtils { + /// @dev Returns whether an optional AddressesProvider registration is consistent: all fields must + /// be set when registering, and left unset otherwise. + function isConsistentRegistration( + IAaveV4ConfigEngine.AddressesProviderRegistration calldata registration + ) internal pure returns (bool) { + return + registration.register + ? address(registration.addressesProvider) != address(0) && + bytes(registration.name).length > 0 + : address(registration.addressesProvider) == address(0) && + bytes(registration.name).length == 0; + } +} diff --git a/src/config-engine/libraries/HubEngine.sol b/src/config-engine/libraries/HubEngine.sol index 3653b7a02..b96704546 100644 --- a/src/config-engine/libraries/HubEngine.sol +++ b/src/config-engine/libraries/HubEngine.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import {SafeCast} from 'src/dependencies/openzeppelin/SafeCast.sol'; import {EngineFlags} from 'src/config-engine/libraries/EngineFlags.sol'; +import {EngineUtils} from 'src/config-engine/libraries/EngineUtils.sol'; import {TokenizationSpokeDeployer} from 'src/config-engine/libraries/TokenizationSpokeDeployer.sol'; import {IHubBase} from 'src/hub/interfaces/IHubBase.sol'; import {IHub} from 'src/hub/interfaces/IHub.sol'; @@ -21,7 +22,8 @@ library HubEngine { /// @dev Thrown when an addresses provider registration is requested for a listing that does not /// support it: registering a Hub when the listed asset is not the Hub's first (asset id != 0), - /// registering a TokenizationSpoke that was not deployed, or no addresses provider supplied. + /// registering a TokenizationSpoke that was not deployed, or the registration fields are + /// inconsistent with the `register` flag. error InvalidAddressesProviderRegistration(); /// @notice Lists new assets on Hubs via the HubConfigurator. @@ -229,13 +231,14 @@ library HubEngine { IAaveV4ConfigEngine.AssetListing calldata listing, uint256 assetId ) private { - if (!listing.hubRegistration.register) { - return; - } require( - assetId == 0 && address(listing.hubRegistration.addressesProvider) != address(0), + EngineUtils.isConsistentRegistration(listing.hubRegistration), InvalidAddressesProviderRegistration() ); + if (!listing.hubRegistration.register) { + return; + } + require(assetId == 0, InvalidAddressesProviderRegistration()); listing.hubRegistration.addressesProvider.setCanonicalHub( listing.hubRegistration.name, listing.hub @@ -248,6 +251,10 @@ library HubEngine { IAaveV4ConfigEngine.AssetListing calldata listing, uint256 assetId ) private { + require( + EngineUtils.isConsistentRegistration(listing.tokenizationSpokeRegistration), + InvalidAddressesProviderRegistration() + ); // if not name and/or symbol given, we assume there is no intention to deploy a TokenizationSpoke, so we skip deployment and registration if ( bytes(listing.tokenization.name).length == 0 || bytes(listing.tokenization.symbol).length == 0 @@ -280,10 +287,6 @@ library HubEngine { ); if (listing.tokenizationSpokeRegistration.register) { - require( - address(listing.tokenizationSpokeRegistration.addressesProvider) != address(0), - InvalidAddressesProviderRegistration() - ); listing.tokenizationSpokeRegistration.addressesProvider.setTokenizationSpoke( listing.tokenizationSpokeRegistration.name, proxy diff --git a/src/config-engine/libraries/SpokeEngine.sol b/src/config-engine/libraries/SpokeEngine.sol index 8a50f66e0..92dd10ab7 100644 --- a/src/config-engine/libraries/SpokeEngine.sol +++ b/src/config-engine/libraries/SpokeEngine.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import {SafeCast} from 'src/dependencies/openzeppelin/SafeCast.sol'; import {EngineFlags} from 'src/config-engine/libraries/EngineFlags.sol'; +import {EngineUtils} from 'src/config-engine/libraries/EngineUtils.sol'; import {IHubBase} from 'src/hub/interfaces/IHubBase.sol'; import {ISpoke} from 'src/spoke/interfaces/ISpoke.sol'; import {IAaveV4ConfigEngine} from 'src/config-engine/interfaces/IAaveV4ConfigEngine.sol'; @@ -14,7 +15,8 @@ library SpokeEngine { using SafeCast for uint256; /// @dev Thrown when a canonical Spoke registration is requested for a listing that does not support - /// it: the listed reserve is not the Spoke's first (reserve id != 0), or no addresses provider supplied. + /// it: the listed reserve is not the Spoke's first (reserve id != 0), or the registration fields are + /// inconsistent with the `register` flag. error InvalidAddressesProviderRegistration(); /// @notice Lists new reserves on Spokes. @@ -240,13 +242,14 @@ library SpokeEngine { IAaveV4ConfigEngine.ReserveListing calldata listing, uint256 reserveId ) private { - if (!listing.spokeRegistration.register) { - return; - } require( - reserveId == 0 && address(listing.spokeRegistration.addressesProvider) != address(0), + EngineUtils.isConsistentRegistration(listing.spokeRegistration), InvalidAddressesProviderRegistration() ); + if (!listing.spokeRegistration.register) { + return; + } + require(reserveId == 0, InvalidAddressesProviderRegistration()); listing.spokeRegistration.addressesProvider.setCanonicalSpoke( listing.spokeRegistration.name, listing.spoke diff --git a/tests/config-engine/AddressesProviderRegistration.t.sol b/tests/config-engine/AddressesProviderRegistration.t.sol index 7dc79e0a0..d00ba12ed 100644 --- a/tests/config-engine/AddressesProviderRegistration.t.sol +++ b/tests/config-engine/AddressesProviderRegistration.t.sol @@ -79,6 +79,24 @@ contract AddressesProviderRegistrationTest is BaseConfigEngineTest { engine.executeHubAssetListings(_toAssetListingArray(listing)); } + function test_executeHubAssetListings_registerHub_revertsWhenNoName() public { + IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); + listing.underlying = address(weth); + listing.hubRegistration = _registration(provider, ''); + + vm.expectRevert(HubEngine.InvalidAddressesProviderRegistration.selector); + engine.executeHubAssetListings(_toAssetListingArray(listing)); + } + + function test_executeHubAssetListings_registerHub_revertsWhenFieldsSetWithoutRegister() public { + IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); + listing.underlying = address(weth); + listing.hubRegistration.name = 'CORE'; + + vm.expectRevert(HubEngine.InvalidAddressesProviderRegistration.selector); + engine.executeHubAssetListings(_toAssetListingArray(listing)); + } + function test_executeHubAssetListings_registerHub_revertsWhenAlreadyRegistered() public { IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); listing.underlying = address(weth); @@ -136,6 +154,17 @@ contract AddressesProviderRegistrationTest is BaseConfigEngineTest { engine.executeHubAssetListings(_toAssetListingArray(listing)); } + function test_executeHubAssetListings_registerTokenizationSpoke_revertsWhenFieldsSetWithoutRegister() + public + { + IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); + listing.underlying = address(weth); + listing.tokenizationSpokeRegistration.name = 'CORE_WETH'; + + vm.expectRevert(HubEngine.InvalidAddressesProviderRegistration.selector); + engine.executeHubAssetListings(_toAssetListingArray(listing)); + } + function test_executeHubAssetListings_registersHubAndTokenizationSpoke() public { IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); listing.underlying = address(weth); @@ -208,4 +237,30 @@ contract AddressesProviderRegistrationTest is BaseConfigEngineTest { vm.expectRevert(SpokeEngine.InvalidAddressesProviderRegistration.selector); engine.executeSpokeReserveListings(_toReserveListingArray(listing)); } + + function test_executeSpokeReserveListings_registerSpoke_revertsWhenNoName() public { + _seedAsset(hub1(), irStrategy1(), address(weth), 18); + + IAaveV4ConfigEngine.ReserveListing memory listing = _defaultReserveListing(); + listing.underlying = address(weth); + listing.priceSource = _deployMockPriceFeed(spoke1(), tokenList[TOKEN_WETH].priceFeed); + listing.spokeRegistration = _registration(provider, ''); + + vm.expectRevert(SpokeEngine.InvalidAddressesProviderRegistration.selector); + engine.executeSpokeReserveListings(_toReserveListingArray(listing)); + } + + function test_executeSpokeReserveListings_registerSpoke_revertsWhenFieldsSetWithoutRegister() + public + { + _seedAsset(hub1(), irStrategy1(), address(weth), 18); + + IAaveV4ConfigEngine.ReserveListing memory listing = _defaultReserveListing(); + listing.underlying = address(weth); + listing.priceSource = _deployMockPriceFeed(spoke1(), tokenList[TOKEN_WETH].priceFeed); + listing.spokeRegistration.name = 'MAIN'; + + vm.expectRevert(SpokeEngine.InvalidAddressesProviderRegistration.selector); + engine.executeSpokeReserveListings(_toReserveListingArray(listing)); + } } diff --git a/tests/config-engine/EngineUtils.t.sol b/tests/config-engine/EngineUtils.t.sol new file mode 100644 index 000000000..eb47ea413 --- /dev/null +++ b/tests/config-engine/EngineUtils.t.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Test} from 'forge-std/Test.sol'; + +import {EngineUtils} from 'src/config-engine/libraries/EngineUtils.sol'; +import {IAaveV4ConfigEngine} from 'src/config-engine/interfaces/IAaveV4ConfigEngine.sol'; +import {IV4AddressesProvider} from 'src/addresses-provider/interfaces/IV4AddressesProvider.sol'; + +/// @dev Wrapper to call EngineUtils library functions externally. +contract EngineUtilsHarness { + function isConsistentRegistration( + IAaveV4ConfigEngine.AddressesProviderRegistration calldata registration + ) external pure returns (bool) { + return EngineUtils.isConsistentRegistration(registration); + } +} + +contract EngineUtilsTest is Test { + EngineUtilsHarness internal _harness; + + function setUp() public { + _harness = new EngineUtilsHarness(); + } + + function _registration( + address addressesProvider, + bool register, + string memory name + ) internal pure returns (IAaveV4ConfigEngine.AddressesProviderRegistration memory) { + return + IAaveV4ConfigEngine.AddressesProviderRegistration({ + addressesProvider: IV4AddressesProvider(addressesProvider), + register: register, + name: name + }); + } + + function test_isConsistentRegistration_register_allFieldsSet() public view { + assertTrue(_harness.isConsistentRegistration(_registration(address(1), true, 'CORE'))); + } + + function test_isConsistentRegistration_register_noProvider() public view { + assertFalse(_harness.isConsistentRegistration(_registration(address(0), true, 'CORE'))); + } + + function test_isConsistentRegistration_register_noName() public view { + assertFalse(_harness.isConsistentRegistration(_registration(address(1), true, ''))); + } + + function test_isConsistentRegistration_register_allFieldsUnset() public view { + assertFalse(_harness.isConsistentRegistration(_registration(address(0), true, ''))); + } + + function test_isConsistentRegistration_noRegister_allFieldsUnset() public view { + assertTrue(_harness.isConsistentRegistration(_registration(address(0), false, ''))); + } + + function test_isConsistentRegistration_noRegister_providerSet() public view { + assertFalse(_harness.isConsistentRegistration(_registration(address(1), false, ''))); + } + + function test_isConsistentRegistration_noRegister_nameSet() public view { + assertFalse(_harness.isConsistentRegistration(_registration(address(0), false, 'CORE'))); + } + + function test_isConsistentRegistration_noRegister_allFieldsSet() public view { + assertFalse(_harness.isConsistentRegistration(_registration(address(1), false, 'CORE'))); + } + + function test_fuzz_isConsistentRegistration( + address addressesProvider, + bool register, + string memory name + ) public view { + bool fieldsSet = addressesProvider != address(0) && bytes(name).length > 0; + bool fieldsUnset = addressesProvider == address(0) && bytes(name).length == 0; + assertEq( + _harness.isConsistentRegistration(_registration(addressesProvider, register, name)), + register ? fieldsSet : fieldsUnset + ); + } +} diff --git a/tests/contracts/addresses-provider/V4AddressesProvider.t.sol b/tests/contracts/addresses-provider/V4AddressesProvider.t.sol index 35a10b04e..2cfc87e23 100644 --- a/tests/contracts/addresses-provider/V4AddressesProvider.t.sol +++ b/tests/contracts/addresses-provider/V4AddressesProvider.t.sol @@ -740,6 +740,53 @@ contract V4AddressesProviderTest is Test { assertEq(last[0], primeHub); } + function test_getCanonicalHubs_bounded() public { + address coreHub = makeAddr('CORE_HUB'); + address plusHub = makeAddr('PLUS_HUB'); + address primeHub = makeAddr('PRIME_HUB'); + + vm.startPrank(OWNER); + provider.setCanonicalHub('CORE', coreHub); + provider.setCanonicalHub('PLUS', plusHub); + provider.setCanonicalHub('PRIME', primeHub); + vm.stopPrank(); + + address[] memory firstTwo = provider.getCanonicalHubs(0, 2); + assertEq(firstTwo.length, 2); + assertEq(firstTwo[0], coreHub); + assertEq(firstTwo[1], plusHub); + + address[] memory last = provider.getCanonicalHubs(2, 100); + assertEq(last.length, 1); + assertEq(last[0], primeHub); + } + + function test_getSpokes_bounded() public { + address mainSpoke = makeAddr('MAIN_SPOKE'); + address extraSpoke = makeAddr('EXTRA_SPOKE'); + address tokenizationSpoke = makeAddr('TOKENIZATION_SPOKE'); + address treasurySpoke = makeAddr('TREASURY_SPOKE'); + + vm.startPrank(OWNER); + provider.setCanonicalSpoke('MAIN', mainSpoke); + provider.setCanonicalSpoke('EXTRA', extraSpoke); + provider.setTokenizationSpoke('CORE_WETH', tokenizationSpoke); + provider.setTreasurySpoke('MAIN', treasurySpoke); + vm.stopPrank(); + + address[] memory canonicalSpokes = provider.getCanonicalSpokes(1, 100); + assertEq(canonicalSpokes.length, 1); + assertEq(canonicalSpokes[0], extraSpoke); + + address[] memory tokenizationSpokes = provider.getTokenizationSpokes(0, 100); + assertEq(tokenizationSpokes.length, 1); + assertEq(tokenizationSpokes[0], tokenizationSpoke); + + address[] memory treasurySpokes = provider.getTreasurySpokes(0, 100); + assertEq(treasurySpokes.length, 1); + assertEq(treasurySpokes[0], treasurySpoke); + } + function test_getAddressIds_bounded() public { address shared = makeAddr('SHARED'); From d0631c39c149b75f601c6f6ebda4b9bf07747641 Mon Sep 17 00:00:00 2001 From: Alexandru Niculae <43644109+avniculae@users.noreply.github.com> Date: Wed, 29 Jul 2026 14:01:23 +0300 Subject: [PATCH 08/15] refactor: drop V4 prefix and adopt entry naming in AddressesProvider - rename V4AddressesProvider/IV4AddressesProvider and the deploy procedure and batch to drop the V4/AaveV4 prefix - rename the AddressEntry struct to Entry and follow through on the API: setAddress -> setEntry, AddressSet -> SetEntry, getAddressEntry -> getEntry, getAddressEntries -> getEntries - name storage vars after the AccessManagerEnumerable convention and set __gap to 50, matching HubStorage and SpokeStorage - document that the range getters clamp out-of-range bounds --- ...ssesProvider.sol => AddressesProvider.sol} | 190 +++++++++--------- .../AddressesProviderStorage.sol | 28 +++ .../V4AddressesProviderStorage.sol | 26 --- ...ance.sol => AddressesProviderInstance.sol} | 8 +- ...sesProvider.sol => IAddressesProvider.sol} | 81 ++++---- .../interfaces/IAaveV4ConfigEngine.sol | 14 +- ...erBatch.sol => AddressesProviderBatch.sol} | 10 +- src/deployments/libraries/BatchReports.sol | 4 +- ...l => AddressesProviderDeployProcedure.sol} | 18 +- tests/config-engine/AaveV4Payload.t.sol | 2 +- .../AddressesProviderRegistration.t.sol | 22 +- tests/config-engine/BaseConfigEngine.t.sol | 8 +- tests/config-engine/EngineUtils.t.sol | 4 +- tests/config-engine/SpokeEngine.t.sol | 6 +- ...ol => AddressesProvider.Upgradeable.t.sol} | 38 ++-- ...Provider.t.sol => AddressesProvider.t.sol} | 140 ++++++------- .../procedures/ProceduresBase.t.sol | 2 +- ...=> AddressesProviderDeployProcedure.t.sol} | 11 +- ....sol => MockAddressesProviderInstance.sol} | 6 +- ...dressesProviderDeployProcedureWrapper.sol} | 4 +- 20 files changed, 312 insertions(+), 310 deletions(-) rename src/addresses-provider/{V4AddressesProvider.sol => AddressesProvider.sol} (52%) create mode 100644 src/addresses-provider/AddressesProviderStorage.sol delete mode 100644 src/addresses-provider/V4AddressesProviderStorage.sol rename src/addresses-provider/instances/{V4AddressesProviderInstance.sol => AddressesProviderInstance.sol} (64%) rename src/addresses-provider/interfaces/{IV4AddressesProvider.sol => IAddressesProvider.sol} (85%) rename src/deployments/batches/{AaveV4AddressesProviderBatch.sol => AddressesProviderBatch.sol} (66%) rename src/deployments/procedures/deploy/addresses-provider/{AaveV4AddressesProviderDeployProcedure.sol => AddressesProviderDeployProcedure.sol} (59%) rename tests/contracts/addresses-provider/{V4AddressesProvider.Upgradeable.t.sol => AddressesProvider.Upgradeable.t.sol} (74%) rename tests/contracts/addresses-provider/{V4AddressesProvider.t.sol => AddressesProvider.t.sol} (83%) rename tests/deployments/procedures/deploy/addresses-provider/{AaveV4AddressesProviderDeployProcedure.t.sol => AddressesProviderDeployProcedure.t.sol} (64%) rename tests/helpers/mocks/{MockV4AddressesProviderInstance.sol => MockAddressesProviderInstance.sol} (77%) rename tests/helpers/mocks/deployments/procedures/{AaveV4AddressesProviderDeployProcedureWrapper.sol => AddressesProviderDeployProcedureWrapper.sol} (50%) diff --git a/src/addresses-provider/V4AddressesProvider.sol b/src/addresses-provider/AddressesProvider.sol similarity index 52% rename from src/addresses-provider/V4AddressesProvider.sol rename to src/addresses-provider/AddressesProvider.sol index a05904c16..b238bf59b 100644 --- a/src/addresses-provider/V4AddressesProvider.sol +++ b/src/addresses-provider/AddressesProvider.sol @@ -3,255 +3,255 @@ pragma solidity 0.8.28; import {Ownable2StepUpgradeable} from 'src/dependencies/openzeppelin-upgradeable/Ownable2StepUpgradeable.sol'; import {EnumerableSet} from 'src/dependencies/openzeppelin/EnumerableSet.sol'; -import {V4AddressesProviderStorage} from 'src/addresses-provider/V4AddressesProviderStorage.sol'; -import {IV4AddressesProvider} from 'src/addresses-provider/interfaces/IV4AddressesProvider.sol'; +import {AddressesProviderStorage} from 'src/addresses-provider/AddressesProviderStorage.sol'; +import {IAddressesProvider} from 'src/addresses-provider/interfaces/IAddressesProvider.sol'; -/// @title V4AddressesProvider +/// @title AddressesProvider /// @author Aave Labs /// @notice Main registry of Aave V4 contract addresses. -abstract contract V4AddressesProvider is - V4AddressesProviderStorage, +abstract contract AddressesProvider is + AddressesProviderStorage, Ownable2StepUpgradeable, - IV4AddressesProvider + IAddressesProvider { using EnumerableSet for *; - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider string public constant CANONICAL_HUB_TAG = 'CANONICAL_HUB'; - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider string public constant CANONICAL_SPOKE_TAG = 'CANONICAL_SPOKE'; - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider string public constant TOKENIZATION_SPOKE_TAG = 'TOKENIZATION_SPOKE'; - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider string public constant TREASURY_SPOKE_TAG = 'TREASURY_SPOKE'; - /// @dev To be overridden by the inheriting V4AddressesProvider instance contract. + /// @dev To be overridden by the inheriting AddressesProvider instance contract. function initialize(address owner) external virtual; - /// @inheritdoc IV4AddressesProvider - function setAddress( + /// @inheritdoc IAddressesProvider + function setEntry( string calldata name, string calldata tag, address newAddress ) external onlyOwner { - _setAddress({name: name, tag: tag, newAddress: newAddress}); + _setEntry({name: name, tag: tag, newAddress: newAddress}); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function setCanonicalHub(string calldata name, address hub) external onlyOwner { - _setAddress({name: name, tag: CANONICAL_HUB_TAG, newAddress: hub}); + _setEntry({name: name, tag: CANONICAL_HUB_TAG, newAddress: hub}); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function setCanonicalSpoke(string calldata name, address spoke) external onlyOwner { - _setAddress({name: name, tag: CANONICAL_SPOKE_TAG, newAddress: spoke}); + _setEntry({name: name, tag: CANONICAL_SPOKE_TAG, newAddress: spoke}); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function setTokenizationSpoke(string calldata name, address spoke) external onlyOwner { - _setAddress({name: name, tag: TOKENIZATION_SPOKE_TAG, newAddress: spoke}); + _setEntry({name: name, tag: TOKENIZATION_SPOKE_TAG, newAddress: spoke}); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function setTreasurySpoke(string calldata name, address spoke) external onlyOwner { - _setAddress({name: name, tag: TREASURY_SPOKE_TAG, newAddress: spoke}); + _setEntry({name: name, tag: TREASURY_SPOKE_TAG, newAddress: spoke}); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getAddress(bytes32 id) external view returns (address) { - return _addressEntries[id].addr; + return _idToEntry[id].addr; } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getAddress(string calldata name, string calldata tag) external view returns (address) { return _getAddress({name: name, tag: tag}); } - /// @inheritdoc IV4AddressesProvider - function getAddressEntry(bytes32 id) external view returns (AddressEntry memory) { - return _addressEntries[id]; + /// @inheritdoc IAddressesProvider + function getEntry(bytes32 id) external view returns (Entry memory) { + return _idToEntry[id]; } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getTagCount() external view returns (uint256) { - return _tags.length(); + return _tagsSet.length(); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getTags() external view returns (string[] memory) { - return _tags.values(); + return _tagsSet.values(); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getTags(uint256 start, uint256 end) external view returns (string[] memory) { - return _tags.values(start, end); + return _tagsSet.values(start, end); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getIdCount(string calldata tag) external view returns (uint256) { - return _taggedIds[tag].length(); + return _tagToIdSet[tag].length(); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getIds(string calldata tag) external view returns (bytes32[] memory) { - return _taggedIds[tag].values(); + return _tagToIdSet[tag].values(); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getIds( string calldata tag, uint256 start, uint256 end ) external view returns (bytes32[] memory) { - return _taggedIds[tag].values(start, end); + return _tagToIdSet[tag].values(start, end); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getAddresses(string calldata tag) external view returns (address[] memory) { - return _toAddresses(_taggedIds[tag].values()); + return _toAddresses(_tagToIdSet[tag].values()); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getAddresses( string calldata tag, uint256 start, uint256 end ) external view returns (address[] memory) { - return _toAddresses(_taggedIds[tag].values(start, end)); + return _toAddresses(_tagToIdSet[tag].values(start, end)); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getAddressIdCount(address addr) external view returns (uint256) { - return _addressIds[addr].length(); + return _addressToIdSet[addr].length(); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getAddressIds(address addr) external view returns (bytes32[] memory) { - return _addressIds[addr].values(); + return _addressToIdSet[addr].values(); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getAddressIds( address addr, uint256 start, uint256 end ) external view returns (bytes32[] memory) { - return _addressIds[addr].values(start, end); + return _addressToIdSet[addr].values(start, end); } - /// @inheritdoc IV4AddressesProvider - function getAddressEntries(address addr) external view returns (AddressEntry[] memory) { - return _toEntries(_addressIds[addr].values()); + /// @inheritdoc IAddressesProvider + function getEntries(address addr) external view returns (Entry[] memory) { + return _toEntries(_addressToIdSet[addr].values()); } - /// @inheritdoc IV4AddressesProvider - function getAddressEntries( + /// @inheritdoc IAddressesProvider + function getEntries( address addr, uint256 start, uint256 end - ) external view returns (AddressEntry[] memory) { - return _toEntries(_addressIds[addr].values(start, end)); + ) external view returns (Entry[] memory) { + return _toEntries(_addressToIdSet[addr].values(start, end)); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getCanonicalHub(string calldata name) external view returns (address) { return _getAddress({name: name, tag: CANONICAL_HUB_TAG}); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getCanonicalHubs() external view returns (address[] memory) { - return _toAddresses(_taggedIds[CANONICAL_HUB_TAG].values()); + return _toAddresses(_tagToIdSet[CANONICAL_HUB_TAG].values()); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getCanonicalHubs(uint256 start, uint256 end) external view returns (address[] memory) { - return _toAddresses(_taggedIds[CANONICAL_HUB_TAG].values(start, end)); + return _toAddresses(_tagToIdSet[CANONICAL_HUB_TAG].values(start, end)); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getCanonicalSpoke(string calldata name) external view returns (address) { return _getAddress({name: name, tag: CANONICAL_SPOKE_TAG}); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getCanonicalSpokes() external view returns (address[] memory) { - return _toAddresses(_taggedIds[CANONICAL_SPOKE_TAG].values()); + return _toAddresses(_tagToIdSet[CANONICAL_SPOKE_TAG].values()); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getCanonicalSpokes(uint256 start, uint256 end) external view returns (address[] memory) { - return _toAddresses(_taggedIds[CANONICAL_SPOKE_TAG].values(start, end)); + return _toAddresses(_tagToIdSet[CANONICAL_SPOKE_TAG].values(start, end)); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getTokenizationSpoke(string calldata name) external view returns (address) { return _getAddress({name: name, tag: TOKENIZATION_SPOKE_TAG}); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getTokenizationSpokes() external view returns (address[] memory) { - return _toAddresses(_taggedIds[TOKENIZATION_SPOKE_TAG].values()); + return _toAddresses(_tagToIdSet[TOKENIZATION_SPOKE_TAG].values()); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getTokenizationSpokes( uint256 start, uint256 end ) external view returns (address[] memory) { - return _toAddresses(_taggedIds[TOKENIZATION_SPOKE_TAG].values(start, end)); + return _toAddresses(_tagToIdSet[TOKENIZATION_SPOKE_TAG].values(start, end)); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getTreasurySpoke(string calldata name) external view returns (address) { return _getAddress({name: name, tag: TREASURY_SPOKE_TAG}); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getTreasurySpokes() external view returns (address[] memory) { - return _toAddresses(_taggedIds[TREASURY_SPOKE_TAG].values()); + return _toAddresses(_tagToIdSet[TREASURY_SPOKE_TAG].values()); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getTreasurySpokes(uint256 start, uint256 end) external view returns (address[] memory) { - return _toAddresses(_taggedIds[TREASURY_SPOKE_TAG].values(start, end)); + return _toAddresses(_tagToIdSet[TREASURY_SPOKE_TAG].values(start, end)); } - /// @inheritdoc IV4AddressesProvider + /// @inheritdoc IAddressesProvider function getId(string calldata name, string calldata tag) external pure returns (bytes32) { return _getId({name: name, tag: tag}); } - function _setAddress(string memory name, string memory tag, address newAddress) internal { + function _setEntry(string memory name, string memory tag, address newAddress) internal { require(bytes(name).length > 0, InvalidName()); require(bytes(tag).length > 0, InvalidTag()); bytes32 id = _getId({name: name, tag: tag}); - AddressEntry memory oldEntry = _addressEntries[id]; + Entry memory oldEntry = _idToEntry[id]; if (newAddress == address(0)) { require(oldEntry.addr != address(0), AddressNotSet(id)); - _taggedIds[oldEntry.tag].remove(id); - if (_taggedIds[oldEntry.tag].length() == 0) { - _tags.remove(oldEntry.tag); + _tagToIdSet[oldEntry.tag].remove(id); + if (_tagToIdSet[oldEntry.tag].length() == 0) { + _tagsSet.remove(oldEntry.tag); } - _addressIds[oldEntry.addr].remove(id); - delete _addressEntries[id]; + _addressToIdSet[oldEntry.addr].remove(id); + delete _idToEntry[id]; } else { require(oldEntry.addr == address(0), AddressAlreadySet(id)); - _addressEntries[id] = AddressEntry({addr: newAddress, name: name, tag: tag}); - _taggedIds[tag].add(id); - _tags.add(tag); - _addressIds[newAddress].add(id); + _idToEntry[id] = Entry({addr: newAddress, name: name, tag: tag}); + _tagToIdSet[tag].add(id); + _tagsSet.add(tag); + _addressToIdSet[newAddress].add(id); } - emit AddressSet(id, name, tag, oldEntry.addr, newAddress); + emit SetEntry(id, name, tag, oldEntry.addr, newAddress); } function _getAddress(string memory name, string memory tag) internal view returns (address) { - return _addressEntries[_getId({name: name, tag: tag})].addr; + return _idToEntry[_getId({name: name, tag: tag})].addr; } function _getId(string memory name, string memory tag) internal pure returns (bytes32) { @@ -261,15 +261,15 @@ abstract contract V4AddressesProvider is function _toAddresses(bytes32[] memory ids) internal view returns (address[] memory) { address[] memory addresses = new address[](ids.length); for (uint256 i = 0; i < ids.length; i++) { - addresses[i] = _addressEntries[ids[i]].addr; + addresses[i] = _idToEntry[ids[i]].addr; } return addresses; } - function _toEntries(bytes32[] memory ids) internal view returns (AddressEntry[] memory) { - AddressEntry[] memory entries = new AddressEntry[](ids.length); + function _toEntries(bytes32[] memory ids) internal view returns (Entry[] memory) { + Entry[] memory entries = new Entry[](ids.length); for (uint256 i = 0; i < ids.length; i++) { - entries[i] = _addressEntries[ids[i]]; + entries[i] = _idToEntry[ids[i]]; } return entries; } diff --git a/src/addresses-provider/AddressesProviderStorage.sol b/src/addresses-provider/AddressesProviderStorage.sol new file mode 100644 index 000000000..021755210 --- /dev/null +++ b/src/addresses-provider/AddressesProviderStorage.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: LicenseRef-BUSL +pragma solidity 0.8.28; + +import {EnumerableSet} from 'src/dependencies/openzeppelin/EnumerableSet.sol'; +import {IAddressesProvider} from 'src/addresses-provider/interfaces/IAddressesProvider.sol'; + +/// @title AddressesProviderStorage +/// @author Aave Labs +/// @notice Storage layout for the AddressesProvider contract. +/// @dev This contract defines all storage variables used by the AddressesProvider. +abstract contract AddressesProviderStorage { + /// @dev Map of entry identifiers to their respective entries. + mapping(bytes32 id => IAddressesProvider.Entry) internal _idToEntry; + + /// @dev Map of tags to their respective sets of entry identifiers. + mapping(string tag => EnumerableSet.Bytes32Set) internal _tagToIdSet; + + /// @dev Set of all tags. + /// @dev A tag is included in the set only if it has at least one registered entry. + EnumerableSet.StringSet internal _tagsSet; + + /// @dev Map of registered addresses to their respective sets of entry identifiers. + /// @dev An address may be registered under more than one entry. + mapping(address addr => EnumerableSet.Bytes32Set) internal _addressToIdSet; + + /// @dev Reserved storage space to allow for future layout updates. + uint256[50] private __gap; +} diff --git a/src/addresses-provider/V4AddressesProviderStorage.sol b/src/addresses-provider/V4AddressesProviderStorage.sol deleted file mode 100644 index 18764db6d..000000000 --- a/src/addresses-provider/V4AddressesProviderStorage.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-BUSL -pragma solidity 0.8.28; - -import {EnumerableSet} from 'src/dependencies/openzeppelin/EnumerableSet.sol'; -import {IV4AddressesProvider} from 'src/addresses-provider/interfaces/IV4AddressesProvider.sol'; - -/// @title V4AddressesProviderStorage -/// @author Aave Labs -/// @notice Storage layout for the V4AddressesProvider contract. -/// @dev This contract defines all storage variables used by the V4AddressesProvider. -abstract contract V4AddressesProviderStorage { - /// @dev Map of entry identifiers to address entries. - mapping(bytes32 id => IV4AddressesProvider.AddressEntry) internal _addressEntries; - - /// @dev Map of tags to set of entry identifiers. - mapping(string tag => EnumerableSet.Bytes32Set ids) internal _taggedIds; - - /// @dev Set of all tags with at least one registered entry. - EnumerableSet.StringSet internal _tags; - - /// @dev Map of registered addresses to set of entry identifiers. - mapping(address addr => EnumerableSet.Bytes32Set ids) internal _addressIds; - - /// @dev Reserved storage space to allow for future layout updates. - uint256[49] private __gap; -} diff --git a/src/addresses-provider/instances/V4AddressesProviderInstance.sol b/src/addresses-provider/instances/AddressesProviderInstance.sol similarity index 64% rename from src/addresses-provider/instances/V4AddressesProviderInstance.sol rename to src/addresses-provider/instances/AddressesProviderInstance.sol index 1d563eb0b..0ab6c71d6 100644 --- a/src/addresses-provider/instances/V4AddressesProviderInstance.sol +++ b/src/addresses-provider/instances/AddressesProviderInstance.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: LicenseRef-BUSL pragma solidity 0.8.28; -import {V4AddressesProvider} from 'src/addresses-provider/V4AddressesProvider.sol'; +import {AddressesProvider} from 'src/addresses-provider/AddressesProvider.sol'; -/// @title V4AddressesProviderInstance +/// @title AddressesProviderInstance /// @author Aave Labs -/// @notice Implementation contract for the V4AddressesProvider. -contract V4AddressesProviderInstance is V4AddressesProvider { +/// @notice Implementation contract for the AddressesProvider. +contract AddressesProviderInstance is AddressesProvider { uint64 public constant ADDRESSES_PROVIDER_REVISION = 1; /// @dev Constructor. diff --git a/src/addresses-provider/interfaces/IV4AddressesProvider.sol b/src/addresses-provider/interfaces/IAddressesProvider.sol similarity index 85% rename from src/addresses-provider/interfaces/IV4AddressesProvider.sol rename to src/addresses-provider/interfaces/IAddressesProvider.sol index 7f3ba996d..718da3cd2 100644 --- a/src/addresses-provider/interfaces/IV4AddressesProvider.sol +++ b/src/addresses-provider/interfaces/IAddressesProvider.sol @@ -1,27 +1,27 @@ // SPDX-License-Identifier: LicenseRef-BUSL pragma solidity ^0.8.0; -/// @title IV4AddressesProvider +/// @title IAddressesProvider /// @author Aave Labs /// @notice Main registry of the Hub and Spoke addresses of an Aave V4 instance. -interface IV4AddressesProvider { - /// @notice Address entry registered under an identifier. +interface IAddressesProvider { + /// @notice Entry registered under an identifier. /// @param addr The registered address. /// @param name The name of the entry. /// @param tag The tag grouping the entry. - struct AddressEntry { + struct Entry { address addr; string name; string tag; } - /// @notice Emitted when the address associated with a name and tag is updated. + /// @notice Emitted when the address of an entry is updated. /// @param id The identifier of the entry. /// @param name The name of the entry. /// @param tag The tag grouping the entry. /// @param oldAddress The previous address of the entry. /// @param newAddress The new address of the entry. - event AddressSet( + event SetEntry( bytes32 indexed id, string name, string tag, @@ -29,10 +29,10 @@ interface IV4AddressesProvider { address indexed newAddress ); - /// @notice Thrown when an empty tag is supplied. + /// @notice Thrown when the specified tag is invalid. error InvalidTag(); - /// @notice Thrown when an empty name is supplied. + /// @notice Thrown when the specified name is invalid. error InvalidName(); /// @notice Thrown when an address is already registered under the identifier. @@ -41,25 +41,13 @@ interface IV4AddressesProvider { /// @notice Thrown when no address is registered under the identifier. error AddressNotSet(bytes32 id); - /// @notice Returns the tag grouping all canonical Hubs. - function CANONICAL_HUB_TAG() external view returns (string memory); - - /// @notice Returns the tag grouping all canonical Spokes. - function CANONICAL_SPOKE_TAG() external view returns (string memory); - - /// @notice Returns the tag grouping all tokenization Spokes. - function TOKENIZATION_SPOKE_TAG() external view returns (string memory); - - /// @notice Returns the tag grouping all treasury Spokes. - function TREASURY_SPOKE_TAG() external view returns (string memory); - /// @notice Associates an address with a name, grouped under a tag. /// @dev Associating the zero address removes the entry and its identifier from enumeration, it reverts if no address is registered. /// @dev Reverts if an address is already registered under the identifier, it must be removed first. /// @param name The name of the entry. /// @param tag The tag grouping the entry. /// @param newAddress The address to associate with the name and tag. - function setAddress(string calldata name, string calldata tag, address newAddress) external; + function setEntry(string calldata name, string calldata tag, address newAddress) external; /// @notice Registers the canonical Hub associated with a name. /// @dev Registering the zero address removes the entry and its identifier from enumeration, it reverts if no address is registered. @@ -100,10 +88,10 @@ interface IV4AddressesProvider { /// @return The address of the entry, the zero address if none is registered. function getAddress(string calldata name, string calldata tag) external view returns (address); - /// @notice Returns the address entry associated with an identifier. + /// @notice Returns the entry associated with an identifier. /// @param id The identifier of the entry. - /// @return The address entry associated with the identifier. - function getAddressEntry(bytes32 id) external view returns (AddressEntry memory); + /// @return The entry associated with the identifier. + function getEntry(bytes32 id) external view returns (Entry memory); /// @notice Returns the number of tags with at least one registered entry. function getTagCount() external view returns (uint256); @@ -112,8 +100,9 @@ interface IV4AddressesProvider { function getTags() external view returns (string[] memory); /// @notice Returns a slice of the tags with at least one registered entry. + /// @dev Out-of-range bounds are clamped to the number of tags, it does not revert. /// @param start The start index of the slice. - /// @param end The end index of the slice, capped to the number of tags. + /// @param end The end index of the slice. /// @return The list of tags in the slice. function getTags(uint256 start, uint256 end) external view returns (string[] memory); @@ -128,9 +117,10 @@ interface IV4AddressesProvider { function getIds(string calldata tag) external view returns (bytes32[] memory); /// @notice Returns a slice of the identifiers of the entries grouped under a tag. + /// @dev Out-of-range bounds are clamped to the number of entries, it does not revert. /// @param tag The tag grouping the entries. /// @param start The start index of the slice. - /// @param end The end index of the slice, capped to the number of entries. + /// @param end The end index of the slice. /// @return The list of identifiers in the slice. function getIds( string calldata tag, @@ -144,9 +134,10 @@ interface IV4AddressesProvider { function getAddresses(string calldata tag) external view returns (address[] memory); /// @notice Returns a slice of the addresses of the entries grouped under a tag. + /// @dev Out-of-range bounds are clamped to the number of entries, it does not revert. /// @param tag The tag grouping the entries. /// @param start The start index of the slice. - /// @param end The end index of the slice, capped to the number of entries. + /// @param end The end index of the slice. /// @return The list of addresses in the slice. function getAddresses( string calldata tag, @@ -165,9 +156,10 @@ interface IV4AddressesProvider { function getAddressIds(address addr) external view returns (bytes32[] memory); /// @notice Returns a slice of the identifiers of the entries registered for an address. + /// @dev Out-of-range bounds are clamped to the number of entries, it does not revert. /// @param addr The registered address. /// @param start The start index of the slice. - /// @param end The end index of the slice, capped to the number of entries. + /// @param end The end index of the slice. /// @return The list of identifiers in the slice. function getAddressIds( address addr, @@ -178,18 +170,19 @@ interface IV4AddressesProvider { /// @notice Returns all entries registered for an address. /// @param addr The registered address. /// @return The list of entries. - function getAddressEntries(address addr) external view returns (AddressEntry[] memory); + function getEntries(address addr) external view returns (Entry[] memory); /// @notice Returns a slice of the entries registered for an address. + /// @dev Out-of-range bounds are clamped to the number of entries, it does not revert. /// @param addr The registered address. /// @param start The start index of the slice. - /// @param end The end index of the slice, capped to the number of entries. + /// @param end The end index of the slice. /// @return The list of entries in the slice. - function getAddressEntries( + function getEntries( address addr, uint256 start, uint256 end - ) external view returns (AddressEntry[] memory); + ) external view returns (Entry[] memory); /// @notice Returns the canonical Hub associated with a name. /// @param name The name of the Hub. @@ -201,8 +194,9 @@ interface IV4AddressesProvider { function getCanonicalHubs() external view returns (address[] memory); /// @notice Returns a slice of the addresses of the registered canonical Hubs. + /// @dev Out-of-range bounds are clamped to the number of entries, it does not revert. /// @param start The start index of the slice. - /// @param end The end index of the slice, capped to the number of entries. + /// @param end The end index of the slice. /// @return The list of canonical Hub addresses in the slice. function getCanonicalHubs(uint256 start, uint256 end) external view returns (address[] memory); @@ -216,8 +210,9 @@ interface IV4AddressesProvider { function getCanonicalSpokes() external view returns (address[] memory); /// @notice Returns a slice of the addresses of the registered canonical Spokes. + /// @dev Out-of-range bounds are clamped to the number of entries, it does not revert. /// @param start The start index of the slice. - /// @param end The end index of the slice, capped to the number of entries. + /// @param end The end index of the slice. /// @return The list of canonical Spoke addresses in the slice. function getCanonicalSpokes(uint256 start, uint256 end) external view returns (address[] memory); @@ -231,8 +226,9 @@ interface IV4AddressesProvider { function getTokenizationSpokes() external view returns (address[] memory); /// @notice Returns a slice of the addresses of the registered tokenization Spokes. + /// @dev Out-of-range bounds are clamped to the number of entries, it does not revert. /// @param start The start index of the slice. - /// @param end The end index of the slice, capped to the number of entries. + /// @param end The end index of the slice. /// @return The list of tokenization Spoke addresses in the slice. function getTokenizationSpokes( uint256 start, @@ -249,8 +245,9 @@ interface IV4AddressesProvider { function getTreasurySpokes() external view returns (address[] memory); /// @notice Returns a slice of the addresses of the registered treasury Spokes. + /// @dev Out-of-range bounds are clamped to the number of entries, it does not revert. /// @param start The start index of the slice. - /// @param end The end index of the slice, capped to the number of entries. + /// @param end The end index of the slice. /// @return The list of treasury Spoke addresses in the slice. function getTreasurySpokes(uint256 start, uint256 end) external view returns (address[] memory); @@ -260,4 +257,16 @@ interface IV4AddressesProvider { /// @param tag The tag grouping the entry. /// @return The identifier of the entry. function getId(string calldata name, string calldata tag) external pure returns (bytes32); + + /// @notice Returns the tag grouping all canonical Hubs. + function CANONICAL_HUB_TAG() external view returns (string memory); + + /// @notice Returns the tag grouping all canonical Spokes. + function CANONICAL_SPOKE_TAG() external view returns (string memory); + + /// @notice Returns the tag grouping all tokenization Spokes. + function TOKENIZATION_SPOKE_TAG() external view returns (string memory); + + /// @notice Returns the tag grouping all treasury Spokes. + function TREASURY_SPOKE_TAG() external view returns (string memory); } diff --git a/src/config-engine/interfaces/IAaveV4ConfigEngine.sol b/src/config-engine/interfaces/IAaveV4ConfigEngine.sol index a793f443b..44bc1df80 100644 --- a/src/config-engine/interfaces/IAaveV4ConfigEngine.sol +++ b/src/config-engine/interfaces/IAaveV4ConfigEngine.sol @@ -6,7 +6,7 @@ import {ISpokeConfigurator} from 'src/spoke/interfaces/ISpokeConfigurator.sol'; import {IHub} from 'src/hub/interfaces/IHub.sol'; import {ISpoke} from 'src/spoke/interfaces/ISpoke.sol'; import {IAssetInterestRateStrategy} from 'src/hub/interfaces/IAssetInterestRateStrategy.sol'; -import {IV4AddressesProvider} from 'src/addresses-provider/interfaces/IV4AddressesProvider.sol'; +import {IAddressesProvider} from 'src/addresses-provider/interfaces/IAddressesProvider.sol'; /// @title IAaveV4ConfigEngine /// @author Aave Labs @@ -25,14 +25,14 @@ interface IAaveV4ConfigEngine { string symbol; } - /// @notice Optional registration of a listed Hub or Spoke on the V4AddressesProvider. + /// @notice Optional registration of a listed Hub or Spoke on the AddressesProvider. /// @dev Left unset (the default), `register` is false and the registration is skipped. /// @dev All fields must be set when `register` is true, and left unset when false; reverts otherwise. - /// @dev addressesProvider The V4AddressesProvider to register the entry on. + /// @dev addressesProvider The AddressesProvider to register the entry on. /// @dev register Whether to register the entry on the addressesProvider. /// @dev name The name to register the entry under. struct AddressesProviderRegistration { - IV4AddressesProvider addressesProvider; + IAddressesProvider addressesProvider; bool register; string name; } @@ -46,10 +46,10 @@ interface IAaveV4ConfigEngine { /// @dev irStrategy The address of the interest rate strategy contract. /// @dev irData The interest rate data to apply to the given asset. /// @dev tokenization The tokenization configuration for the asset. - /// @dev hubRegistration Optional registration of the Hub on the V4AddressesProvider; only allowed + /// @dev hubRegistration Optional registration of the Hub on the AddressesProvider; only allowed /// when the listed asset is the Hub's first (asset id 0), reverts otherwise. /// @dev tokenizationSpokeRegistration Optional registration of the deployed TokenizationSpoke on the - /// V4AddressesProvider; only allowed when a TokenizationSpoke is deployed for this listing. + /// AddressesProvider; only allowed when a TokenizationSpoke is deployed for this listing. struct AssetListing { IHubConfigurator hubConfigurator; address hub; @@ -185,7 +185,7 @@ interface IAaveV4ConfigEngine { /// @dev priceSource The address of the price source. /// @dev config The configuration of the reserve. /// @dev dynamicConfig The dynamic configuration of the reserve. - /// @dev spokeRegistration Optional registration of the Spoke on the V4AddressesProvider; only allowed + /// @dev spokeRegistration Optional registration of the Spoke on the AddressesProvider; only allowed /// when the listed reserve is the Spoke's first (reserve id 0), reverts otherwise. struct ReserveListing { ISpokeConfigurator spokeConfigurator; diff --git a/src/deployments/batches/AaveV4AddressesProviderBatch.sol b/src/deployments/batches/AddressesProviderBatch.sol similarity index 66% rename from src/deployments/batches/AaveV4AddressesProviderBatch.sol rename to src/deployments/batches/AddressesProviderBatch.sol index f8de27ddb..98fe54f8e 100644 --- a/src/deployments/batches/AaveV4AddressesProviderBatch.sol +++ b/src/deployments/batches/AddressesProviderBatch.sol @@ -2,16 +2,16 @@ pragma solidity ^0.8.0; import {BatchReports} from 'src/deployments/libraries/BatchReports.sol'; -import {AaveV4AddressesProviderDeployProcedure} from 'src/deployments/procedures/deploy/addresses-provider/AaveV4AddressesProviderDeployProcedure.sol'; +import {AddressesProviderDeployProcedure} from 'src/deployments/procedures/deploy/addresses-provider/AddressesProviderDeployProcedure.sol'; -/// @title AaveV4AddressesProviderBatch +/// @title AddressesProviderBatch /// @author Aave Labs -/// @notice Deploys the V4AddressesProvider contract, producing a batch report. -contract AaveV4AddressesProviderBatch is AaveV4AddressesProviderDeployProcedure { +/// @notice Deploys the AddressesProvider contract, producing a batch report. +contract AddressesProviderBatch is AddressesProviderDeployProcedure { BatchReports.AddressesProviderBatchReport internal _report; /// @dev Constructor. - /// @param owner_ The owner of the V4AddressesProvider proxy admin and initializer. + /// @param owner_ The owner of the AddressesProvider proxy admin and initializer. /// @param salt_ The CREATE2 salt for deterministic deployment. constructor(address owner_, bytes32 salt_) { ( diff --git a/src/deployments/libraries/BatchReports.sol b/src/deployments/libraries/BatchReports.sol index a90ec0cc0..84705e79f 100644 --- a/src/deployments/libraries/BatchReports.sol +++ b/src/deployments/libraries/BatchReports.sol @@ -40,8 +40,8 @@ library BatchReports { address treasurySpoke; } - /// @dev addressesProviderProxy The deployed V4AddressesProvider proxy contract address. - /// @dev addressesProviderImplementation The deployed V4AddressesProvider implementation contract address. + /// @dev addressesProviderProxy The deployed AddressesProvider proxy contract address. + /// @dev addressesProviderImplementation The deployed AddressesProvider implementation contract address. struct AddressesProviderBatchReport { address addressesProviderProxy; address addressesProviderImplementation; diff --git a/src/deployments/procedures/deploy/addresses-provider/AaveV4AddressesProviderDeployProcedure.sol b/src/deployments/procedures/deploy/addresses-provider/AddressesProviderDeployProcedure.sol similarity index 59% rename from src/deployments/procedures/deploy/addresses-provider/AaveV4AddressesProviderDeployProcedure.sol rename to src/deployments/procedures/deploy/addresses-provider/AddressesProviderDeployProcedure.sol index b02d0ee05..89d2c02c6 100644 --- a/src/deployments/procedures/deploy/addresses-provider/AaveV4AddressesProviderDeployProcedure.sol +++ b/src/deployments/procedures/deploy/addresses-provider/AddressesProviderDeployProcedure.sol @@ -3,17 +3,17 @@ pragma solidity ^0.8.0; import {AaveV4DeployProcedureBase} from 'src/deployments/procedures/AaveV4DeployProcedureBase.sol'; import {Create2Utils} from 'src/deployments/utils/libraries/Create2Utils.sol'; -import {V4AddressesProviderInstance} from 'src/addresses-provider/instances/V4AddressesProviderInstance.sol'; +import {AddressesProviderInstance} from 'src/addresses-provider/instances/AddressesProviderInstance.sol'; -/// @title AaveV4AddressesProviderDeployProcedure +/// @title AddressesProviderDeployProcedure /// @author Aave Labs -/// @notice Deploys the V4AddressesProvider contract behind a transparent proxy. -contract AaveV4AddressesProviderDeployProcedure is AaveV4DeployProcedureBase { - /// @notice Deploys a V4AddressesProvider instance via CREATE2 and sets up a transparent proxy. - /// @param owner The owner of the proxy admin and the V4AddressesProvider initializer. +/// @notice Deploys the AddressesProvider contract behind a transparent proxy. +contract AddressesProviderDeployProcedure is AaveV4DeployProcedureBase { + /// @notice Deploys a AddressesProvider instance via CREATE2 and sets up a transparent proxy. + /// @param owner The owner of the proxy admin and the AddressesProvider initializer. /// @param salt The CREATE2 salt for deterministic deployment. /// @return addressesProviderProxy The address of the deployed transparent proxy. - /// @return addressesProviderImplementation The address of the deployed V4AddressesProvider implementation contract. + /// @return addressesProviderImplementation The address of the deployed AddressesProvider implementation contract. function _deployAddressesProvider( address owner, bytes32 salt @@ -21,13 +21,13 @@ contract AaveV4AddressesProviderDeployProcedure is AaveV4DeployProcedureBase { require(owner != address(0), 'invalid owner'); addressesProviderImplementation = Create2Utils.create2Deploy({ salt: salt, - bytecode: type(V4AddressesProviderInstance).creationCode + bytecode: type(AddressesProviderInstance).creationCode }); addressesProviderProxy = Create2Utils.proxify({ salt: salt, logic: addressesProviderImplementation, initialOwner: owner, - data: abi.encodeCall(V4AddressesProviderInstance.initialize, (owner)) + data: abi.encodeCall(AddressesProviderInstance.initialize, (owner)) }); return (addressesProviderProxy, addressesProviderImplementation); } diff --git a/tests/config-engine/AaveV4Payload.t.sol b/tests/config-engine/AaveV4Payload.t.sol index b1800d36a..230725044 100644 --- a/tests/config-engine/AaveV4Payload.t.sol +++ b/tests/config-engine/AaveV4Payload.t.sol @@ -670,7 +670,7 @@ contract AaveV4PayloadTest is BaseConfigEngineTest { liquidationFee: 10_00 }), spokeRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ - addressesProvider: IV4AddressesProvider(address(0)), + addressesProvider: IAddressesProvider(address(0)), register: false, name: '' }) diff --git a/tests/config-engine/AddressesProviderRegistration.t.sol b/tests/config-engine/AddressesProviderRegistration.t.sol index d00ba12ed..2cbee007e 100644 --- a/tests/config-engine/AddressesProviderRegistration.t.sol +++ b/tests/config-engine/AddressesProviderRegistration.t.sol @@ -4,13 +4,13 @@ pragma solidity ^0.8.0; import 'tests/config-engine/BaseConfigEngine.t.sol'; import {TransparentUpgradeableProxy} from 'src/dependencies/openzeppelin/TransparentUpgradeableProxy.sol'; -import {V4AddressesProviderInstance} from 'src/addresses-provider/instances/V4AddressesProviderInstance.sol'; +import {AddressesProviderInstance} from 'src/addresses-provider/instances/AddressesProviderInstance.sol'; -/// @notice Tests the optional V4AddressesProvider registration during Hub asset and Spoke reserve +/// @notice Tests the optional AddressesProvider registration during Hub asset and Spoke reserve /// listings. The environment is intentionally left unseeded so the first listed asset/reserve has /// id 0, which is the gate for registering a newly configured Hub/Spoke. contract AddressesProviderRegistrationTest is BaseConfigEngineTest { - IV4AddressesProvider internal provider; + IAddressesProvider internal provider; function setUp() public override { super.setUp(); @@ -19,7 +19,7 @@ contract AddressesProviderRegistrationTest is BaseConfigEngineTest { } function _registration( - IV4AddressesProvider addressesProvider, + IAddressesProvider addressesProvider, string memory name ) internal pure returns (IAaveV4ConfigEngine.AddressesProviderRegistration memory) { return @@ -30,14 +30,14 @@ contract AddressesProviderRegistrationTest is BaseConfigEngineTest { }); } - function _deployAddressesProvider(address owner) internal returns (IV4AddressesProvider) { + function _deployAddressesProvider(address owner) internal returns (IAddressesProvider) { return - IV4AddressesProvider( + IAddressesProvider( address( new TransparentUpgradeableProxy( - address(new V4AddressesProviderInstance()), + address(new AddressesProviderInstance()), ADMIN, - abi.encodeCall(V4AddressesProviderInstance.initialize, (owner)) + abi.encodeCall(AddressesProviderInstance.initialize, (owner)) ) ) ); @@ -73,7 +73,7 @@ contract AddressesProviderRegistrationTest is BaseConfigEngineTest { function test_executeHubAssetListings_registerHub_revertsWhenNoProvider() public { IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); listing.underlying = address(weth); - listing.hubRegistration = _registration(IV4AddressesProvider(address(0)), 'CORE'); + listing.hubRegistration = _registration(IAddressesProvider(address(0)), 'CORE'); vm.expectRevert(HubEngine.InvalidAddressesProviderRegistration.selector); engine.executeHubAssetListings(_toAssetListingArray(listing)); @@ -112,7 +112,7 @@ contract AddressesProviderRegistrationTest is BaseConfigEngineTest { vm.expectRevert( abi.encodeWithSelector( - IV4AddressesProvider.AddressAlreadySet.selector, + IAddressesProvider.AddressAlreadySet.selector, provider.getId('CORE', provider.CANONICAL_HUB_TAG()) ) ); @@ -232,7 +232,7 @@ contract AddressesProviderRegistrationTest is BaseConfigEngineTest { IAaveV4ConfigEngine.ReserveListing memory listing = _defaultReserveListing(); listing.underlying = address(weth); listing.priceSource = _deployMockPriceFeed(spoke1(), tokenList[TOKEN_WETH].priceFeed); - listing.spokeRegistration = _registration(IV4AddressesProvider(address(0)), 'MAIN'); + listing.spokeRegistration = _registration(IAddressesProvider(address(0)), 'MAIN'); vm.expectRevert(SpokeEngine.InvalidAddressesProviderRegistration.selector); engine.executeSpokeReserveListings(_toReserveListingArray(listing)); diff --git a/tests/config-engine/BaseConfigEngine.t.sol b/tests/config-engine/BaseConfigEngine.t.sol index a78561270..d7b4d042b 100644 --- a/tests/config-engine/BaseConfigEngine.t.sol +++ b/tests/config-engine/BaseConfigEngine.t.sol @@ -28,7 +28,7 @@ import {Create2TestHelper} from 'tests/utils/Create2TestHelper.sol'; import {AaveV4Payload} from 'src/config-engine/AaveV4Payload.sol'; import {AaveV4ConfigEngine} from 'src/config-engine/AaveV4ConfigEngine.sol'; import {IAaveV4ConfigEngine} from 'src/config-engine/interfaces/IAaveV4ConfigEngine.sol'; -import {IV4AddressesProvider} from 'src/addresses-provider/interfaces/IV4AddressesProvider.sol'; +import {IAddressesProvider} from 'src/addresses-provider/interfaces/IAddressesProvider.sol'; import {EngineFlags} from 'src/config-engine/libraries/EngineFlags.sol'; import {AccessManagerEngine} from 'src/config-engine/libraries/AccessManagerEngine.sol'; import {HubEngine} from 'src/config-engine/libraries/HubEngine.sol'; @@ -339,12 +339,12 @@ abstract contract BaseConfigEngineTest is Test, Create2TestHelper { symbol: '' }), hubRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ - addressesProvider: IV4AddressesProvider(address(0)), + addressesProvider: IAddressesProvider(address(0)), register: false, name: '' }), tokenizationSpokeRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ - addressesProvider: IV4AddressesProvider(address(0)), + addressesProvider: IAddressesProvider(address(0)), register: false, name: '' }) @@ -480,7 +480,7 @@ abstract contract BaseConfigEngineTest is Test, Create2TestHelper { config: _defaultReserveConfig(), dynamicConfig: _defaultDynamicReserveConfig(), spokeRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ - addressesProvider: IV4AddressesProvider(address(0)), + addressesProvider: IAddressesProvider(address(0)), register: false, name: '' }) diff --git a/tests/config-engine/EngineUtils.t.sol b/tests/config-engine/EngineUtils.t.sol index eb47ea413..e4009b193 100644 --- a/tests/config-engine/EngineUtils.t.sol +++ b/tests/config-engine/EngineUtils.t.sol @@ -5,7 +5,7 @@ import {Test} from 'forge-std/Test.sol'; import {EngineUtils} from 'src/config-engine/libraries/EngineUtils.sol'; import {IAaveV4ConfigEngine} from 'src/config-engine/interfaces/IAaveV4ConfigEngine.sol'; -import {IV4AddressesProvider} from 'src/addresses-provider/interfaces/IV4AddressesProvider.sol'; +import {IAddressesProvider} from 'src/addresses-provider/interfaces/IAddressesProvider.sol'; /// @dev Wrapper to call EngineUtils library functions externally. contract EngineUtilsHarness { @@ -30,7 +30,7 @@ contract EngineUtilsTest is Test { ) internal pure returns (IAaveV4ConfigEngine.AddressesProviderRegistration memory) { return IAaveV4ConfigEngine.AddressesProviderRegistration({ - addressesProvider: IV4AddressesProvider(addressesProvider), + addressesProvider: IAddressesProvider(addressesProvider), register: register, name: name }); diff --git a/tests/config-engine/SpokeEngine.t.sol b/tests/config-engine/SpokeEngine.t.sol index 47d82a778..4d9131eb7 100644 --- a/tests/config-engine/SpokeEngine.t.sol +++ b/tests/config-engine/SpokeEngine.t.sol @@ -634,7 +634,7 @@ contract SpokeEngineTest is BaseConfigEngineTest { liquidationFee: 2_00 }), spokeRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ - addressesProvider: IV4AddressesProvider(address(0)), + addressesProvider: IAddressesProvider(address(0)), register: false, name: '' }) @@ -932,7 +932,7 @@ contract SpokeEngineTest is BaseConfigEngineTest { config: _defaultReserveConfig(), dynamicConfig: _defaultDynamicReserveConfig(), spokeRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ - addressesProvider: IV4AddressesProvider(address(0)), + addressesProvider: IAddressesProvider(address(0)), register: false, name: '' }) @@ -947,7 +947,7 @@ contract SpokeEngineTest is BaseConfigEngineTest { config: _defaultReserveConfig(), dynamicConfig: _defaultDynamicReserveConfig(), spokeRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ - addressesProvider: IV4AddressesProvider(address(0)), + addressesProvider: IAddressesProvider(address(0)), register: false, name: '' }) diff --git a/tests/contracts/addresses-provider/V4AddressesProvider.Upgradeable.t.sol b/tests/contracts/addresses-provider/AddressesProvider.Upgradeable.t.sol similarity index 74% rename from tests/contracts/addresses-provider/V4AddressesProvider.Upgradeable.t.sol rename to tests/contracts/addresses-provider/AddressesProvider.Upgradeable.t.sol index 441134dbe..d644b0621 100644 --- a/tests/contracts/addresses-provider/V4AddressesProvider.Upgradeable.t.sol +++ b/tests/contracts/addresses-provider/AddressesProvider.Upgradeable.t.sol @@ -10,11 +10,11 @@ import { TransparentUpgradeableProxy, ITransparentUpgradeableProxy } from 'src/dependencies/openzeppelin/TransparentUpgradeableProxy.sol'; -import {V4AddressesProviderInstance} from 'src/addresses-provider/instances/V4AddressesProviderInstance.sol'; -import {MockV4AddressesProviderInstance} from 'tests/helpers/mocks/MockV4AddressesProviderInstance.sol'; +import {AddressesProviderInstance} from 'src/addresses-provider/instances/AddressesProviderInstance.sol'; +import {MockAddressesProviderInstance} from 'tests/helpers/mocks/MockAddressesProviderInstance.sol'; import {ProxyHelpers} from 'tests/helpers/commons/ProxyHelpers.sol'; -contract V4AddressesProviderUpgradeableTest is Test, ProxyHelpers { +contract AddressesProviderUpgradeableTest is Test, ProxyHelpers { address internal OWNER = makeAddr('OWNER'); address internal proxyAdminOwner = makeAddr('proxyAdminOwner'); @@ -23,7 +23,7 @@ contract V4AddressesProviderUpgradeableTest is Test, ProxyHelpers { vm.expectEmit(implAddress); emit Initializable.Initialized(type(uint64).max); - MockV4AddressesProviderInstance impl = new MockV4AddressesProviderInstance(revision); + MockAddressesProviderInstance impl = new MockAddressesProviderInstance(revision); assertEq(address(impl), implAddress); assertEq(impl.ADDRESSES_PROVIDER_REVISION(), revision); @@ -36,7 +36,7 @@ contract V4AddressesProviderUpgradeableTest is Test, ProxyHelpers { function test_proxy_constructor_fuzz(uint64 revision) public { revision = uint64(bound(revision, 1, type(uint64).max)); - MockV4AddressesProviderInstance impl = new MockV4AddressesProviderInstance(revision); + MockAddressesProviderInstance impl = new MockAddressesProviderInstance(revision); address proxyAddress = vm.computeCreateAddress(address(this), vm.getNonce(address(this))); address proxyAdminAddress = vm.computeCreateAddress(proxyAddress, 1); @@ -63,25 +63,25 @@ contract V4AddressesProviderUpgradeableTest is Test, ProxyHelpers { function test_proxy_reinitialization_fuzz(uint64 initialRevision) public { initialRevision = uint64(bound(initialRevision, 1, type(uint64).max - 1)); - MockV4AddressesProviderInstance impl = new MockV4AddressesProviderInstance(initialRevision); + MockAddressesProviderInstance impl = new MockAddressesProviderInstance(initialRevision); ITransparentUpgradeableProxy proxy = ITransparentUpgradeableProxy(_proxify(address(impl))); uint64 secondRevision = uint64(vm.randomUint(initialRevision + 1, type(uint64).max)); - MockV4AddressesProviderInstance impl2 = new MockV4AddressesProviderInstance(secondRevision); + MockAddressesProviderInstance impl2 = new MockAddressesProviderInstance(secondRevision); vm.expectEmit(address(proxy)); emit OwnableUpgradeable.OwnershipTransferred(OWNER, OWNER); vm.prank(_getProxyAdminAddress(address(proxy))); proxy.upgradeToAndCall( address(impl2), - abi.encodeCall(MockV4AddressesProviderInstance.initialize, (OWNER)) + abi.encodeCall(MockAddressesProviderInstance.initialize, (OWNER)) ); assertEq(Ownable2StepUpgradeable(address(proxy)).owner(), OWNER); } function test_proxy_constructor_revertsWith_InvalidInitialization_ZeroRevision() public { - MockV4AddressesProviderInstance impl = new MockV4AddressesProviderInstance(0); + MockAddressesProviderInstance impl = new MockAddressesProviderInstance(0); vm.expectRevert(Initializable.InvalidInitialization.selector); _proxify(address(impl)); @@ -92,48 +92,48 @@ contract V4AddressesProviderUpgradeableTest is Test, ProxyHelpers { ) public { initialRevision = uint64(bound(initialRevision, 1, type(uint64).max)); - MockV4AddressesProviderInstance impl = new MockV4AddressesProviderInstance(initialRevision); + MockAddressesProviderInstance impl = new MockAddressesProviderInstance(initialRevision); ITransparentUpgradeableProxy proxy = ITransparentUpgradeableProxy(_proxify(address(impl))); vm.expectRevert(Initializable.InvalidInitialization.selector); vm.prank(_getProxyAdminAddress(address(proxy))); proxy.upgradeToAndCall( address(impl), - abi.encodeCall(MockV4AddressesProviderInstance.initialize, (OWNER)) + abi.encodeCall(MockAddressesProviderInstance.initialize, (OWNER)) ); uint64 secondRevision = uint64(vm.randomUint(0, initialRevision)); - MockV4AddressesProviderInstance impl2 = new MockV4AddressesProviderInstance(secondRevision); + MockAddressesProviderInstance impl2 = new MockAddressesProviderInstance(secondRevision); vm.expectRevert(Initializable.InvalidInitialization.selector); vm.prank(_getProxyAdminAddress(address(proxy))); proxy.upgradeToAndCall( address(impl2), - abi.encodeCall(MockV4AddressesProviderInstance.initialize, (OWNER)) + abi.encodeCall(MockAddressesProviderInstance.initialize, (OWNER)) ); } function test_proxy_constructor_revertsWith_InvalidAddress() public { - V4AddressesProviderInstance impl = new V4AddressesProviderInstance(); + AddressesProviderInstance impl = new AddressesProviderInstance(); vm.expectRevert( abi.encodeWithSelector(OwnableUpgradeable.OwnableInvalidOwner.selector, address(0)) ); new TransparentUpgradeableProxy( address(impl), proxyAdminOwner, - abi.encodeCall(V4AddressesProviderInstance.initialize, (address(0))) + abi.encodeCall(AddressesProviderInstance.initialize, (address(0))) ); } function test_proxy_reinitialization_revertsWith_CallerNotProxyAdmin() public { - V4AddressesProviderInstance impl = new V4AddressesProviderInstance(); + AddressesProviderInstance impl = new AddressesProviderInstance(); ITransparentUpgradeableProxy proxy = ITransparentUpgradeableProxy(_proxify(address(impl))); - V4AddressesProviderInstance impl2 = new V4AddressesProviderInstance(); + AddressesProviderInstance impl2 = new AddressesProviderInstance(); vm.expectRevert(); vm.prank(makeAddr('user')); proxy.upgradeToAndCall( address(impl2), - abi.encodeCall(V4AddressesProviderInstance.initialize, (OWNER)) + abi.encodeCall(AddressesProviderInstance.initialize, (OWNER)) ); } @@ -143,7 +143,7 @@ contract V4AddressesProviderUpgradeableTest is Test, ProxyHelpers { new TransparentUpgradeableProxy( impl, proxyAdminOwner, - abi.encodeCall(V4AddressesProviderInstance.initialize, (OWNER)) + abi.encodeCall(AddressesProviderInstance.initialize, (OWNER)) ) ); } diff --git a/tests/contracts/addresses-provider/V4AddressesProvider.t.sol b/tests/contracts/addresses-provider/AddressesProvider.t.sol similarity index 83% rename from tests/contracts/addresses-provider/V4AddressesProvider.t.sol rename to tests/contracts/addresses-provider/AddressesProvider.t.sol index 2cfc87e23..2922e4644 100644 --- a/tests/contracts/addresses-provider/V4AddressesProvider.t.sol +++ b/tests/contracts/addresses-provider/AddressesProvider.t.sol @@ -4,23 +4,23 @@ pragma solidity ^0.8.0; import {Test} from 'forge-std/Test.sol'; import {OwnableUpgradeable} from 'src/dependencies/openzeppelin-upgradeable/OwnableUpgradeable.sol'; import {TransparentUpgradeableProxy} from 'src/dependencies/openzeppelin/TransparentUpgradeableProxy.sol'; -import {V4AddressesProvider} from 'src/addresses-provider/V4AddressesProvider.sol'; -import {V4AddressesProviderInstance} from 'src/addresses-provider/instances/V4AddressesProviderInstance.sol'; -import {IV4AddressesProvider} from 'src/addresses-provider/interfaces/IV4AddressesProvider.sol'; +import {AddressesProvider} from 'src/addresses-provider/AddressesProvider.sol'; +import {AddressesProviderInstance} from 'src/addresses-provider/instances/AddressesProviderInstance.sol'; +import {IAddressesProvider} from 'src/addresses-provider/interfaces/IAddressesProvider.sol'; -contract V4AddressesProviderTest is Test { +contract AddressesProviderTest is Test { address internal OWNER = makeAddr('OWNER'); address internal PROXY_ADMIN_OWNER = makeAddr('PROXY_ADMIN_OWNER'); - V4AddressesProvider internal provider; + AddressesProvider internal provider; function setUp() public { - provider = V4AddressesProvider( + provider = AddressesProvider( address( new TransparentUpgradeableProxy( - address(new V4AddressesProviderInstance()), + address(new AddressesProviderInstance()), PROXY_ADMIN_OWNER, - abi.encodeCall(V4AddressesProviderInstance.initialize, (OWNER)) + abi.encodeCall(AddressesProviderInstance.initialize, (OWNER)) ) ) ); @@ -73,26 +73,20 @@ contract V4AddressesProviderTest is Test { ); } - function test_setAddress() public { + function test_setEntry() public { bytes32 id = _id('CONFIG_ENGINE', 'PERIPHERY'); address configEngine = makeAddr('CONFIG_ENGINE'); vm.expectEmit(address(provider)); - emit IV4AddressesProvider.AddressSet( - id, - 'CONFIG_ENGINE', - 'PERIPHERY', - address(0), - configEngine - ); + emit IAddressesProvider.SetEntry(id, 'CONFIG_ENGINE', 'PERIPHERY', address(0), configEngine); vm.prank(OWNER); - provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: configEngine}); + provider.setEntry({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: configEngine}); assertEq(provider.getAddress(id), configEngine); assertEq(provider.getAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY'}), configEngine); - IV4AddressesProvider.AddressEntry memory entry = provider.getAddressEntry(id); + IAddressesProvider.Entry memory entry = provider.getEntry(id); assertEq(entry.addr, configEngine); assertEq(entry.name, 'CONFIG_ENGINE'); assertEq(entry.tag, 'PERIPHERY'); @@ -110,35 +104,35 @@ contract V4AddressesProviderTest is Test { assertEq(addressIds[0], id); } - function test_setAddress_remove() public { + function test_setEntry_remove() public { bytes32 id = _id('CONFIG_ENGINE', 'PERIPHERY'); address configEngine = makeAddr('CONFIG_ENGINE'); vm.startPrank(OWNER); - provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: configEngine}); - provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); + provider.setEntry({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: configEngine}); + provider.setEntry({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); vm.stopPrank(); assertEq(provider.getAddress(id), address(0)); - assertEq(provider.getAddressEntry(id).tag, ''); - assertEq(provider.getAddressEntry(id).name, ''); + assertEq(provider.getEntry(id).tag, ''); + assertEq(provider.getEntry(id).name, ''); assertEq(provider.getIds('PERIPHERY').length, 0); assertEq(provider.getTags().length, 0); assertEq(provider.getAddressIds(configEngine).length, 0); } - function test_setAddress_removeThenSet() public { + function test_setEntry_removeThenSet() public { bytes32 id = _id('CONFIG_ENGINE', 'PERIPHERY'); address newConfigEngine = makeAddr('NEW_CONFIG_ENGINE'); vm.startPrank(OWNER); - provider.setAddress({ + provider.setEntry({ name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: makeAddr('CONFIG_ENGINE') }); - provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); - provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: newConfigEngine}); + provider.setEntry({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); + provider.setEntry({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: newConfigEngine}); vm.stopPrank(); assertEq(provider.getAddress(id), newConfigEngine); @@ -148,26 +142,26 @@ contract V4AddressesProviderTest is Test { assertEq(ids[0], id); } - function test_setAddress_revertsWith_AddressAlreadySet() public { + function test_setEntry_revertsWith_AddressAlreadySet() public { bytes32 id = _id('CONFIG_ENGINE', 'PERIPHERY'); address configEngine = makeAddr('CONFIG_ENGINE'); vm.startPrank(OWNER); - provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: configEngine}); + provider.setEntry({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: configEngine}); - vm.expectRevert(abi.encodeWithSelector(IV4AddressesProvider.AddressAlreadySet.selector, id)); - provider.setAddress({ + vm.expectRevert(abi.encodeWithSelector(IAddressesProvider.AddressAlreadySet.selector, id)); + provider.setEntry({ name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: makeAddr('NEW_CONFIG_ENGINE') }); - vm.expectRevert(abi.encodeWithSelector(IV4AddressesProvider.AddressAlreadySet.selector, id)); - provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: configEngine}); + vm.expectRevert(abi.encodeWithSelector(IAddressesProvider.AddressAlreadySet.selector, id)); + provider.setEntry({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: configEngine}); vm.stopPrank(); } - function test_setAddress_noIdCollision() public { + function test_setEntry_noIdCollision() public { // With abi.encode, ('A_B', 'C') and ('A', 'B_C') resolve to distinct identifiers. bytes32 firstId = _id('A_B', 'C'); bytes32 secondId = _id('A', 'B_C'); @@ -179,22 +173,22 @@ contract V4AddressesProviderTest is Test { address second = makeAddr('SECOND'); vm.startPrank(OWNER); - provider.setAddress({name: 'A_B', tag: 'C', newAddress: first}); - provider.setAddress({name: 'A', tag: 'B_C', newAddress: second}); + provider.setEntry({name: 'A_B', tag: 'C', newAddress: first}); + provider.setEntry({name: 'A', tag: 'B_C', newAddress: second}); vm.stopPrank(); assertEq(provider.getAddress({name: 'A_B', tag: 'C'}), first); assertEq(provider.getAddress({name: 'A', tag: 'B_C'}), second); } - function test_setAddress_sameAddressUnderMultipleIds() public { + function test_setEntry_sameAddressUnderMultipleIds() public { address configEngine = makeAddr('CONFIG_ENGINE'); vm.startPrank(OWNER); - provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: configEngine}); - provider.setAddress({name: 'ENGINE', tag: 'PERIPHERY', newAddress: configEngine}); - provider.setAddress({name: 'CONFIG_ENGINE', tag: 'ENGINE', newAddress: configEngine}); - provider.setAddress({name: 'V3_CONFIG_ENGINE', tag: 'V3_PERIPHERY', newAddress: configEngine}); + provider.setEntry({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: configEngine}); + provider.setEntry({name: 'ENGINE', tag: 'PERIPHERY', newAddress: configEngine}); + provider.setEntry({name: 'CONFIG_ENGINE', tag: 'ENGINE', newAddress: configEngine}); + provider.setEntry({name: 'V3_CONFIG_ENGINE', tag: 'V3_PERIPHERY', newAddress: configEngine}); vm.stopPrank(); assertEq(provider.getAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY'}), configEngine); @@ -222,7 +216,7 @@ contract V4AddressesProviderTest is Test { assertEq(addressIds[2], _id('CONFIG_ENGINE', 'ENGINE')); assertEq(addressIds[3], _id('V3_CONFIG_ENGINE', 'V3_PERIPHERY')); - IV4AddressesProvider.AddressEntry[] memory entries = provider.getAddressEntries(configEngine); + IAddressesProvider.Entry[] memory entries = provider.getEntries(configEngine); assertEq(entries.length, 4); assertEq(entries[0].name, 'CONFIG_ENGINE'); assertEq(entries[0].tag, 'PERIPHERY'); @@ -232,7 +226,7 @@ contract V4AddressesProviderTest is Test { // removing one entry does not affect the other entries of the same address vm.prank(OWNER); - provider.setAddress({name: 'ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); + provider.setEntry({name: 'ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); assertEq(provider.getAddress({name: 'ENGINE', tag: 'PERIPHERY'}), address(0)); assertEq(provider.getAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY'}), configEngine); @@ -266,52 +260,52 @@ contract V4AddressesProviderTest is Test { assertEq(treasurySpokes.length, 1); assertEq(treasurySpokes[0], sharedSpoke); - IV4AddressesProvider.AddressEntry[] memory entries = provider.getAddressEntries(sharedSpoke); + IAddressesProvider.Entry[] memory entries = provider.getEntries(sharedSpoke); assertEq(entries.length, 3); assertEq(entries[0].tag, 'CANONICAL_SPOKE'); assertEq(entries[1].tag, 'TOKENIZATION_SPOKE'); assertEq(entries[2].tag, 'TREASURY_SPOKE'); } - function test_setAddress_remove_revertsWith_AddressNotSet() public { + function test_setEntry_remove_revertsWith_AddressNotSet() public { bytes32 id = _id('CONFIG_ENGINE', 'PERIPHERY'); vm.startPrank(OWNER); - vm.expectRevert(abi.encodeWithSelector(IV4AddressesProvider.AddressNotSet.selector, id)); - provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); + vm.expectRevert(abi.encodeWithSelector(IAddressesProvider.AddressNotSet.selector, id)); + provider.setEntry({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); - provider.setAddress({ + provider.setEntry({ name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: makeAddr('CONFIG_ENGINE') }); - provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); + provider.setEntry({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); - vm.expectRevert(abi.encodeWithSelector(IV4AddressesProvider.AddressNotSet.selector, id)); - provider.setAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); + vm.expectRevert(abi.encodeWithSelector(IAddressesProvider.AddressNotSet.selector, id)); + provider.setEntry({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); vm.stopPrank(); } - function test_setAddress_revertsWith_InvalidName() public { - vm.expectRevert(IV4AddressesProvider.InvalidName.selector); + function test_setEntry_revertsWith_InvalidName() public { + vm.expectRevert(IAddressesProvider.InvalidName.selector); vm.prank(OWNER); - provider.setAddress({name: '', tag: 'PERIPHERY', newAddress: makeAddr('CONFIG_ENGINE')}); + provider.setEntry({name: '', tag: 'PERIPHERY', newAddress: makeAddr('CONFIG_ENGINE')}); } - function test_setAddress_revertsWith_InvalidTag() public { - vm.expectRevert(IV4AddressesProvider.InvalidTag.selector); + function test_setEntry_revertsWith_InvalidTag() public { + vm.expectRevert(IAddressesProvider.InvalidTag.selector); vm.prank(OWNER); - provider.setAddress({name: 'CONFIG_ENGINE', tag: '', newAddress: makeAddr('CONFIG_ENGINE')}); + provider.setEntry({name: 'CONFIG_ENGINE', tag: '', newAddress: makeAddr('CONFIG_ENGINE')}); } - function test_setAddress_revertsWith_OwnableUnauthorizedAccount() public { + function test_setEntry_revertsWith_OwnableUnauthorizedAccount() public { address caller = makeAddr('caller'); vm.expectRevert( abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, caller) ); vm.prank(caller); - provider.setAddress({ + provider.setEntry({ name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: makeAddr('CONFIG_ENGINE') @@ -325,7 +319,7 @@ contract V4AddressesProviderTest is Test { vm.startPrank(OWNER); vm.expectEmit(address(provider)); - emit IV4AddressesProvider.AddressSet( + emit IAddressesProvider.SetEntry( _id('CORE', 'CANONICAL_HUB'), 'CORE', 'CANONICAL_HUB', @@ -342,9 +336,7 @@ contract V4AddressesProviderTest is Test { assertEq(provider.getCanonicalHub('PRIME'), primeHub); assertEq(provider.getAddress(_id('CORE', 'CANONICAL_HUB')), coreHub); - IV4AddressesProvider.AddressEntry memory entry = provider.getAddressEntry( - _id('CORE', 'CANONICAL_HUB') - ); + IAddressesProvider.Entry memory entry = provider.getEntry(_id('CORE', 'CANONICAL_HUB')); assertEq(entry.name, 'CORE'); assertEq(entry.tag, 'CANONICAL_HUB'); @@ -371,7 +363,7 @@ contract V4AddressesProviderTest is Test { provider.setCanonicalHub('CORE', address(0)); vm.expectEmit(address(provider)); - emit IV4AddressesProvider.AddressSet( + emit IAddressesProvider.SetEntry( _id('CORE', 'CANONICAL_HUB'), 'CORE', 'CANONICAL_HUB', @@ -391,7 +383,7 @@ contract V4AddressesProviderTest is Test { vm.expectRevert( abi.encodeWithSelector( - IV4AddressesProvider.AddressAlreadySet.selector, + IAddressesProvider.AddressAlreadySet.selector, _id('CORE', 'CANONICAL_HUB') ) ); @@ -416,7 +408,7 @@ contract V4AddressesProviderTest is Test { function test_setCanonicalHub_remove_revertsWith_AddressNotSet() public { vm.expectRevert( abi.encodeWithSelector( - IV4AddressesProvider.AddressNotSet.selector, + IAddressesProvider.AddressNotSet.selector, _id('CORE', 'CANONICAL_HUB') ) ); @@ -425,7 +417,7 @@ contract V4AddressesProviderTest is Test { } function test_setCanonicalHub_revertsWith_InvalidName() public { - vm.expectRevert(IV4AddressesProvider.InvalidName.selector); + vm.expectRevert(IAddressesProvider.InvalidName.selector); vm.prank(OWNER); provider.setCanonicalHub('', makeAddr('CORE_HUB')); } @@ -461,7 +453,7 @@ contract V4AddressesProviderTest is Test { vm.startPrank(OWNER); vm.expectEmit(address(provider)); - emit IV4AddressesProvider.AddressSet( + emit IAddressesProvider.SetEntry( _id('MAIN', 'CANONICAL_SPOKE'), 'MAIN', 'CANONICAL_SPOKE', @@ -551,7 +543,7 @@ contract V4AddressesProviderTest is Test { vm.expectRevert( abi.encodeWithSelector( - IV4AddressesProvider.AddressAlreadySet.selector, + IAddressesProvider.AddressAlreadySet.selector, _id('MAIN', 'CANONICAL_SPOKE') ) ); @@ -594,7 +586,7 @@ contract V4AddressesProviderTest is Test { function test_setSpoke_remove_revertsWith_AddressNotSet() public { vm.expectRevert( abi.encodeWithSelector( - IV4AddressesProvider.AddressNotSet.selector, + IAddressesProvider.AddressNotSet.selector, _id('MAIN', 'CANONICAL_SPOKE') ) ); @@ -605,13 +597,13 @@ contract V4AddressesProviderTest is Test { function test_setSpoke_revertsWith_InvalidName() public { vm.startPrank(OWNER); - vm.expectRevert(IV4AddressesProvider.InvalidName.selector); + vm.expectRevert(IAddressesProvider.InvalidName.selector); provider.setCanonicalSpoke('', makeAddr('MAIN_SPOKE')); - vm.expectRevert(IV4AddressesProvider.InvalidName.selector); + vm.expectRevert(IAddressesProvider.InvalidName.selector); provider.setTokenizationSpoke('', makeAddr('CORE_WETH_TOKENIZATION_SPOKE')); - vm.expectRevert(IV4AddressesProvider.InvalidName.selector); + vm.expectRevert(IAddressesProvider.InvalidName.selector); provider.setTreasurySpoke('', makeAddr('TREASURY_SPOKE')); vm.stopPrank(); @@ -803,7 +795,7 @@ contract V4AddressesProviderTest is Test { assertEq(firstTwo[0], _id('CORE', 'CANONICAL_HUB')); assertEq(firstTwo[1], _id('MAIN', 'CANONICAL_SPOKE')); - IV4AddressesProvider.AddressEntry[] memory entries = provider.getAddressEntries(shared, 1, 3); + IAddressesProvider.Entry[] memory entries = provider.getEntries(shared, 1, 3); assertEq(entries.length, 2); assertEq(entries[0].tag, 'CANONICAL_SPOKE'); assertEq(entries[1].tag, 'TREASURY_SPOKE'); diff --git a/tests/deployments/procedures/ProceduresBase.t.sol b/tests/deployments/procedures/ProceduresBase.t.sol index fe3be1db3..62c610e91 100644 --- a/tests/deployments/procedures/ProceduresBase.t.sol +++ b/tests/deployments/procedures/ProceduresBase.t.sol @@ -16,7 +16,7 @@ import {AaveV4AccessManagerEnumerableDeployProcedureWrapper} from 'tests/helpers import {AaveV4AaveOracleDeployProcedureWrapper} from 'tests/helpers/mocks/deployments/procedures/AaveV4AaveOracleDeployProcedureWrapper.sol'; import {AaveV4SpokeDeployProcedureWrapper} from 'tests/helpers/mocks/deployments/procedures/AaveV4SpokeDeployProcedureWrapper.sol'; import {AaveV4TreasurySpokeDeployProcedureWrapper} from 'tests/helpers/mocks/deployments/procedures/AaveV4TreasurySpokeDeployProcedureWrapper.sol'; -import {AaveV4AddressesProviderDeployProcedureWrapper} from 'tests/helpers/mocks/deployments/procedures/AaveV4AddressesProviderDeployProcedureWrapper.sol'; +import {AddressesProviderDeployProcedureWrapper} from 'tests/helpers/mocks/deployments/procedures/AddressesProviderDeployProcedureWrapper.sol'; import {AaveV4SpokeConfiguratorDeployProcedureWrapper} from 'tests/helpers/mocks/deployments/procedures/AaveV4SpokeConfiguratorDeployProcedureWrapper.sol'; import {AaveV4AccessManagerRolesProcedureWrapper} from 'tests/helpers/mocks/deployments/procedures/AaveV4AccessManagerRolesProcedureWrapper.sol'; import {AaveV4SpokeRolesProcedureWrapper} from 'tests/helpers/mocks/deployments/procedures/AaveV4SpokeRolesProcedureWrapper.sol'; diff --git a/tests/deployments/procedures/deploy/addresses-provider/AaveV4AddressesProviderDeployProcedure.t.sol b/tests/deployments/procedures/deploy/addresses-provider/AddressesProviderDeployProcedure.t.sol similarity index 64% rename from tests/deployments/procedures/deploy/addresses-provider/AaveV4AddressesProviderDeployProcedure.t.sol rename to tests/deployments/procedures/deploy/addresses-provider/AddressesProviderDeployProcedure.t.sol index e8a95ba71..88c80828a 100644 --- a/tests/deployments/procedures/deploy/addresses-provider/AaveV4AddressesProviderDeployProcedure.t.sol +++ b/tests/deployments/procedures/deploy/addresses-provider/AddressesProviderDeployProcedure.t.sol @@ -3,20 +3,19 @@ pragma solidity ^0.8.0; import 'tests/deployments/procedures/ProceduresBase.t.sol'; -contract AaveV4AddressesProviderDeployProcedureTest is ProceduresBase { - AaveV4AddressesProviderDeployProcedureWrapper - public aaveV4AddressesProviderDeployProcedureWrapper; +contract AddressesProviderDeployProcedureTest is ProceduresBase { + AddressesProviderDeployProcedureWrapper public addressesProviderDeployProcedureWrapper; function setUp() public override { super.setUp(); - aaveV4AddressesProviderDeployProcedureWrapper = new AaveV4AddressesProviderDeployProcedureWrapper(); + addressesProviderDeployProcedureWrapper = new AddressesProviderDeployProcedureWrapper(); } function test_deployAddressesProvider() public { ( address addressesProviderProxy, address addressesProviderImplementation - ) = aaveV4AddressesProviderDeployProcedureWrapper.deployAddressesProvider(owner, salt); + ) = addressesProviderDeployProcedureWrapper.deployAddressesProvider(owner, salt); assertEq(Ownable(addressesProviderProxy).owner(), owner); assertEq(Ownable(ProxyHelper.getProxyAdmin(addressesProviderProxy)).owner(), owner); assertNotEq(addressesProviderImplementation, address(0)); @@ -28,7 +27,7 @@ contract AaveV4AddressesProviderDeployProcedureTest is ProceduresBase { function test_deployAddressesProvider_reverts() public { vm.expectRevert('invalid owner'); - aaveV4AddressesProviderDeployProcedureWrapper.deployAddressesProvider({ + addressesProviderDeployProcedureWrapper.deployAddressesProvider({ owner: address(0), salt: salt }); diff --git a/tests/helpers/mocks/MockV4AddressesProviderInstance.sol b/tests/helpers/mocks/MockAddressesProviderInstance.sol similarity index 77% rename from tests/helpers/mocks/MockV4AddressesProviderInstance.sol rename to tests/helpers/mocks/MockAddressesProviderInstance.sol index e8b3a5e77..cfafc3f38 100644 --- a/tests/helpers/mocks/MockV4AddressesProviderInstance.sol +++ b/tests/helpers/mocks/MockAddressesProviderInstance.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {V4AddressesProvider} from 'src/addresses-provider/V4AddressesProvider.sol'; +import {AddressesProvider} from 'src/addresses-provider/AddressesProvider.sol'; -contract MockV4AddressesProviderInstance is V4AddressesProvider { +contract MockAddressesProviderInstance is AddressesProvider { bool public constant IS_TEST = true; uint64 public immutable ADDRESSES_PROVIDER_REVISION; @@ -18,7 +18,7 @@ contract MockV4AddressesProviderInstance is V4AddressesProvider { _disableInitializers(); } - /// @inheritdoc V4AddressesProvider + /// @inheritdoc AddressesProvider function initialize(address owner) external override reinitializer(ADDRESSES_PROVIDER_REVISION) { __Ownable_init(owner); __Ownable2Step_init(); diff --git a/tests/helpers/mocks/deployments/procedures/AaveV4AddressesProviderDeployProcedureWrapper.sol b/tests/helpers/mocks/deployments/procedures/AddressesProviderDeployProcedureWrapper.sol similarity index 50% rename from tests/helpers/mocks/deployments/procedures/AaveV4AddressesProviderDeployProcedureWrapper.sol rename to tests/helpers/mocks/deployments/procedures/AddressesProviderDeployProcedureWrapper.sol index c1922722f..26891b0a6 100644 --- a/tests/helpers/mocks/deployments/procedures/AaveV4AddressesProviderDeployProcedureWrapper.sol +++ b/tests/helpers/mocks/deployments/procedures/AddressesProviderDeployProcedureWrapper.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {AaveV4AddressesProviderDeployProcedure} from 'src/deployments/procedures/deploy/addresses-provider/AaveV4AddressesProviderDeployProcedure.sol'; +import {AddressesProviderDeployProcedure} from 'src/deployments/procedures/deploy/addresses-provider/AddressesProviderDeployProcedure.sol'; -contract AaveV4AddressesProviderDeployProcedureWrapper is AaveV4AddressesProviderDeployProcedure { +contract AddressesProviderDeployProcedureWrapper is AddressesProviderDeployProcedure { bool public IS_TEST = true; function deployAddressesProvider( From 6a1614a1433f189d40a8505bbedc2dd7891cb63e Mon Sep 17 00:00:00 2001 From: Alexandru Niculae <43644109+avniculae@users.noreply.github.com> Date: Wed, 29 Jul 2026 15:49:45 +0300 Subject: [PATCH 09/15] style: order view before pure functions in AddressesProvider Move getId() after the tag constant getters in the interface, and _getId() after _toAddresses()/_toEntries() in the implementation, so each visibility group runs non-mutating -> view -> pure per the Solidity style guide order of layout. --- src/addresses-provider/AddressesProvider.sol | 8 ++++---- .../interfaces/IAddressesProvider.sol | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/addresses-provider/AddressesProvider.sol b/src/addresses-provider/AddressesProvider.sol index b238bf59b..e5e043f37 100644 --- a/src/addresses-provider/AddressesProvider.sol +++ b/src/addresses-provider/AddressesProvider.sol @@ -254,10 +254,6 @@ abstract contract AddressesProvider is return _idToEntry[_getId({name: name, tag: tag})].addr; } - function _getId(string memory name, string memory tag) internal pure returns (bytes32) { - return keccak256(abi.encode(name, tag)); - } - function _toAddresses(bytes32[] memory ids) internal view returns (address[] memory) { address[] memory addresses = new address[](ids.length); for (uint256 i = 0; i < ids.length; i++) { @@ -273,4 +269,8 @@ abstract contract AddressesProvider is } return entries; } + + function _getId(string memory name, string memory tag) internal pure returns (bytes32) { + return keccak256(abi.encode(name, tag)); + } } diff --git a/src/addresses-provider/interfaces/IAddressesProvider.sol b/src/addresses-provider/interfaces/IAddressesProvider.sol index 718da3cd2..5f6e4e406 100644 --- a/src/addresses-provider/interfaces/IAddressesProvider.sol +++ b/src/addresses-provider/interfaces/IAddressesProvider.sol @@ -251,13 +251,6 @@ interface IAddressesProvider { /// @return The list of treasury Spoke addresses in the slice. function getTreasurySpokes(uint256 start, uint256 end) external view returns (address[] memory); - /// @notice Returns the identifier of the entry associated with a name and tag. - /// @dev The identifier is the hash of the ABI-encoded name and tag. - /// @param name The name of the entry. - /// @param tag The tag grouping the entry. - /// @return The identifier of the entry. - function getId(string calldata name, string calldata tag) external pure returns (bytes32); - /// @notice Returns the tag grouping all canonical Hubs. function CANONICAL_HUB_TAG() external view returns (string memory); @@ -269,4 +262,11 @@ interface IAddressesProvider { /// @notice Returns the tag grouping all treasury Spokes. function TREASURY_SPOKE_TAG() external view returns (string memory); + + /// @notice Returns the identifier of the entry associated with a name and tag. + /// @dev The identifier is the hash of the ABI-encoded name and tag. + /// @param name The name of the entry. + /// @param tag The tag grouping the entry. + /// @return The identifier of the entry. + function getId(string calldata name, string calldata tag) external pure returns (bytes32); } From d7e33401040ebe17d2ecd8ee5f2469651f855aa2 Mon Sep 17 00:00:00 2001 From: Alexandru Niculae <43644109+avniculae@users.noreply.github.com> Date: Tue, 4 Aug 2026 18:16:23 +0300 Subject: [PATCH 10/15] refactor: derive registration guard from asset and reserve counts - _registerHub and _registerSpoke no longer take the listed id; they read getAssetCount()/getReserveCount() directly, so the guard stays beside the action it protects and the helpers take only the listing - reorder the Entry struct to name, tag, addr, matching the (name, tag) ordering the rest of the interface uses - describe IAddressesProvider as an interface, in line with the other interface natspec --- .../interfaces/IAddressesProvider.sol | 6 +++--- src/config-engine/libraries/HubEngine.sol | 11 ++++------- src/config-engine/libraries/SpokeEngine.sol | 15 ++++++--------- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/addresses-provider/interfaces/IAddressesProvider.sol b/src/addresses-provider/interfaces/IAddressesProvider.sol index 5f6e4e406..441a49cfa 100644 --- a/src/addresses-provider/interfaces/IAddressesProvider.sol +++ b/src/addresses-provider/interfaces/IAddressesProvider.sol @@ -3,16 +3,16 @@ pragma solidity ^0.8.0; /// @title IAddressesProvider /// @author Aave Labs -/// @notice Main registry of the Hub and Spoke addresses of an Aave V4 instance. +/// @notice Interface for the AddressesProvider. interface IAddressesProvider { /// @notice Entry registered under an identifier. - /// @param addr The registered address. /// @param name The name of the entry. /// @param tag The tag grouping the entry. + /// @param addr The registered address. struct Entry { - address addr; string name; string tag; + address addr; } /// @notice Emitted when the address of an entry is updated. diff --git a/src/config-engine/libraries/HubEngine.sol b/src/config-engine/libraries/HubEngine.sol index f8262cba7..d6c1f8285 100644 --- a/src/config-engine/libraries/HubEngine.sol +++ b/src/config-engine/libraries/HubEngine.sol @@ -48,7 +48,7 @@ library HubEngine { irData ); - _registerHub(listings[i], assetId); + _registerHub(listings[i]); _deployAndRegisterTokenizationSpoke(listings[i], assetId); } } @@ -229,12 +229,9 @@ library HubEngine { } /// @dev Registers the Hub on the AddressesProvider when requested. - /// @dev Only allowed when the listed asset is the Hub's first (asset id 0), to avoid registering an + /// @dev Only allowed when the listed asset is the Hub's only asset, to avoid registering an /// already-configured Hub; reverts otherwise. - function _registerHub( - IAaveV4ConfigEngine.AssetListing calldata listing, - uint256 assetId - ) private { + function _registerHub(IAaveV4ConfigEngine.AssetListing calldata listing) private { require( EngineUtils.isConsistentRegistration(listing.hubRegistration), InvalidAddressesProviderRegistration() @@ -242,7 +239,7 @@ library HubEngine { if (!listing.hubRegistration.register) { return; } - require(assetId == 0, InvalidAddressesProviderRegistration()); + require(IHub(listing.hub).getAssetCount() == 1, InvalidAddressesProviderRegistration()); listing.hubRegistration.addressesProvider.setCanonicalHub( listing.hubRegistration.name, listing.hub diff --git a/src/config-engine/libraries/SpokeEngine.sol b/src/config-engine/libraries/SpokeEngine.sol index 92dd10ab7..7680b1629 100644 --- a/src/config-engine/libraries/SpokeEngine.sol +++ b/src/config-engine/libraries/SpokeEngine.sol @@ -28,7 +28,7 @@ library SpokeEngine { uint256 length = listings.length; for (uint256 i; i < length; ++i) { uint256 assetId = IHubBase(listings[i].hub).getAssetId(listings[i].underlying); - uint256 reserveId = listings[i].spokeConfigurator.addReserve( + listings[i].spokeConfigurator.addReserve( listings[i].spoke, listings[i].hub, assetId, @@ -37,7 +37,7 @@ library SpokeEngine { listings[i].dynamicConfig ); - _registerSpoke(listings[i], reserveId); + _registerSpoke(listings[i]); } } @@ -236,12 +236,9 @@ library SpokeEngine { } /// @dev Registers the Spoke on the AddressesProvider when requested. - /// @dev Only allowed when the listed reserve is the Spoke's first (reserve id 0), to avoid - /// registering an already-configured Spoke; reverts otherwise. - function _registerSpoke( - IAaveV4ConfigEngine.ReserveListing calldata listing, - uint256 reserveId - ) private { + /// @dev Only allowed when the listed reserve is the Spoke's only reserve, to avoid registering an + /// already-configured Spoke; reverts otherwise. + function _registerSpoke(IAaveV4ConfigEngine.ReserveListing calldata listing) private { require( EngineUtils.isConsistentRegistration(listing.spokeRegistration), InvalidAddressesProviderRegistration() @@ -249,7 +246,7 @@ library SpokeEngine { if (!listing.spokeRegistration.register) { return; } - require(reserveId == 0, InvalidAddressesProviderRegistration()); + require(ISpoke(listing.spoke).getReserveCount() == 1, InvalidAddressesProviderRegistration()); listing.spokeRegistration.addressesProvider.setCanonicalSpoke( listing.spokeRegistration.name, listing.spoke From 5650778bec3275218d88a79ade021bd99bd55510 Mon Sep 17 00:00:00 2001 From: Alexandru Niculae <43644109+avniculae@users.noreply.github.com> Date: Thu, 6 Aug 2026 17:58:37 +0300 Subject: [PATCH 11/15] feat: require AddressesProvider registration for config engine actions - Hub and Spoke actions revert unless the target is registered on the AddressesProvider: Hubs as canonical Hubs, SpokeEngine targets as canonical Spokes, and Spokes referenced by hub-side actions under any spoke tag - add executeAddressesProviderEntryUpdates as a dedicated engine action to register or unregister entries, executed before Hub and Spoke actions in AaveV4Payload - bind the AddressesProvider to the engine as a constructor immutable - drop the optional per-listing registration structs and the first-asset/first-reserve guards; TokenizationSpoke deployment always registers the proxy under a required registrationName - add IAddressesProvider.isRegistered(addr, tag) --- src/addresses-provider/AddressesProvider.sol | 12 + .../interfaces/IAddressesProvider.sol | 6 + src/config-engine/AaveV4ConfigEngine.sol | 53 +- src/config-engine/AaveV4Payload.sol | 25 + .../interfaces/IAaveV4ConfigEngine.sol | 44 +- .../libraries/AddressesProviderEngine.sol | 23 + src/config-engine/libraries/EngineUtils.sol | 57 +- src/config-engine/libraries/HubEngine.sol | 145 +++-- src/config-engine/libraries/SpokeEngine.sol | 64 +- .../AaveV4Payload.EmptyReturns.t.sol | 4 + tests/config-engine/AaveV4Payload.t.sol | 40 +- .../AddressesProviderRegistration.t.sol | 603 ++++++++++++------ tests/config-engine/BaseConfigEngine.t.sol | 76 ++- tests/config-engine/EngineUtils.t.sol | 139 ++-- tests/config-engine/GovernanceTopology.t.sol | 6 + tests/config-engine/HubEngine.t.sol | 28 +- tests/config-engine/SpokeEngine.t.sol | 19 +- .../AddressesProvider.t.sol | 37 ++ .../config-engine/AaveV4PayloadWrapper.sol | 22 + .../MockTokenizationListingPayload.sol | 14 +- 20 files changed, 992 insertions(+), 425 deletions(-) create mode 100644 src/config-engine/libraries/AddressesProviderEngine.sol diff --git a/src/addresses-provider/AddressesProvider.sol b/src/addresses-provider/AddressesProvider.sol index e5e043f37..0f4e4d8d0 100644 --- a/src/addresses-provider/AddressesProvider.sol +++ b/src/addresses-provider/AddressesProvider.sol @@ -156,6 +156,18 @@ abstract contract AddressesProvider is return _toEntries(_addressToIdSet[addr].values(start, end)); } + /// @inheritdoc IAddressesProvider + function isRegistered(address addr, string calldata tag) external view returns (bool) { + bytes32 tagHash = keccak256(bytes(tag)); + bytes32[] memory ids = _addressToIdSet[addr].values(); + for (uint256 i = 0; i < ids.length; i++) { + if (keccak256(bytes(_idToEntry[ids[i]].tag)) == tagHash) { + return true; + } + } + return false; + } + /// @inheritdoc IAddressesProvider function getCanonicalHub(string calldata name) external view returns (address) { return _getAddress({name: name, tag: CANONICAL_HUB_TAG}); diff --git a/src/addresses-provider/interfaces/IAddressesProvider.sol b/src/addresses-provider/interfaces/IAddressesProvider.sol index 441a49cfa..10a64b729 100644 --- a/src/addresses-provider/interfaces/IAddressesProvider.sol +++ b/src/addresses-provider/interfaces/IAddressesProvider.sol @@ -184,6 +184,12 @@ interface IAddressesProvider { uint256 end ) external view returns (Entry[] memory); + /// @notice Returns whether an address is registered under a tag. + /// @param addr The registered address. + /// @param tag The tag grouping the entries. + /// @return True if at least one entry associates the address with the tag. + function isRegistered(address addr, string calldata tag) external view returns (bool); + /// @notice Returns the canonical Hub associated with a name. /// @param name The name of the Hub. /// @return The address of the Hub, the zero address if none is registered. diff --git a/src/config-engine/AaveV4ConfigEngine.sol b/src/config-engine/AaveV4ConfigEngine.sol index 1c8597920..50a860c42 100644 --- a/src/config-engine/AaveV4ConfigEngine.sol +++ b/src/config-engine/AaveV4ConfigEngine.sol @@ -5,92 +5,115 @@ import {HubEngine} from 'src/config-engine/libraries/HubEngine.sol'; import {SpokeEngine} from 'src/config-engine/libraries/SpokeEngine.sol'; import {AccessManagerEngine} from 'src/config-engine/libraries/AccessManagerEngine.sol'; import {PositionManagerEngine} from 'src/config-engine/libraries/PositionManagerEngine.sol'; +import {AddressesProviderEngine} from 'src/config-engine/libraries/AddressesProviderEngine.sol'; import {IAaveV4ConfigEngine} from 'src/config-engine/interfaces/IAaveV4ConfigEngine.sol'; +import {IAddressesProvider} from 'src/addresses-provider/interfaces/IAddressesProvider.sol'; /// @title AaveV4ConfigEngine /// @author Aave Labs /// @notice Implementation of IAaveV4ConfigEngine. Delegates to external library contracts for /// each action category. Invoked via delegatecall from payload contracts. +/// @dev Hub and Spoke actions revert when the targeted Hub or Spoke is not registered on the +/// AddressesProvider; entries are managed via `executeAddressesProviderEntryUpdates`. contract AaveV4ConfigEngine is IAaveV4ConfigEngine { + /// @inheritdoc IAaveV4ConfigEngine + IAddressesProvider public immutable ADDRESSES_PROVIDER; + + /// @dev Thrown when the addresses provider address is zero. + error InvalidAddressesProvider(); + + /// @param addressesProvider_ The AddressesProvider authorizing and registering engine actions. + constructor(IAddressesProvider addressesProvider_) { + require(address(addressesProvider_) != address(0), InvalidAddressesProvider()); + ADDRESSES_PROVIDER = addressesProvider_; + } + + /// @inheritdoc IAaveV4ConfigEngine + function executeAddressesProviderEntryUpdates( + AddressesProviderEntryUpdate[] calldata updates + ) external { + AddressesProviderEngine.executeAddressesProviderEntryUpdates(updates, ADDRESSES_PROVIDER); + } + /// @inheritdoc IAaveV4ConfigEngine function executeHubAssetListings(AssetListing[] calldata listings) external { - HubEngine.executeHubAssetListings(listings); + HubEngine.executeHubAssetListings(listings, ADDRESSES_PROVIDER); } /// @inheritdoc IAaveV4ConfigEngine function executeHubAssetConfigUpdates(AssetConfigUpdate[] calldata updates) external { - HubEngine.executeHubAssetConfigUpdates(updates); + HubEngine.executeHubAssetConfigUpdates(updates, ADDRESSES_PROVIDER); } /// @inheritdoc IAaveV4ConfigEngine function executeHubSpokeToAssetsAdditions(SpokeToAssetsAddition[] calldata additions) external { - HubEngine.executeHubSpokeToAssetsAdditions(additions); + HubEngine.executeHubSpokeToAssetsAdditions(additions, ADDRESSES_PROVIDER); } /// @inheritdoc IAaveV4ConfigEngine function executeHubSpokeConfigUpdates(SpokeConfigUpdate[] calldata updates) external { - HubEngine.executeHubSpokeConfigUpdates(updates); + HubEngine.executeHubSpokeConfigUpdates(updates, ADDRESSES_PROVIDER); } /// @inheritdoc IAaveV4ConfigEngine function executeHubAssetHalts(AssetHalt[] calldata halts) external { - HubEngine.executeHubAssetHalts(halts); + HubEngine.executeHubAssetHalts(halts, ADDRESSES_PROVIDER); } /// @inheritdoc IAaveV4ConfigEngine function executeHubAssetDeactivations(AssetDeactivation[] calldata deactivations) external { - HubEngine.executeHubAssetDeactivations(deactivations); + HubEngine.executeHubAssetDeactivations(deactivations, ADDRESSES_PROVIDER); } /// @inheritdoc IAaveV4ConfigEngine function executeHubAssetCapsResets(AssetCapsReset[] calldata resets) external { - HubEngine.executeHubAssetCapsResets(resets); + HubEngine.executeHubAssetCapsResets(resets, ADDRESSES_PROVIDER); } /// @inheritdoc IAaveV4ConfigEngine function executeHubSpokeDeactivations(SpokeDeactivation[] calldata deactivations) external { - HubEngine.executeHubSpokeDeactivations(deactivations); + HubEngine.executeHubSpokeDeactivations(deactivations, ADDRESSES_PROVIDER); } /// @inheritdoc IAaveV4ConfigEngine function executeHubSpokeCapsResets(SpokeCapsReset[] calldata resets) external { - HubEngine.executeHubSpokeCapsResets(resets); + HubEngine.executeHubSpokeCapsResets(resets, ADDRESSES_PROVIDER); } /// @inheritdoc IAaveV4ConfigEngine function executeSpokeReserveListings(ReserveListing[] calldata listings) external { - SpokeEngine.executeSpokeReserveListings(listings); + SpokeEngine.executeSpokeReserveListings(listings, ADDRESSES_PROVIDER); } /// @inheritdoc IAaveV4ConfigEngine function executeSpokeReserveConfigUpdates(ReserveConfigUpdate[] calldata updates) external { - SpokeEngine.executeSpokeReserveConfigUpdates(updates); + SpokeEngine.executeSpokeReserveConfigUpdates(updates, ADDRESSES_PROVIDER); } /// @inheritdoc IAaveV4ConfigEngine function executeSpokeLiquidationConfigUpdates( LiquidationConfigUpdate[] calldata updates ) external { - SpokeEngine.executeSpokeLiquidationConfigUpdates(updates); + SpokeEngine.executeSpokeLiquidationConfigUpdates(updates, ADDRESSES_PROVIDER); } /// @inheritdoc IAaveV4ConfigEngine function executeSpokeDynamicReserveConfigAdditions( DynamicReserveConfigAddition[] calldata additions ) external { - SpokeEngine.executeSpokeDynamicReserveConfigAdditions(additions); + SpokeEngine.executeSpokeDynamicReserveConfigAdditions(additions, ADDRESSES_PROVIDER); } /// @inheritdoc IAaveV4ConfigEngine function executeSpokeDynamicReserveConfigUpdates( DynamicReserveConfigUpdate[] calldata updates ) external { - SpokeEngine.executeSpokeDynamicReserveConfigUpdates(updates); + SpokeEngine.executeSpokeDynamicReserveConfigUpdates(updates, ADDRESSES_PROVIDER); } /// @inheritdoc IAaveV4ConfigEngine function executeSpokePositionManagerUpdates(PositionManagerUpdate[] calldata updates) external { - SpokeEngine.executeSpokePositionManagerUpdates(updates); + SpokeEngine.executeSpokePositionManagerUpdates(updates, ADDRESSES_PROVIDER); } /// @inheritdoc IAaveV4ConfigEngine diff --git a/src/config-engine/AaveV4Payload.sol b/src/config-engine/AaveV4Payload.sol index f54b249af..fd00ca055 100644 --- a/src/config-engine/AaveV4Payload.sol +++ b/src/config-engine/AaveV4Payload.sol @@ -28,12 +28,26 @@ abstract contract AaveV4Payload { function execute() external { _preExecute(); _executeAccessManagerActions(); + _executeAddressesProviderActions(); _executeHubActions(); _executeSpokeActions(); _executePositionManagerActions(); _postExecute(); } + /// @notice Returns the AddressesProvider entry updates to execute. Override to provide updates. + /// @dev Executed before Hub and Spoke actions, so a Hub or Spoke can be registered and targeted + /// within the same payload. + /// @return An array of AddressesProviderEntryUpdate structs (empty by default). + function addressesProviderEntryUpdates() + public + view + virtual + returns (IAaveV4ConfigEngine.AddressesProviderEntryUpdate[] memory) + { + return new IAaveV4ConfigEngine.AddressesProviderEntryUpdate[](0); + } + /// @notice Returns the Hub asset listings to execute. Override to provide listings. /// @return An array of AssetListing structs (empty by default). function hubAssetListings() @@ -260,6 +274,17 @@ abstract contract AaveV4Payload { return new IAaveV4ConfigEngine.PositionManagerRoleRenouncement[](0); } + /// @notice Executes all AddressesProvider entry updates via delegatecall to the engine. + function _executeAddressesProviderActions() internal { + IAaveV4ConfigEngine.AddressesProviderEntryUpdate[] + memory updates = addressesProviderEntryUpdates(); + if (updates.length > 0) { + _delegateCallEngine( + abi.encodeCall(IAaveV4ConfigEngine.executeAddressesProviderEntryUpdates, (updates)) + ); + } + } + /// @notice Executes all hub-related configuration actions via delegatecall to the engine. function _executeHubActions() internal { IAaveV4ConfigEngine.AssetListing[] memory listings = hubAssetListings(); diff --git a/src/config-engine/interfaces/IAaveV4ConfigEngine.sol b/src/config-engine/interfaces/IAaveV4ConfigEngine.sol index 66eaa7229..cca22c67b 100644 --- a/src/config-engine/interfaces/IAaveV4ConfigEngine.sol +++ b/src/config-engine/interfaces/IAaveV4ConfigEngine.sol @@ -16,32 +16,33 @@ import {IAddressesProvider} from 'src/addresses-provider/interfaces/IAddressesPr /// the universal KEEP_CURRENT sentinel. Boolean fields use uint256 (0=false, 1=true, KEEP_CURRENT=skip). interface IAaveV4ConfigEngine { /// @notice Parameters for tokenization of an asset on a Hub when listing the asset. - /// @dev Tokenization is skipped only when all fields are unset. Otherwise `name`, `symbol` and - /// `proxyAdminOwner` are all required; a partially set config reverts. + /// @dev Tokenization is skipped only when all fields are unset. Otherwise `name`, `symbol`, + /// `proxyAdminOwner` and `registrationName` are all required; a partially set config reverts. /// @dev addCap The add cap for the TokenizationSpoke. /// @dev proxyAdminOwner The owner to set on the ProxyAdmin of the deployed TokenizationSpoke (address(0) when unset). /// @dev name The name for the TokenizationSpoke ('' when unset). /// @dev symbol The symbol for the TokenizationSpoke ('' when unset). + /// @dev registrationName The name to register the deployed TokenizationSpoke under on the AddressesProvider ('' when unset). struct TokenizationSpokeConfig { uint256 addCap; address proxyAdminOwner; string name; string symbol; + string registrationName; } - /// @notice Optional registration of a listed Hub or Spoke on the AddressesProvider. - /// @dev Left unset (the default), `register` is false and the registration is skipped. - /// @dev All fields must be set when `register` is true, and left unset when false; reverts otherwise. - /// @dev addressesProvider The AddressesProvider to register the entry on. - /// @dev register Whether to register the entry on the addressesProvider. - /// @dev name The name to register the entry under. - struct AddressesProviderRegistration { - IAddressesProvider addressesProvider; - bool register; + /// @notice Parameters for updating an entry on the AddressesProvider. + /// @dev name The name of the entry. + /// @dev tag The tag grouping the entry. + /// @dev addr The address to register; the zero address removes the entry. + struct AddressesProviderEntryUpdate { string name; + string tag; + address addr; } /// @notice Parameters for listing a new asset on a Hub. + /// @dev The Hub must be registered on the AddressesProvider as a canonical Hub. /// @dev hubConfigurator The HubConfigurator to use for this action. /// @dev hub The address of the Hub. /// @dev underlying The address of the underlying asset. @@ -50,10 +51,6 @@ interface IAaveV4ConfigEngine { /// @dev irStrategy The address of the interest rate strategy contract. /// @dev irData The interest rate data to apply to the given asset. /// @dev tokenization The tokenization configuration for the asset. - /// @dev hubRegistration Optional registration of the Hub on the AddressesProvider; only allowed - /// when the listed asset is the Hub's first (asset id 0), reverts otherwise. - /// @dev tokenizationSpokeRegistration Optional registration of the deployed TokenizationSpoke on the - /// AddressesProvider; only allowed when a TokenizationSpoke is deployed for this listing. struct AssetListing { IHubConfigurator hubConfigurator; address hub; @@ -63,8 +60,6 @@ interface IAaveV4ConfigEngine { address irStrategy; IAssetInterestRateStrategy.InterestRateData irData; TokenizationSpokeConfig tokenization; - AddressesProviderRegistration hubRegistration; - AddressesProviderRegistration tokenizationSpokeRegistration; } /// @notice Parameters for updating asset config (fee, interest rate, reinvestment) on a Hub. @@ -182,6 +177,7 @@ interface IAaveV4ConfigEngine { } /// @notice Parameters for listing a new reserve on a Spoke. + /// @dev The Spoke must be registered on the AddressesProvider as a canonical Spoke. /// @dev spokeConfigurator The SpokeConfigurator to use for this action. /// @dev spoke The address of the Spoke. /// @dev hub The address of the Hub. @@ -189,8 +185,6 @@ interface IAaveV4ConfigEngine { /// @dev priceSource The address of the price source. /// @dev config The configuration of the reserve. /// @dev dynamicConfig The dynamic configuration of the reserve. - /// @dev spokeRegistration Optional registration of the Spoke on the AddressesProvider; only allowed - /// when the listed reserve is the Spoke's first (reserve id 0), reverts otherwise. struct ReserveListing { ISpokeConfigurator spokeConfigurator; address spoke; @@ -199,7 +193,6 @@ interface IAaveV4ConfigEngine { address priceSource; ISpoke.ReserveConfig config; ISpoke.DynamicReserveConfig dynamicConfig; - AddressesProviderRegistration spokeRegistration; } /// @notice Parameters for updating reserve config on a Spoke. @@ -364,6 +357,14 @@ interface IAaveV4ConfigEngine { uint32 newDelay; } + /// @notice Updates entries on the AddressesProvider. + /// @dev Hubs and Spokes must be registered on the AddressesProvider before other engine actions + /// can target them; this is the action to register (or unregister) them. + /// @param updates The entry updates to execute. + function executeAddressesProviderEntryUpdates( + AddressesProviderEntryUpdate[] calldata updates + ) external; + /// @notice Lists new assets on Hubs via the HubConfigurator. /// @param listings The asset listings to execute. function executeHubAssetListings(AssetListing[] calldata listings) external; @@ -460,4 +461,7 @@ interface IAaveV4ConfigEngine { /// @notice Updates target admin delays via AccessManager. /// @param updates The target admin delay updates to execute. function executeTargetAdminDelayUpdates(TargetAdminDelayUpdate[] calldata updates) external; + + /// @notice Returns the AddressesProvider used to authorize and register engine actions. + function ADDRESSES_PROVIDER() external view returns (IAddressesProvider); } diff --git a/src/config-engine/libraries/AddressesProviderEngine.sol b/src/config-engine/libraries/AddressesProviderEngine.sol new file mode 100644 index 000000000..5207a1f2c --- /dev/null +++ b/src/config-engine/libraries/AddressesProviderEngine.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: LicenseRef-BUSL +pragma solidity ^0.8.0; + +import {IAddressesProvider} from 'src/addresses-provider/interfaces/IAddressesProvider.sol'; +import {IAaveV4ConfigEngine} from 'src/config-engine/interfaces/IAaveV4ConfigEngine.sol'; + +/// @title AddressesProviderEngine +/// @author Aave Labs +/// @notice Library containing AddressesProvider logic for AaveV4ConfigEngine. +library AddressesProviderEngine { + /// @notice Updates entries on the AddressesProvider. + /// @param updates The entry updates to execute. + /// @param addressesProvider The AddressesProvider to update. + function executeAddressesProviderEntryUpdates( + IAaveV4ConfigEngine.AddressesProviderEntryUpdate[] calldata updates, + IAddressesProvider addressesProvider + ) external { + uint256 length = updates.length; + for (uint256 i; i < length; ++i) { + addressesProvider.setEntry(updates[i].name, updates[i].tag, updates[i].addr); + } + } +} diff --git a/src/config-engine/libraries/EngineUtils.sol b/src/config-engine/libraries/EngineUtils.sol index abc3ad6d8..4d19ffb6c 100644 --- a/src/config-engine/libraries/EngineUtils.sol +++ b/src/config-engine/libraries/EngineUtils.sol @@ -1,22 +1,55 @@ // SPDX-License-Identifier: LicenseRef-BUSL pragma solidity ^0.8.0; -import {IAaveV4ConfigEngine} from 'src/config-engine/interfaces/IAaveV4ConfigEngine.sol'; +import {IAddressesProvider} from 'src/addresses-provider/interfaces/IAddressesProvider.sol'; /// @title EngineUtils /// @author Aave Labs /// @notice Library containing shared helpers for the AaveV4ConfigEngine libraries. library EngineUtils { - /// @dev Returns whether an optional AddressesProvider registration is consistent: all fields must - /// be set when registering, and left unset otherwise. - function isConsistentRegistration( - IAaveV4ConfigEngine.AddressesProviderRegistration calldata registration - ) internal pure returns (bool) { - return - registration.register - ? address(registration.addressesProvider) != address(0) && - bytes(registration.name).length > 0 - : address(registration.addressesProvider) == address(0) && - bytes(registration.name).length == 0; + /// @dev Thrown when a Hub targeted by an engine action is not registered on the + /// AddressesProvider as a canonical Hub. + error HubNotRegistered(address hub); + + /// @dev Thrown when a Spoke targeted by an engine action is not registered on the + /// AddressesProvider under a spoke tag. + error SpokeNotRegistered(address spoke); + + /// @dev Thrown when a Spoke targeted by an engine action that only supports canonical Spokes is + /// not registered on the AddressesProvider as a canonical Spoke. + error CanonicalSpokeNotRegistered(address spoke); + + /// @dev Reverts unless the Hub is registered on the AddressesProvider as a canonical Hub. + function requireRegisteredHub(IAddressesProvider addressesProvider, address hub) internal view { + require( + addressesProvider.isRegistered(hub, addressesProvider.CANONICAL_HUB_TAG()), + HubNotRegistered(hub) + ); + } + + /// @dev Reverts unless the Spoke is registered on the AddressesProvider under a spoke tag + /// (canonical, tokenization or treasury). For flows supporting any spoke participant. + function requireRegisteredSpoke( + IAddressesProvider addressesProvider, + address spoke + ) internal view { + require( + addressesProvider.isRegistered(spoke, addressesProvider.CANONICAL_SPOKE_TAG()) || + addressesProvider.isRegistered(spoke, addressesProvider.TOKENIZATION_SPOKE_TAG()) || + addressesProvider.isRegistered(spoke, addressesProvider.TREASURY_SPOKE_TAG()), + SpokeNotRegistered(spoke) + ); + } + + /// @dev Reverts unless the Spoke is registered on the AddressesProvider as a canonical Spoke. + /// For flows that only canonical Spokes support (reserves, liquidations, position managers). + function requireRegisteredCanonicalSpoke( + IAddressesProvider addressesProvider, + address spoke + ) internal view { + require( + addressesProvider.isRegistered(spoke, addressesProvider.CANONICAL_SPOKE_TAG()), + CanonicalSpokeNotRegistered(spoke) + ); } } diff --git a/src/config-engine/libraries/HubEngine.sol b/src/config-engine/libraries/HubEngine.sol index d6c1f8285..69bd965d5 100644 --- a/src/config-engine/libraries/HubEngine.sol +++ b/src/config-engine/libraries/HubEngine.sol @@ -9,6 +9,7 @@ import {IHubBase} from 'src/hub/interfaces/IHubBase.sol'; import {IHub} from 'src/hub/interfaces/IHub.sol'; import {IAssetInterestRateStrategy} from 'src/hub/interfaces/IAssetInterestRateStrategy.sol'; import {IAaveV4ConfigEngine} from 'src/config-engine/interfaces/IAaveV4ConfigEngine.sol'; +import {IAddressesProvider} from 'src/addresses-provider/interfaces/IAddressesProvider.sol'; /// @title HubEngine /// @author Aave Labs @@ -20,24 +21,25 @@ library HubEngine { /// KEEP_CURRENT sentinel. All fields must be explicitly set when the strategy changes. error InvalidIrDataWithNewStrategy(); - /// @dev Thrown when an addresses provider registration is requested for a listing that does not - /// support it: registering a Hub when the listed asset is not the Hub's first (asset id != 0), - /// registering a TokenizationSpoke that was not deployed, or the registration fields are - /// inconsistent with the `register` flag. - error InvalidAddressesProviderRegistration(); - /// @dev Thrown when a tokenization config is partially set. Either all fields are unset (no - /// TokenizationSpoke) or `name`, `symbol` and `proxyAdminOwner` must all be provided. + /// TokenizationSpoke) or `name`, `symbol`, `proxyAdminOwner` and `registrationName` must all be + /// provided. error InvalidTokenizationSpokeConfig(); /// @notice Lists new assets on Hubs via the HubConfigurator. + /// @dev The Hub must be registered on the AddressesProvider as a canonical Hub. /// @dev When tokenization data is set, also deploys a TokenizationSpoke (impl + proxy) via - /// CREATE2 and registers it on the Hub for the listed asset. - /// @dev Optionally registers the Hub and/or the deployed TokenizationSpoke on the AddressesProvider. + /// CREATE2 and registers it on the AddressesProvider and on the Hub for the listed asset. /// @param listings The asset listings to execute. - function executeHubAssetListings(IAaveV4ConfigEngine.AssetListing[] calldata listings) external { + /// @param addressesProvider The AddressesProvider authorizing and registering the actions. + function executeHubAssetListings( + IAaveV4ConfigEngine.AssetListing[] calldata listings, + IAddressesProvider addressesProvider + ) external { uint256 length = listings.length; for (uint256 i; i < length; ++i) { + EngineUtils.requireRegisteredHub(addressesProvider, listings[i].hub); + bytes memory irData = abi.encode(listings[i].irData); uint256 assetId = listings[i].hubConfigurator.addAsset( listings[i].hub, @@ -48,8 +50,7 @@ library HubEngine { irData ); - _registerHub(listings[i]); - _deployAndRegisterTokenizationSpoke(listings[i], assetId); + _deployAndRegisterTokenizationSpoke(listings[i], assetId, addressesProvider); } } @@ -60,11 +61,15 @@ library HubEngine { /// read-modify-write via updateInterestRateData. /// Reinvestment: address set → updateReinvestmentController. /// @param updates The asset config updates to execute. + /// @param addressesProvider The AddressesProvider authorizing the actions. function executeHubAssetConfigUpdates( - IAaveV4ConfigEngine.AssetConfigUpdate[] calldata updates + IAaveV4ConfigEngine.AssetConfigUpdate[] calldata updates, + IAddressesProvider addressesProvider ) external { uint256 length = updates.length; for (uint256 i; i < length; ++i) { + EngineUtils.requireRegisteredHub(addressesProvider, updates[i].hub); + uint256 assetId = IHubBase(updates[i].hub).getAssetId(updates[i].underlying); bool updateFee = updates[i].liquidityFee != EngineFlags.KEEP_CURRENT; @@ -104,12 +109,18 @@ library HubEngine { } /// @notice Registers Spokes for multiple assets on Hubs. + /// @dev The Hub and the added Spoke must both be registered on the AddressesProvider. /// @param additions The Spoke-to-assets additions to execute. + /// @param addressesProvider The AddressesProvider authorizing the actions. function executeHubSpokeToAssetsAdditions( - IAaveV4ConfigEngine.SpokeToAssetsAddition[] calldata additions + IAaveV4ConfigEngine.SpokeToAssetsAddition[] calldata additions, + IAddressesProvider addressesProvider ) external { uint256 length = additions.length; for (uint256 i; i < length; ++i) { + EngineUtils.requireRegisteredHub(addressesProvider, additions[i].hub); + EngineUtils.requireRegisteredSpoke(addressesProvider, additions[i].spoke); + uint256 assetsLength = additions[i].assets.length; uint256[] memory assetIds = new uint256[](assetsLength); IHub.SpokeConfig[] memory configs = new IHub.SpokeConfig[](assetsLength); @@ -131,12 +142,18 @@ library HubEngine { /// Caps: both set → updateSpokeCaps; only add → updateSpokeAddCap; only draw → updateSpokeDrawCap. /// Risk premium threshold: set → updateSpokeRiskPremiumThreshold. /// Status: active set → updateSpokeActive; halted set → updateSpokeHalted. + /// @dev The Hub and the Spoke must both be registered on the AddressesProvider. /// @param updates The Spoke config updates to execute. + /// @param addressesProvider The AddressesProvider authorizing the actions. function executeHubSpokeConfigUpdates( - IAaveV4ConfigEngine.SpokeConfigUpdate[] calldata updates + IAaveV4ConfigEngine.SpokeConfigUpdate[] calldata updates, + IAddressesProvider addressesProvider ) external { uint256 length = updates.length; for (uint256 i; i < length; ++i) { + EngineUtils.requireRegisteredHub(addressesProvider, updates[i].hub); + EngineUtils.requireRegisteredSpoke(addressesProvider, updates[i].spoke); + uint256 assetId = IHubBase(updates[i].hub).getAssetId(updates[i].underlying); _updateSpokeCaps(assetId, updates[i]); @@ -171,9 +188,15 @@ library HubEngine { /// @notice Halts assets on Hubs. /// @param halts The asset halts to execute. - function executeHubAssetHalts(IAaveV4ConfigEngine.AssetHalt[] calldata halts) external { + /// @param addressesProvider The AddressesProvider authorizing the actions. + function executeHubAssetHalts( + IAaveV4ConfigEngine.AssetHalt[] calldata halts, + IAddressesProvider addressesProvider + ) external { uint256 length = halts.length; for (uint256 i; i < length; ++i) { + EngineUtils.requireRegisteredHub(addressesProvider, halts[i].hub); + uint256 assetId = IHubBase(halts[i].hub).getAssetId(halts[i].underlying); halts[i].hubConfigurator.haltAsset(halts[i].hub, assetId); } @@ -181,11 +204,15 @@ library HubEngine { /// @notice Deactivates assets on Hubs. /// @param deactivations The asset deactivations to execute. + /// @param addressesProvider The AddressesProvider authorizing the actions. function executeHubAssetDeactivations( - IAaveV4ConfigEngine.AssetDeactivation[] calldata deactivations + IAaveV4ConfigEngine.AssetDeactivation[] calldata deactivations, + IAddressesProvider addressesProvider ) external { uint256 length = deactivations.length; for (uint256 i; i < length; ++i) { + EngineUtils.requireRegisteredHub(addressesProvider, deactivations[i].hub); + uint256 assetId = IHubBase(deactivations[i].hub).getAssetId(deactivations[i].underlying); deactivations[i].hubConfigurator.deactivateAsset(deactivations[i].hub, assetId); } @@ -193,23 +220,33 @@ library HubEngine { /// @notice Resets asset caps on Hubs. /// @param resets The asset caps resets to execute. + /// @param addressesProvider The AddressesProvider authorizing the actions. function executeHubAssetCapsResets( - IAaveV4ConfigEngine.AssetCapsReset[] calldata resets + IAaveV4ConfigEngine.AssetCapsReset[] calldata resets, + IAddressesProvider addressesProvider ) external { uint256 length = resets.length; for (uint256 i; i < length; ++i) { + EngineUtils.requireRegisteredHub(addressesProvider, resets[i].hub); + uint256 assetId = IHubBase(resets[i].hub).getAssetId(resets[i].underlying); resets[i].hubConfigurator.resetAssetCaps(resets[i].hub, assetId); } } /// @notice Deactivates Spokes on Hubs. + /// @dev The Hub and the Spoke must both be registered on the AddressesProvider. /// @param deactivations The Spoke deactivations to execute. + /// @param addressesProvider The AddressesProvider authorizing the actions. function executeHubSpokeDeactivations( - IAaveV4ConfigEngine.SpokeDeactivation[] calldata deactivations + IAaveV4ConfigEngine.SpokeDeactivation[] calldata deactivations, + IAddressesProvider addressesProvider ) external { uint256 length = deactivations.length; for (uint256 i; i < length; ++i) { + EngineUtils.requireRegisteredHub(addressesProvider, deactivations[i].hub); + EngineUtils.requireRegisteredSpoke(addressesProvider, deactivations[i].spoke); + deactivations[i].hubConfigurator.deactivateSpoke( deactivations[i].hub, deactivations[i].spoke @@ -218,61 +255,50 @@ library HubEngine { } /// @notice Resets Spoke caps on Hubs. + /// @dev The Hub and the Spoke must both be registered on the AddressesProvider. /// @param resets The Spoke caps resets to execute. + /// @param addressesProvider The AddressesProvider authorizing the actions. function executeHubSpokeCapsResets( - IAaveV4ConfigEngine.SpokeCapsReset[] calldata resets + IAaveV4ConfigEngine.SpokeCapsReset[] calldata resets, + IAddressesProvider addressesProvider ) external { uint256 length = resets.length; for (uint256 i; i < length; ++i) { - resets[i].hubConfigurator.resetSpokeCaps(resets[i].hub, resets[i].spoke); - } - } + EngineUtils.requireRegisteredHub(addressesProvider, resets[i].hub); + EngineUtils.requireRegisteredSpoke(addressesProvider, resets[i].spoke); - /// @dev Registers the Hub on the AddressesProvider when requested. - /// @dev Only allowed when the listed asset is the Hub's only asset, to avoid registering an - /// already-configured Hub; reverts otherwise. - function _registerHub(IAaveV4ConfigEngine.AssetListing calldata listing) private { - require( - EngineUtils.isConsistentRegistration(listing.hubRegistration), - InvalidAddressesProviderRegistration() - ); - if (!listing.hubRegistration.register) { - return; + resets[i].hubConfigurator.resetSpokeCaps(resets[i].hub, resets[i].spoke); } - require(IHub(listing.hub).getAssetCount() == 1, InvalidAddressesProviderRegistration()); - listing.hubRegistration.addressesProvider.setCanonicalHub( - listing.hubRegistration.name, - listing.hub - ); } - /// @dev Deploys a TokenizationSpoke (impl + proxy) via CREATE2 and registers it on the Hub. - /// Skipped only when the tokenization config is fully unset; a partially set config reverts - /// instead of being silently ignored. - /// @dev Optionally registers the deployed TokenizationSpoke on the AddressesProvider. + /// @dev Deploys a TokenizationSpoke (impl + proxy) via CREATE2 and registers it on the + /// AddressesProvider and on the Hub. Skipped only when the tokenization config is fully unset; + /// a partially set config reverts instead of being silently ignored. function _deployAndRegisterTokenizationSpoke( IAaveV4ConfigEngine.AssetListing calldata listing, - uint256 assetId + uint256 assetId, + IAddressesProvider addressesProvider ) private { - require( - EngineUtils.isConsistentRegistration(listing.tokenizationSpokeRegistration), - InvalidAddressesProviderRegistration() - ); - IAaveV4ConfigEngine.TokenizationSpokeConfig calldata tokenization = listing.tokenization; bool hasName = bytes(tokenization.name).length > 0; bool hasSymbol = bytes(tokenization.symbol).length > 0; bool hasProxyAdminOwner = tokenization.proxyAdminOwner != address(0); - - if (!hasName && !hasSymbol && !hasProxyAdminOwner && tokenization.addCap == 0) { - require( - !listing.tokenizationSpokeRegistration.register, - InvalidAddressesProviderRegistration() - ); + bool hasRegistrationName = bytes(tokenization.registrationName).length > 0; + + if ( + !hasName && + !hasSymbol && + !hasProxyAdminOwner && + !hasRegistrationName && + tokenization.addCap == 0 + ) { return; } - require(hasName && hasSymbol && hasProxyAdminOwner, InvalidTokenizationSpokeConfig()); + require( + hasName && hasSymbol && hasProxyAdminOwner && hasRegistrationName, + InvalidTokenizationSpokeConfig() + ); address proxy = TokenizationSpokeDeployer.deploy({ hub: listing.hub, @@ -282,6 +308,8 @@ library HubEngine { proxyAdminOwner: tokenization.proxyAdminOwner }); + addressesProvider.setTokenizationSpoke(tokenization.registrationName, proxy); + listing.hubConfigurator.addSpoke( listing.hub, proxy, @@ -294,13 +322,6 @@ library HubEngine { halted: false }) ); - - if (listing.tokenizationSpokeRegistration.register) { - listing.tokenizationSpokeRegistration.addressesProvider.setTokenizationSpoke( - listing.tokenizationSpokeRegistration.name, - proxy - ); - } } /// @dev Merges non-sentinel fields from irData into the current on-chain IR data. diff --git a/src/config-engine/libraries/SpokeEngine.sol b/src/config-engine/libraries/SpokeEngine.sol index 7680b1629..c9dff3ba0 100644 --- a/src/config-engine/libraries/SpokeEngine.sol +++ b/src/config-engine/libraries/SpokeEngine.sol @@ -7,6 +7,7 @@ import {EngineUtils} from 'src/config-engine/libraries/EngineUtils.sol'; import {IHubBase} from 'src/hub/interfaces/IHubBase.sol'; import {ISpoke} from 'src/spoke/interfaces/ISpoke.sol'; import {IAaveV4ConfigEngine} from 'src/config-engine/interfaces/IAaveV4ConfigEngine.sol'; +import {IAddressesProvider} from 'src/addresses-provider/interfaces/IAddressesProvider.sol'; /// @title SpokeEngine /// @author Aave Labs @@ -14,19 +15,18 @@ import {IAaveV4ConfigEngine} from 'src/config-engine/interfaces/IAaveV4ConfigEng library SpokeEngine { using SafeCast for uint256; - /// @dev Thrown when a canonical Spoke registration is requested for a listing that does not support - /// it: the listed reserve is not the Spoke's first (reserve id != 0), or the registration fields are - /// inconsistent with the `register` flag. - error InvalidAddressesProviderRegistration(); - /// @notice Lists new reserves on Spokes. - /// @dev Optionally registers the Spoke on the AddressesProvider. + /// @dev The Spoke must be registered on the AddressesProvider as a canonical Spoke. /// @param listings The reserve listings to execute. + /// @param addressesProvider The AddressesProvider authorizing the actions. function executeSpokeReserveListings( - IAaveV4ConfigEngine.ReserveListing[] calldata listings + IAaveV4ConfigEngine.ReserveListing[] calldata listings, + IAddressesProvider addressesProvider ) external { uint256 length = listings.length; for (uint256 i; i < length; ++i) { + EngineUtils.requireRegisteredCanonicalSpoke(addressesProvider, listings[i].spoke); + uint256 assetId = IHubBase(listings[i].hub).getAssetId(listings[i].underlying); listings[i].spokeConfigurator.addReserve( listings[i].spoke, @@ -36,18 +36,20 @@ library SpokeEngine { listings[i].config, listings[i].dynamicConfig ); - - _registerSpoke(listings[i]); } } /// @notice Updates reserve config on Spokes. /// @param updates The reserve config updates to execute. + /// @param addressesProvider The AddressesProvider authorizing the actions. function executeSpokeReserveConfigUpdates( - IAaveV4ConfigEngine.ReserveConfigUpdate[] calldata updates + IAaveV4ConfigEngine.ReserveConfigUpdate[] calldata updates, + IAddressesProvider addressesProvider ) external { uint256 length = updates.length; for (uint256 i; i < length; ++i) { + EngineUtils.requireRegisteredCanonicalSpoke(addressesProvider, updates[i].spoke); + uint256 reserveId = _resolveReserveId( updates[i].spoke, updates[i].hub, @@ -104,11 +106,15 @@ library SpokeEngine { /// are set, calls updateLiquidationConfig with the full struct. Otherwise, each non-KEEP_CURRENT /// field is updated individually via its dedicated setter. If no field is set, the update is skipped. /// @param updates The liquidation config updates to execute. + /// @param addressesProvider The AddressesProvider authorizing the actions. function executeSpokeLiquidationConfigUpdates( - IAaveV4ConfigEngine.LiquidationConfigUpdate[] calldata updates + IAaveV4ConfigEngine.LiquidationConfigUpdate[] calldata updates, + IAddressesProvider addressesProvider ) external { uint256 length = updates.length; for (uint256 i; i < length; ++i) { + EngineUtils.requireRegisteredCanonicalSpoke(addressesProvider, updates[i].spoke); + bool updateTarget = updates[i].targetHealthFactor != EngineFlags.KEEP_CURRENT; bool updateMaxBonus = updates[i].healthFactorForMaxBonus != EngineFlags.KEEP_CURRENT; bool updateBonusFactor = updates[i].liquidationBonusFactor != EngineFlags.KEEP_CURRENT; @@ -147,11 +153,15 @@ library SpokeEngine { /// @notice Adds dynamic reserve configs on Spokes. /// @param additions The dynamic reserve config additions to execute. + /// @param addressesProvider The AddressesProvider authorizing the actions. function executeSpokeDynamicReserveConfigAdditions( - IAaveV4ConfigEngine.DynamicReserveConfigAddition[] calldata additions + IAaveV4ConfigEngine.DynamicReserveConfigAddition[] calldata additions, + IAddressesProvider addressesProvider ) external { uint256 length = additions.length; for (uint256 i; i < length; ++i) { + EngineUtils.requireRegisteredCanonicalSpoke(addressesProvider, additions[i].spoke); + uint256 reserveId = _resolveReserveId( additions[i].spoke, additions[i].hub, @@ -169,11 +179,15 @@ library SpokeEngine { /// @dev Reads the current config, applies only the fields that differ from KEEP_CURRENT, /// and writes back. If no field is modified the external call is skipped entirely. /// @param updates The dynamic reserve config updates to execute. + /// @param addressesProvider The AddressesProvider authorizing the actions. function executeSpokeDynamicReserveConfigUpdates( - IAaveV4ConfigEngine.DynamicReserveConfigUpdate[] calldata updates + IAaveV4ConfigEngine.DynamicReserveConfigUpdate[] calldata updates, + IAddressesProvider addressesProvider ) external { uint256 length = updates.length; for (uint256 i; i < length; ++i) { + EngineUtils.requireRegisteredCanonicalSpoke(addressesProvider, updates[i].spoke); + uint256 reserveId = _resolveReserveId( updates[i].spoke, updates[i].hub, @@ -212,11 +226,15 @@ library SpokeEngine { /// @notice Updates position managers on Spokes. /// @param updates The position manager updates to execute on Spokes. + /// @param addressesProvider The AddressesProvider authorizing the actions. function executeSpokePositionManagerUpdates( - IAaveV4ConfigEngine.PositionManagerUpdate[] calldata updates + IAaveV4ConfigEngine.PositionManagerUpdate[] calldata updates, + IAddressesProvider addressesProvider ) external { uint256 length = updates.length; for (uint256 i; i < length; ++i) { + EngineUtils.requireRegisteredCanonicalSpoke(addressesProvider, updates[i].spoke); + updates[i].spokeConfigurator.updatePositionManager( updates[i].spoke, updates[i].positionManager, @@ -234,22 +252,4 @@ library SpokeEngine { uint256 assetId = IHubBase(hub).getAssetId(underlying); return ISpoke(spoke).getReserveId(hub, assetId); } - - /// @dev Registers the Spoke on the AddressesProvider when requested. - /// @dev Only allowed when the listed reserve is the Spoke's only reserve, to avoid registering an - /// already-configured Spoke; reverts otherwise. - function _registerSpoke(IAaveV4ConfigEngine.ReserveListing calldata listing) private { - require( - EngineUtils.isConsistentRegistration(listing.spokeRegistration), - InvalidAddressesProviderRegistration() - ); - if (!listing.spokeRegistration.register) { - return; - } - require(ISpoke(listing.spoke).getReserveCount() == 1, InvalidAddressesProviderRegistration()); - listing.spokeRegistration.addressesProvider.setCanonicalSpoke( - listing.spokeRegistration.name, - listing.spoke - ); - } } diff --git a/tests/config-engine/AaveV4Payload.EmptyReturns.t.sol b/tests/config-engine/AaveV4Payload.EmptyReturns.t.sol index 9165aac08..90ab3549f 100644 --- a/tests/config-engine/AaveV4Payload.EmptyReturns.t.sol +++ b/tests/config-engine/AaveV4Payload.EmptyReturns.t.sol @@ -40,6 +40,10 @@ contract AaveV4PayloadEmptyReturnsTest is BaseConfigEngineTest { minimal.execute(); } + function test_addressesProviderEntryUpdates_returnsEmpty() public view { + assertEq(minimal.addressesProviderEntryUpdates().length, 0); + } + function test_hubAssetListings_returnsEmpty() public view { assertEq(minimal.hubAssetListings().length, 0); } diff --git a/tests/config-engine/AaveV4Payload.t.sol b/tests/config-engine/AaveV4Payload.t.sol index 2adea0c82..75e01be86 100644 --- a/tests/config-engine/AaveV4Payload.t.sol +++ b/tests/config-engine/AaveV4Payload.t.sol @@ -385,6 +385,7 @@ contract AaveV4PayloadTest is BaseConfigEngineTest { function test_execute_hubSpokeToAssetsAdditions() public { (ISpoke newSpoke, ) = _deployNewSpoke(); + _registerSpokeOnProvider(newSpoke); IAaveV4ConfigEngine.SpokeToAssetsAddition[] memory additions = new IAaveV4ConfigEngine.SpokeToAssetsAddition[](1); @@ -642,6 +643,40 @@ contract AaveV4PayloadTest is BaseConfigEngineTest { assertEq(spokeConfig.drawCap, 0); } + function test_execute_addressesProviderEntryUpdates_runBeforeHubAndSpokeActions() public { + (ISpoke newSpoke, ) = _deployNewSpoke(); + _seedAsset(hub1(), irStrategy1(), address(newToken), 18); + + // provider writes run in the payload's context here, so it must own the provider + vm.prank(address(engine)); + AddressesProviderInstance(address(addressesProvider)).transferOwnership(address(payload)); + vm.prank(address(payload)); + AddressesProviderInstance(address(addressesProvider)).acceptOwnership(); + + // the payload registers the new Spoke and lists a reserve on it in the same execution; + // the listing only succeeds if the entry updates are executed first + payload.setAddressesProviderEntryUpdates( + _toAddressesProviderEntryUpdateArray( + IAaveV4ConfigEngine.AddressesProviderEntryUpdate({ + name: 'NEW', + tag: addressesProvider.CANONICAL_SPOKE_TAG(), + addr: address(newSpoke) + }) + ) + ); + + IAaveV4ConfigEngine.ReserveListing memory listing = _defaultReserveListing(); + listing.spoke = address(newSpoke); + listing.underlying = address(newToken); + listing.priceSource = _deployMockPriceFeed(newSpoke, address(priceFeedNew)); + payload.setSpokeReserveListings(_toReserveListingArray(listing)); + + payload.execute(); + + assertEq(addressesProvider.getCanonicalSpoke('NEW'), address(newSpoke)); + assertEq(newSpoke.getReserveCount(), 1); + } + function test_execute_spokeReserveListings() public { uint256 newAssetId = _seedAsset(hub1(), irStrategy1(), address(newToken), 18); _seedSpokeOnAsset(hub1(), newAssetId, spoke1()); @@ -668,11 +703,6 @@ contract AaveV4PayloadTest is BaseConfigEngineTest { collateralFactor: 80_00, maxLiquidationBonus: 105_00, liquidationFee: 10_00 - }), - spokeRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ - addressesProvider: IAddressesProvider(address(0)), - register: false, - name: '' }) }); payload.setSpokeReserveListings(listings); diff --git a/tests/config-engine/AddressesProviderRegistration.t.sol b/tests/config-engine/AddressesProviderRegistration.t.sol index 515aea689..affd341ef 100644 --- a/tests/config-engine/AddressesProviderRegistration.t.sol +++ b/tests/config-engine/AddressesProviderRegistration.t.sol @@ -3,266 +3,509 @@ pragma solidity ^0.8.0; import 'tests/config-engine/BaseConfigEngine.t.sol'; -import {TransparentUpgradeableProxy} from 'src/dependencies/openzeppelin/TransparentUpgradeableProxy.sol'; -import {AddressesProviderInstance} from 'src/addresses-provider/instances/AddressesProviderInstance.sol'; +import {Ownable} from 'src/dependencies/openzeppelin/Ownable.sol'; +import {EngineUtils} from 'src/config-engine/libraries/EngineUtils.sol'; -/// @notice Tests the optional AddressesProvider registration during Hub asset and Spoke reserve -/// listings. The environment is intentionally left unseeded so the first listed asset/reserve has -/// id 0, which is the gate for registering a newly configured Hub/Spoke. +/// @notice Tests the AddressesProvider integration of the config engine: entry updates via the +/// dedicated action, and the requirement that Hubs and Spokes targeted by engine actions are +/// registered on the AddressesProvider. contract AddressesProviderRegistrationTest is BaseConfigEngineTest { - IAddressesProvider internal provider; + function _entryUpdate( + string memory name, + string memory tag, + address addr + ) internal pure returns (IAaveV4ConfigEngine.AddressesProviderEntryUpdate[] memory) { + return + _toAddressesProviderEntryUpdateArray( + IAaveV4ConfigEngine.AddressesProviderEntryUpdate({name: name, tag: tag, addr: addr}) + ); + } - function setUp() public override { - super.setUp(); - // The engine is the actor making the external calls in these tests, so it must own the provider. - provider = _deployAddressesProvider(address(engine)); + function _unregisterHub1() internal { + engine.executeAddressesProviderEntryUpdates( + _entryUpdate('HUB_1', addressesProvider.CANONICAL_HUB_TAG(), address(0)) + ); } - function _registration( - IAddressesProvider addressesProvider, - string memory name - ) internal pure returns (IAaveV4ConfigEngine.AddressesProviderRegistration memory) { - return - IAaveV4ConfigEngine.AddressesProviderRegistration({ - addressesProvider: addressesProvider, - register: true, - name: name - }); + function _unregisterSpoke1() internal { + engine.executeAddressesProviderEntryUpdates( + _entryUpdate('SPOKE_1', addressesProvider.CANONICAL_SPOKE_TAG(), address(0)) + ); } - function _deployAddressesProvider(address owner) internal returns (IAddressesProvider) { - return - IAddressesProvider( - address( - new TransparentUpgradeableProxy( - address(new AddressesProviderInstance()), - ADMIN, - abi.encodeCall(AddressesProviderInstance.initialize, (owner)) - ) - ) - ); + // Entry updates + + function test_executeAddressesProviderEntryUpdates_registers() public { + address configEngine = makeAddr('CONFIG_ENGINE'); + + engine.executeAddressesProviderEntryUpdates( + _entryUpdate('CONFIG_ENGINE', 'PERIPHERY', configEngine) + ); + + assertEq(addressesProvider.getAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY'}), configEngine); + assertTrue(addressesProvider.isRegistered(configEngine, 'PERIPHERY')); } - // Hub registration + function test_executeAddressesProviderEntryUpdates_unregisters() public { + assertTrue( + addressesProvider.isRegistered(address(hub1()), addressesProvider.CANONICAL_HUB_TAG()) + ); - function test_executeHubAssetListings_registersHub() public { - IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); - listing.underlying = address(weth); - listing.hubRegistration = _registration(provider, 'CORE'); + _unregisterHub1(); - engine.executeHubAssetListings(_toAssetListingArray(listing)); + assertFalse( + addressesProvider.isRegistered(address(hub1()), addressesProvider.CANONICAL_HUB_TAG()) + ); + } - assertEq(hub1().getAssetId(address(weth)), 0); - assertEq(provider.getCanonicalHub('CORE'), address(hub1())); + function test_executeAddressesProviderEntryUpdates_multiple() public { + IAaveV4ConfigEngine.AddressesProviderEntryUpdate[] + memory updates = new IAaveV4ConfigEngine.AddressesProviderEntryUpdate[](2); + updates[0] = IAaveV4ConfigEngine.AddressesProviderEntryUpdate({ + name: 'HUB_1', + tag: addressesProvider.CANONICAL_HUB_TAG(), + addr: address(0) + }); + updates[1] = IAaveV4ConfigEngine.AddressesProviderEntryUpdate({ + name: 'CORE', + tag: addressesProvider.CANONICAL_HUB_TAG(), + addr: address(hub1()) + }); + + engine.executeAddressesProviderEntryUpdates(updates); + + assertEq(addressesProvider.getCanonicalHub('HUB_1'), address(0)); + assertEq(addressesProvider.getCanonicalHub('CORE'), address(hub1())); } - function test_executeHubAssetListings_registerHub_revertsWhenNotFirstAsset() public { - IAaveV4ConfigEngine.AssetListing memory first = _defaultAssetListing(); - first.underlying = address(weth); - engine.executeHubAssetListings(_toAssetListingArray(first)); + function test_executeAddressesProviderEntryUpdates_revertsWith_AddressAlreadySet() public { + string memory tag = addressesProvider.CANONICAL_HUB_TAG(); + bytes32 id = addressesProvider.getId('HUB_1', tag); + IAaveV4ConfigEngine.AddressesProviderEntryUpdate[] memory updates = _entryUpdate( + 'HUB_1', + tag, + address(hub2()) + ); - // usdx becomes asset id 1 on hub1, so registering the hub during its listing is rejected - IAaveV4ConfigEngine.AssetListing memory second = _defaultAssetListing(); - second.underlying = address(usdx); - second.hubRegistration = _registration(provider, 'CORE'); + vm.expectRevert(abi.encodeWithSelector(IAddressesProvider.AddressAlreadySet.selector, id)); + engine.executeAddressesProviderEntryUpdates(updates); + } - vm.expectRevert(HubEngine.InvalidAddressesProviderRegistration.selector); - engine.executeHubAssetListings(_toAssetListingArray(second)); + function test_executeAddressesProviderEntryUpdates_revertsWith_OwnableUnauthorizedAccount() + public + { + vm.prank(address(engine)); + AddressesProviderInstance(address(addressesProvider)).transferOwnership(ADMIN); + vm.prank(ADMIN); + AddressesProviderInstance(address(addressesProvider)).acceptOwnership(); + + vm.expectRevert( + abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, address(engine)) + ); + engine.executeAddressesProviderEntryUpdates( + _entryUpdate('CONFIG_ENGINE', 'PERIPHERY', makeAddr('CONFIG_ENGINE')) + ); } - function test_executeHubAssetListings_registerHub_revertsWhenNoProvider() public { - IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); - listing.underlying = address(weth); - listing.hubRegistration = _registration(IAddressesProvider(address(0)), 'CORE'); + // Hub actions require a registered Hub - vm.expectRevert(HubEngine.InvalidAddressesProviderRegistration.selector); - engine.executeHubAssetListings(_toAssetListingArray(listing)); + function test_executeHubAssetListings_revertsWith_HubNotRegistered() public { + _unregisterHub1(); + + vm.expectRevert(abi.encodeWithSelector(EngineUtils.HubNotRegistered.selector, address(hub1()))); + engine.executeHubAssetListings(_toAssetListingArray(_defaultAssetListing())); } - function test_executeHubAssetListings_registerHub_revertsWhenNoName() public { - IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); - listing.underlying = address(weth); - listing.hubRegistration = _registration(provider, ''); + function test_executeHubAssetConfigUpdates_revertsWith_HubNotRegistered() public { + _unregisterHub1(); - vm.expectRevert(HubEngine.InvalidAddressesProviderRegistration.selector); - engine.executeHubAssetListings(_toAssetListingArray(listing)); + vm.expectRevert(abi.encodeWithSelector(EngineUtils.HubNotRegistered.selector, address(hub1()))); + engine.executeHubAssetConfigUpdates(_toAssetConfigUpdateArray(_defaultAssetConfigUpdate())); } - function test_executeHubAssetListings_registerHub_revertsWhenFieldsSetWithoutRegister() public { - IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); - listing.underlying = address(weth); - listing.hubRegistration.name = 'CORE'; + function test_executeHubSpokeToAssetsAdditions_revertsWith_HubNotRegistered() public { + _unregisterHub1(); - vm.expectRevert(HubEngine.InvalidAddressesProviderRegistration.selector); - engine.executeHubAssetListings(_toAssetListingArray(listing)); + IAaveV4ConfigEngine.SpokeToAssetsAddition memory addition = IAaveV4ConfigEngine + .SpokeToAssetsAddition({ + hubConfigurator: hubConfigurator, + hub: address(hub1()), + spoke: address(spoke1()), + assets: new IAaveV4ConfigEngine.SpokeAssetConfig[](0) + }); + + vm.expectRevert(abi.encodeWithSelector(EngineUtils.HubNotRegistered.selector, address(hub1()))); + engine.executeHubSpokeToAssetsAdditions(_toSpokeToAssetsAdditionArray(addition)); } - function test_executeHubAssetListings_registerHub_revertsWhenAlreadyRegistered() public { - IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); - listing.underlying = address(weth); - listing.hubRegistration = _registration(provider, 'CORE'); - engine.executeHubAssetListings(_toAssetListingArray(listing)); + function test_executeHubSpokeConfigUpdates_revertsWith_HubNotRegistered() public { + _unregisterHub1(); - // listing on another fresh hub (asset id 0) but reusing the same name reverts in the provider - IAaveV4ConfigEngine.AssetListing memory second = _defaultAssetListing(); - second.hub = address(hub2()); - second.underlying = address(weth); - second.irStrategy = address(irStrategy2()); - second.hubRegistration = _registration(provider, 'CORE'); + vm.expectRevert(abi.encodeWithSelector(EngineUtils.HubNotRegistered.selector, address(hub1()))); + engine.executeHubSpokeConfigUpdates(_toSpokeConfigUpdateArray(_defaultSpokeConfigUpdate())); + } - vm.expectRevert( - abi.encodeWithSelector( - IAddressesProvider.AddressAlreadySet.selector, - provider.getId('CORE', provider.CANONICAL_HUB_TAG()) + function test_executeHubAssetHalts_revertsWith_HubNotRegistered() public { + _unregisterHub1(); + + vm.expectRevert(abi.encodeWithSelector(EngineUtils.HubNotRegistered.selector, address(hub1()))); + engine.executeHubAssetHalts( + _toAssetHaltArray( + IAaveV4ConfigEngine.AssetHalt({ + hubConfigurator: hubConfigurator, + hub: address(hub1()), + underlying: address(weth) + }) ) ); - engine.executeHubAssetListings(_toAssetListingArray(second)); } - // Tokenization spoke registration + function test_executeHubAssetDeactivations_revertsWith_HubNotRegistered() public { + _unregisterHub1(); + + vm.expectRevert(abi.encodeWithSelector(EngineUtils.HubNotRegistered.selector, address(hub1()))); + engine.executeHubAssetDeactivations( + _toAssetDeactivationArray( + IAaveV4ConfigEngine.AssetDeactivation({ + hubConfigurator: hubConfigurator, + hub: address(hub1()), + underlying: address(weth) + }) + ) + ); + } - function test_executeHubAssetListings_registersTokenizationSpoke() public { - IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); - listing.underlying = address(weth); - listing.tokenization = IAaveV4ConfigEngine.TokenizationSpokeConfig({ - addCap: 1_000, - proxyAdminOwner: PROXY_ADMIN_OWNER, - name: 'Aave WETH', - symbol: 'aWETH' - }); - listing.tokenizationSpokeRegistration = _registration(provider, 'CORE_WETH'); + function test_executeHubAssetCapsResets_revertsWith_HubNotRegistered() public { + _unregisterHub1(); + + vm.expectRevert(abi.encodeWithSelector(EngineUtils.HubNotRegistered.selector, address(hub1()))); + engine.executeHubAssetCapsResets( + _toAssetCapsResetArray( + IAaveV4ConfigEngine.AssetCapsReset({ + hubConfigurator: hubConfigurator, + hub: address(hub1()), + underlying: address(weth) + }) + ) + ); + } - address expectedProxy = TokenizationSpokeDeployer.computeProxyAddress( - address(hub1()), - address(weth), - 'Aave WETH', - 'aWETH', - PROXY_ADMIN_OWNER + function test_executeHubSpokeDeactivations_revertsWith_HubNotRegistered() public { + _unregisterHub1(); + + vm.expectRevert(abi.encodeWithSelector(EngineUtils.HubNotRegistered.selector, address(hub1()))); + engine.executeHubSpokeDeactivations( + _toSpokeDeactivationArray( + IAaveV4ConfigEngine.SpokeDeactivation({ + hubConfigurator: hubConfigurator, + hub: address(hub1()), + spoke: address(spoke1()) + }) + ) ); + } - engine.executeHubAssetListings(_toAssetListingArray(listing)); + function test_executeHubSpokeCapsResets_revertsWith_HubNotRegistered() public { + _unregisterHub1(); + + vm.expectRevert(abi.encodeWithSelector(EngineUtils.HubNotRegistered.selector, address(hub1()))); + engine.executeHubSpokeCapsResets( + _toSpokeCapsResetArray( + IAaveV4ConfigEngine.SpokeCapsReset({ + hubConfigurator: hubConfigurator, + hub: address(hub1()), + spoke: address(spoke1()) + }) + ) + ); + } + + // Spoke actions require a registered Spoke + + function test_executeSpokeReserveListings_revertsWith_CanonicalSpokeNotRegistered() public { + _unregisterSpoke1(); - assertEq(provider.getTokenizationSpoke('CORE_WETH'), expectedProxy); + vm.expectRevert( + abi.encodeWithSelector(EngineUtils.CanonicalSpokeNotRegistered.selector, address(spoke1())) + ); + engine.executeSpokeReserveListings(_toReserveListingArray(_defaultReserveListing())); } - function test_executeHubAssetListings_registerTokenizationSpoke_revertsWhenNotDeployed() public { - IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); - listing.underlying = address(weth); - // no tokenization name/symbol => no TokenizationSpoke deployed - listing.tokenizationSpokeRegistration = _registration(provider, 'CORE_WETH'); + function test_executeSpokeReserveConfigUpdates_revertsWith_CanonicalSpokeNotRegistered() public { + _unregisterSpoke1(); - vm.expectRevert(HubEngine.InvalidAddressesProviderRegistration.selector); - engine.executeHubAssetListings(_toAssetListingArray(listing)); + vm.expectRevert( + abi.encodeWithSelector(EngineUtils.CanonicalSpokeNotRegistered.selector, address(spoke1())) + ); + engine.executeSpokeReserveConfigUpdates( + _toReserveConfigUpdateArray(_defaultReserveConfigUpdate()) + ); } - function test_executeHubAssetListings_registerTokenizationSpoke_revertsWhenFieldsSetWithoutRegister() + function test_executeSpokeLiquidationConfigUpdates_revertsWith_CanonicalSpokeNotRegistered() public { - IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); - listing.underlying = address(weth); - listing.tokenizationSpokeRegistration.name = 'CORE_WETH'; + _unregisterSpoke1(); - vm.expectRevert(HubEngine.InvalidAddressesProviderRegistration.selector); - engine.executeHubAssetListings(_toAssetListingArray(listing)); + vm.expectRevert( + abi.encodeWithSelector(EngineUtils.CanonicalSpokeNotRegistered.selector, address(spoke1())) + ); + engine.executeSpokeLiquidationConfigUpdates( + _toLiquidationConfigUpdateArray(_defaultLiquidationConfigUpdate()) + ); } - function test_executeHubAssetListings_registersHubAndTokenizationSpoke() public { - IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); - listing.underlying = address(weth); - listing.tokenization = IAaveV4ConfigEngine.TokenizationSpokeConfig({ - addCap: 1_000, - proxyAdminOwner: PROXY_ADMIN_OWNER, - name: 'Aave WETH', - symbol: 'aWETH' - }); - listing.hubRegistration = _registration(provider, 'CORE'); - listing.tokenizationSpokeRegistration = _registration(provider, 'CORE_WETH'); + function test_executeSpokeDynamicReserveConfigAdditions_revertsWith_CanonicalSpokeNotRegistered() + public + { + _unregisterSpoke1(); - address expectedProxy = TokenizationSpokeDeployer.computeProxyAddress( - address(hub1()), - address(weth), - 'Aave WETH', - 'aWETH', - PROXY_ADMIN_OWNER + vm.expectRevert( + abi.encodeWithSelector(EngineUtils.CanonicalSpokeNotRegistered.selector, address(spoke1())) + ); + engine.executeSpokeDynamicReserveConfigAdditions( + _toDynamicReserveConfigAdditionArray(_defaultDynamicReserveConfigAddition()) ); + } - engine.executeHubAssetListings(_toAssetListingArray(listing)); + function test_executeSpokeDynamicReserveConfigUpdates_revertsWith_CanonicalSpokeNotRegistered() + public + { + _unregisterSpoke1(); - assertEq(provider.getCanonicalHub('CORE'), address(hub1())); - assertEq(provider.getTokenizationSpoke('CORE_WETH'), expectedProxy); + vm.expectRevert( + abi.encodeWithSelector(EngineUtils.CanonicalSpokeNotRegistered.selector, address(spoke1())) + ); + engine.executeSpokeDynamicReserveConfigUpdates( + _toDynamicReserveConfigUpdateArray(_defaultDynamicReserveConfigUpdate()) + ); } - // Canonical spoke registration + function test_executeSpokePositionManagerUpdates_revertsWith_CanonicalSpokeNotRegistered() + public + { + _unregisterSpoke1(); - function test_executeSpokeReserveListings_registersSpoke() public { - _seedAsset(hub1(), irStrategy1(), address(weth), 18); - address priceSource = _deployMockPriceFeed(spoke1(), tokenList[TOKEN_WETH].priceFeed); + vm.expectRevert( + abi.encodeWithSelector(EngineUtils.CanonicalSpokeNotRegistered.selector, address(spoke1())) + ); + engine.executeSpokePositionManagerUpdates( + _toPositionManagerUpdateArray(_defaultPositionManagerUpdate()) + ); + } - IAaveV4ConfigEngine.ReserveListing memory listing = _defaultReserveListing(); - listing.underlying = address(weth); - listing.priceSource = priceSource; - listing.spokeRegistration = _registration(provider, 'MAIN'); + // Spokes attached to a Hub asset require registration - engine.executeSpokeReserveListings(_toReserveListingArray(listing)); + function test_executeHubSpokeToAssetsAdditions_revertsWith_SpokeNotRegistered() public { + (ISpoke newSpoke, ) = _deployNewSpoke(); - assertEq(spoke1().getReserveId(address(hub1()), 0), 0); - assertEq(provider.getCanonicalSpoke('MAIN'), address(spoke1())); + IAaveV4ConfigEngine.SpokeToAssetsAddition memory addition = IAaveV4ConfigEngine + .SpokeToAssetsAddition({ + hubConfigurator: hubConfigurator, + hub: address(hub1()), + spoke: address(newSpoke), + assets: new IAaveV4ConfigEngine.SpokeAssetConfig[](0) + }); + + vm.expectRevert( + abi.encodeWithSelector(EngineUtils.SpokeNotRegistered.selector, address(newSpoke)) + ); + engine.executeHubSpokeToAssetsAdditions(_toSpokeToAssetsAdditionArray(addition)); } - function test_executeSpokeReserveListings_registerSpoke_revertsWhenNotFirstReserve() public { - _seedAsset(hub1(), irStrategy1(), address(weth), 18); - _seedAsset(hub1(), irStrategy1(), address(usdx), 6); + // Spoke actions require the canonical Spoke tag specifically + + function test_spokeActions_revertWith_CanonicalSpokeNotRegistered_tokenizationTag() public { + (ISpoke newSpoke, ) = _deployNewSpoke(); + engine.executeAddressesProviderEntryUpdates( + _entryUpdate('NEW', addressesProvider.TOKENIZATION_SPOKE_TAG(), address(newSpoke)) + ); + + IAaveV4ConfigEngine.PositionManagerUpdate memory update = _defaultPositionManagerUpdate(); + update.spoke = address(newSpoke); + + vm.expectRevert( + abi.encodeWithSelector(EngineUtils.CanonicalSpokeNotRegistered.selector, address(newSpoke)) + ); + engine.executeSpokePositionManagerUpdates(_toPositionManagerUpdateArray(update)); + } - IAaveV4ConfigEngine.ReserveListing memory first = _defaultReserveListing(); - first.underlying = address(weth); - first.priceSource = _deployMockPriceFeed(spoke1(), tokenList[TOKEN_WETH].priceFeed); - engine.executeSpokeReserveListings(_toReserveListingArray(first)); + function test_spokeActions_revertWith_CanonicalSpokeNotRegistered_treasuryTag() public { + (ISpoke newSpoke, ) = _deployNewSpoke(); + engine.executeAddressesProviderEntryUpdates( + _entryUpdate('NEW', addressesProvider.TREASURY_SPOKE_TAG(), address(newSpoke)) + ); - // usdx becomes reserve id 1 on spoke1, so registering the spoke during its listing is rejected - IAaveV4ConfigEngine.ReserveListing memory second = _defaultReserveListing(); - second.underlying = address(usdx); - second.priceSource = _deployMockPriceFeed(spoke1(), tokenList[TOKEN_USDX].priceFeed); - second.spokeRegistration = _registration(provider, 'MAIN'); + IAaveV4ConfigEngine.PositionManagerUpdate memory update = _defaultPositionManagerUpdate(); + update.spoke = address(newSpoke); - vm.expectRevert(SpokeEngine.InvalidAddressesProviderRegistration.selector); - engine.executeSpokeReserveListings(_toReserveListingArray(second)); + vm.expectRevert( + abi.encodeWithSelector(EngineUtils.CanonicalSpokeNotRegistered.selector, address(newSpoke)) + ); + engine.executeSpokePositionManagerUpdates(_toPositionManagerUpdateArray(update)); } - function test_executeSpokeReserveListings_registerSpoke_revertsWhenNoProvider() public { + // Hub-side Spoke references accept any spoke tag + + function test_executeHubSpokeConfigUpdates_revertsWith_SpokeNotRegistered() public { + (ISpoke newSpoke, ) = _deployNewSpoke(); + + IAaveV4ConfigEngine.SpokeConfigUpdate memory update = _defaultSpokeConfigUpdate(); + update.spoke = address(newSpoke); + + vm.expectRevert( + abi.encodeWithSelector(EngineUtils.SpokeNotRegistered.selector, address(newSpoke)) + ); + engine.executeHubSpokeConfigUpdates(_toSpokeConfigUpdateArray(update)); + } + + function test_executeHubSpokeDeactivations_revertsWith_SpokeNotRegistered() public { + (ISpoke newSpoke, ) = _deployNewSpoke(); + + vm.expectRevert( + abi.encodeWithSelector(EngineUtils.SpokeNotRegistered.selector, address(newSpoke)) + ); + engine.executeHubSpokeDeactivations( + _toSpokeDeactivationArray( + IAaveV4ConfigEngine.SpokeDeactivation({ + hubConfigurator: hubConfigurator, + hub: address(hub1()), + spoke: address(newSpoke) + }) + ) + ); + } + + function test_executeHubSpokeCapsResets_revertsWith_SpokeNotRegistered() public { + (ISpoke newSpoke, ) = _deployNewSpoke(); + + vm.expectRevert( + abi.encodeWithSelector(EngineUtils.SpokeNotRegistered.selector, address(newSpoke)) + ); + engine.executeHubSpokeCapsResets( + _toSpokeCapsResetArray( + IAaveV4ConfigEngine.SpokeCapsReset({ + hubConfigurator: hubConfigurator, + hub: address(hub1()), + spoke: address(newSpoke) + }) + ) + ); + } + + function test_hubSpokeActions_allowAnySpokeTag() public { _seedAsset(hub1(), irStrategy1(), address(weth), 18); + (ISpoke newSpoke, ) = _deployNewSpoke(); + engine.executeAddressesProviderEntryUpdates( + _entryUpdate('NEW', addressesProvider.TOKENIZATION_SPOKE_TAG(), address(newSpoke)) + ); - IAaveV4ConfigEngine.ReserveListing memory listing = _defaultReserveListing(); - listing.underlying = address(weth); - listing.priceSource = _deployMockPriceFeed(spoke1(), tokenList[TOKEN_WETH].priceFeed); - listing.spokeRegistration = _registration(IAddressesProvider(address(0)), 'MAIN'); + IAaveV4ConfigEngine.SpokeAssetConfig[] + memory assets = new IAaveV4ConfigEngine.SpokeAssetConfig[](1); + assets[0] = IAaveV4ConfigEngine.SpokeAssetConfig({ + underlying: address(weth), + config: IHub.SpokeConfig({ + addCap: 1000, + drawCap: 500, + riskPremiumThreshold: 100, + active: true, + halted: false + }) + }); + engine.executeHubSpokeToAssetsAdditions( + _toSpokeToAssetsAdditionArray( + IAaveV4ConfigEngine.SpokeToAssetsAddition({ + hubConfigurator: hubConfigurator, + hub: address(hub1()), + spoke: address(newSpoke), + assets: assets + }) + ) + ); - vm.expectRevert(SpokeEngine.InvalidAddressesProviderRegistration.selector); - engine.executeSpokeReserveListings(_toReserveListingArray(listing)); + engine.executeHubSpokeDeactivations( + _toSpokeDeactivationArray( + IAaveV4ConfigEngine.SpokeDeactivation({ + hubConfigurator: hubConfigurator, + hub: address(hub1()), + spoke: address(newSpoke) + }) + ) + ); + + assertFalse(hub1().getSpokeConfig(0, address(newSpoke)).active); } - function test_executeSpokeReserveListings_registerSpoke_revertsWhenNoName() public { + // Register-then-act within the same flow + + function test_registerThenList() public { + (ISpoke newSpoke, ) = _deployNewSpoke(); _seedAsset(hub1(), irStrategy1(), address(weth), 18); + engine.executeAddressesProviderEntryUpdates( + _entryUpdate('NEW', addressesProvider.CANONICAL_SPOKE_TAG(), address(newSpoke)) + ); + IAaveV4ConfigEngine.ReserveListing memory listing = _defaultReserveListing(); - listing.underlying = address(weth); - listing.priceSource = _deployMockPriceFeed(spoke1(), tokenList[TOKEN_WETH].priceFeed); - listing.spokeRegistration = _registration(provider, ''); + listing.spoke = address(newSpoke); + listing.priceSource = _deployMockPriceFeed(newSpoke, tokenList[TOKEN_WETH].priceFeed); - vm.expectRevert(SpokeEngine.InvalidAddressesProviderRegistration.selector); engine.executeSpokeReserveListings(_toReserveListingArray(listing)); + + assertEq(newSpoke.getReserveId(address(hub1()), 0), 0); + } + + // Tokenization spoke auto-registration + + function test_executeHubAssetListings_registersTokenizationSpoke() public { + IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); + listing.tokenization = IAaveV4ConfigEngine.TokenizationSpokeConfig({ + addCap: 1_000, + proxyAdminOwner: PROXY_ADMIN_OWNER, + name: 'Aave WETH', + symbol: 'aWETH', + registrationName: 'HUB1_WETH' + }); + + address expectedProxy = TokenizationSpokeDeployer.computeProxyAddress( + address(hub1()), + address(weth), + 'Aave WETH', + 'aWETH', + PROXY_ADMIN_OWNER + ); + + engine.executeHubAssetListings(_toAssetListingArray(listing)); + + assertEq(addressesProvider.getTokenizationSpoke('HUB1_WETH'), expectedProxy); + assertTrue( + addressesProvider.isRegistered(expectedProxy, addressesProvider.TOKENIZATION_SPOKE_TAG()) + ); } - function test_executeSpokeReserveListings_registerSpoke_revertsWhenFieldsSetWithoutRegister() + function test_executeHubAssetListings_revertsWith_InvalidTokenizationSpokeConfig_whenNoRegistrationName() public { - _seedAsset(hub1(), irStrategy1(), address(weth), 18); + IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); + listing.tokenization = IAaveV4ConfigEngine.TokenizationSpokeConfig({ + addCap: 1_000, + proxyAdminOwner: PROXY_ADMIN_OWNER, + name: 'Aave WETH', + symbol: 'aWETH', + registrationName: '' + }); - IAaveV4ConfigEngine.ReserveListing memory listing = _defaultReserveListing(); - listing.underlying = address(weth); - listing.priceSource = _deployMockPriceFeed(spoke1(), tokenList[TOKEN_WETH].priceFeed); - listing.spokeRegistration.name = 'MAIN'; + vm.expectRevert(HubEngine.InvalidTokenizationSpokeConfig.selector); + engine.executeHubAssetListings(_toAssetListingArray(listing)); + } - vm.expectRevert(SpokeEngine.InvalidAddressesProviderRegistration.selector); - engine.executeSpokeReserveListings(_toReserveListingArray(listing)); + function test_executeHubAssetListings_revertsWith_InvalidTokenizationSpokeConfig_whenOnlyRegistrationName() + public + { + IAaveV4ConfigEngine.AssetListing memory listing = _defaultAssetListing(); + listing.tokenization.registrationName = 'HUB1_WETH'; + + vm.expectRevert(HubEngine.InvalidTokenizationSpokeConfig.selector); + engine.executeHubAssetListings(_toAssetListingArray(listing)); } } diff --git a/tests/config-engine/BaseConfigEngine.t.sol b/tests/config-engine/BaseConfigEngine.t.sol index f530c065c..30989c310 100644 --- a/tests/config-engine/BaseConfigEngine.t.sol +++ b/tests/config-engine/BaseConfigEngine.t.sol @@ -29,6 +29,8 @@ import {AaveV4Payload} from 'src/config-engine/AaveV4Payload.sol'; import {AaveV4ConfigEngine} from 'src/config-engine/AaveV4ConfigEngine.sol'; import {IAaveV4ConfigEngine} from 'src/config-engine/interfaces/IAaveV4ConfigEngine.sol'; import {IAddressesProvider} from 'src/addresses-provider/interfaces/IAddressesProvider.sol'; +import {AddressesProviderInstance} from 'src/addresses-provider/instances/AddressesProviderInstance.sol'; +import {TransparentUpgradeableProxy} from 'src/dependencies/openzeppelin/TransparentUpgradeableProxy.sol'; import {EngineFlags} from 'src/config-engine/libraries/EngineFlags.sol'; import {AccessManagerEngine} from 'src/config-engine/libraries/AccessManagerEngine.sol'; import {HubEngine} from 'src/config-engine/libraries/HubEngine.sol'; @@ -80,6 +82,7 @@ abstract contract BaseConfigEngineTest is Test, Create2TestHelper { address internal PROXY_ADMIN_OWNER = makeAddr('PROXY_ADMIN_OWNER'); MockGovernanceExecutor internal executor; + IAddressesProvider internal addressesProvider; AaveV4ConfigEngine internal engine; IAccessManager internal accessManager; IHubConfigurator internal hubConfigurator; @@ -158,10 +161,12 @@ abstract contract BaseConfigEngineTest is Test, Create2TestHelper { } executor = new MockGovernanceExecutor(PAYLOADS_CONTROLLER); - engine = new AaveV4ConfigEngine(); + addressesProvider = _deployAddressesProvider(address(this)); + engine = new AaveV4ConfigEngine(addressesProvider); positionManager = new PositionManagerBaseWrapper(address(engine)); _setupRoles(report); + _registerTestEnv(); vm.label(address(hubs[0]), 'hub1'); vm.label(address(hubs[1]), 'hub2'); @@ -196,6 +201,48 @@ abstract contract BaseConfigEngineTest is Test, Create2TestHelper { assertEq(vm.getRecordedLogs().length, expectedCount); } + function _deployAddressesProvider(address owner) internal returns (IAddressesProvider) { + return + IAddressesProvider( + address( + new TransparentUpgradeableProxy( + address(new AddressesProviderInstance()), + ADMIN, + abi.encodeCall(AddressesProviderInstance.initialize, (owner)) + ) + ) + ); + } + + /// @dev Registers the test Hubs and Spokes on the AddressesProvider, then hands ownership to the + /// engine, which is the actor making the provider calls when tests invoke it directly. + function _registerTestEnv() internal { + for (uint256 i; i < NUM_HUBS; ++i) { + addressesProvider.setCanonicalHub( + string.concat('HUB_', vm.toString(i + 1)), + address(hubs[i]) + ); + } + for (uint256 i; i < NUM_SPOKES; ++i) { + addressesProvider.setCanonicalSpoke( + string.concat('SPOKE_', vm.toString(i + 1)), + address(spokes[i]) + ); + } + AddressesProviderInstance(address(addressesProvider)).transferOwnership(address(engine)); + vm.prank(address(engine)); + AddressesProviderInstance(address(addressesProvider)).acceptOwnership(); + } + + /// @dev Registers a Spoke on the AddressesProvider as the engine, the provider owner after setUp. + function _registerSpokeOnProvider(ISpoke spoke) internal { + vm.prank(address(engine)); + addressesProvider.setCanonicalSpoke( + string.concat('SPOKE_', vm.toString(address(spoke))), + address(spoke) + ); + } + function _deployNewSpoke() internal returns (ISpoke, IAaveOracle) { vm.startPrank(ADMIN); TestTypes.TestSpokeReport memory report = AaveV4TestOrchestration.deployTestSpoke( @@ -342,17 +389,8 @@ abstract contract BaseConfigEngineTest is Test, Create2TestHelper { addCap: 0, proxyAdminOwner: address(0), name: '', - symbol: '' - }), - hubRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ - addressesProvider: IAddressesProvider(address(0)), - register: false, - name: '' - }), - tokenizationSpokeRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ - addressesProvider: IAddressesProvider(address(0)), - register: false, - name: '' + symbol: '', + registrationName: '' }) }); } @@ -484,12 +522,7 @@ abstract contract BaseConfigEngineTest is Test, Create2TestHelper { underlying: address(weth), priceSource: address(priceFeedWeth), config: _defaultReserveConfig(), - dynamicConfig: _defaultDynamicReserveConfig(), - spokeRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ - addressesProvider: IAddressesProvider(address(0)), - register: false, - name: '' - }) + dynamicConfig: _defaultDynamicReserveConfig() }); } @@ -536,6 +569,13 @@ abstract contract BaseConfigEngineTest is Test, Create2TestHelper { assertEq(actual.halted, expected.halted); } + function _toAddressesProviderEntryUpdateArray( + IAaveV4ConfigEngine.AddressesProviderEntryUpdate memory item + ) internal pure returns (IAaveV4ConfigEngine.AddressesProviderEntryUpdate[] memory arr) { + arr = new IAaveV4ConfigEngine.AddressesProviderEntryUpdate[](1); + arr[0] = item; + } + function _toAssetConfigUpdateArray( IAaveV4ConfigEngine.AssetConfigUpdate memory item ) internal pure returns (IAaveV4ConfigEngine.AssetConfigUpdate[] memory arr) { diff --git a/tests/config-engine/EngineUtils.t.sol b/tests/config-engine/EngineUtils.t.sol index e4009b193..06fec3dce 100644 --- a/tests/config-engine/EngineUtils.t.sol +++ b/tests/config-engine/EngineUtils.t.sol @@ -3,81 +3,134 @@ pragma solidity ^0.8.0; import {Test} from 'forge-std/Test.sol'; -import {EngineUtils} from 'src/config-engine/libraries/EngineUtils.sol'; -import {IAaveV4ConfigEngine} from 'src/config-engine/interfaces/IAaveV4ConfigEngine.sol'; +import {TransparentUpgradeableProxy} from 'src/dependencies/openzeppelin/TransparentUpgradeableProxy.sol'; +import {AddressesProviderInstance} from 'src/addresses-provider/instances/AddressesProviderInstance.sol'; import {IAddressesProvider} from 'src/addresses-provider/interfaces/IAddressesProvider.sol'; +import {EngineUtils} from 'src/config-engine/libraries/EngineUtils.sol'; /// @dev Wrapper to call EngineUtils library functions externally. contract EngineUtilsHarness { - function isConsistentRegistration( - IAaveV4ConfigEngine.AddressesProviderRegistration calldata registration - ) external pure returns (bool) { - return EngineUtils.isConsistentRegistration(registration); + function requireRegisteredHub(IAddressesProvider addressesProvider, address hub) external view { + EngineUtils.requireRegisteredHub(addressesProvider, hub); + } + + function requireRegisteredSpoke( + IAddressesProvider addressesProvider, + address spoke + ) external view { + EngineUtils.requireRegisteredSpoke(addressesProvider, spoke); + } + + function requireRegisteredCanonicalSpoke( + IAddressesProvider addressesProvider, + address spoke + ) external view { + EngineUtils.requireRegisteredCanonicalSpoke(addressesProvider, spoke); } } contract EngineUtilsTest is Test { + address internal HUB = makeAddr('HUB'); + address internal SPOKE = makeAddr('SPOKE'); + EngineUtilsHarness internal _harness; + IAddressesProvider internal _provider; function setUp() public { _harness = new EngineUtilsHarness(); + _provider = IAddressesProvider( + address( + new TransparentUpgradeableProxy( + address(new AddressesProviderInstance()), + makeAddr('PROXY_ADMIN_OWNER'), + abi.encodeCall(AddressesProviderInstance.initialize, (address(this))) + ) + ) + ); } - function _registration( - address addressesProvider, - bool register, - string memory name - ) internal pure returns (IAaveV4ConfigEngine.AddressesProviderRegistration memory) { - return - IAaveV4ConfigEngine.AddressesProviderRegistration({ - addressesProvider: IAddressesProvider(addressesProvider), - register: register, - name: name - }); + function test_requireRegisteredHub() public { + _provider.setCanonicalHub('CORE', HUB); + _harness.requireRegisteredHub(_provider, HUB); } - function test_isConsistentRegistration_register_allFieldsSet() public view { - assertTrue(_harness.isConsistentRegistration(_registration(address(1), true, 'CORE'))); + function test_requireRegisteredHub_revertsWith_HubNotRegistered() public { + vm.expectRevert(abi.encodeWithSelector(EngineUtils.HubNotRegistered.selector, HUB)); + _harness.requireRegisteredHub(_provider, HUB); } - function test_isConsistentRegistration_register_noProvider() public view { - assertFalse(_harness.isConsistentRegistration(_registration(address(0), true, 'CORE'))); + function test_requireRegisteredHub_revertsWith_HubNotRegistered_otherTag() public { + _provider.setEntry({name: 'CORE', tag: 'PERIPHERY', newAddress: HUB}); + + vm.expectRevert(abi.encodeWithSelector(EngineUtils.HubNotRegistered.selector, HUB)); + _harness.requireRegisteredHub(_provider, HUB); } - function test_isConsistentRegistration_register_noName() public view { - assertFalse(_harness.isConsistentRegistration(_registration(address(1), true, ''))); + function test_requireRegisteredHub_revertsWith_HubNotRegistered_spokeTag() public { + _provider.setCanonicalSpoke('CORE', HUB); + + vm.expectRevert(abi.encodeWithSelector(EngineUtils.HubNotRegistered.selector, HUB)); + _harness.requireRegisteredHub(_provider, HUB); } - function test_isConsistentRegistration_register_allFieldsUnset() public view { - assertFalse(_harness.isConsistentRegistration(_registration(address(0), true, ''))); + function test_requireRegisteredSpoke_canonicalTag() public { + _provider.setCanonicalSpoke('MAIN', SPOKE); + _harness.requireRegisteredSpoke(_provider, SPOKE); } - function test_isConsistentRegistration_noRegister_allFieldsUnset() public view { - assertTrue(_harness.isConsistentRegistration(_registration(address(0), false, ''))); + function test_requireRegisteredSpoke_tokenizationTag() public { + _provider.setTokenizationSpoke('MAIN', SPOKE); + _harness.requireRegisteredSpoke(_provider, SPOKE); } - function test_isConsistentRegistration_noRegister_providerSet() public view { - assertFalse(_harness.isConsistentRegistration(_registration(address(1), false, ''))); + function test_requireRegisteredSpoke_treasuryTag() public { + _provider.setTreasurySpoke('MAIN', SPOKE); + _harness.requireRegisteredSpoke(_provider, SPOKE); } - function test_isConsistentRegistration_noRegister_nameSet() public view { - assertFalse(_harness.isConsistentRegistration(_registration(address(0), false, 'CORE'))); + function test_requireRegisteredSpoke_revertsWith_SpokeNotRegistered() public { + vm.expectRevert(abi.encodeWithSelector(EngineUtils.SpokeNotRegistered.selector, SPOKE)); + _harness.requireRegisteredSpoke(_provider, SPOKE); } - function test_isConsistentRegistration_noRegister_allFieldsSet() public view { - assertFalse(_harness.isConsistentRegistration(_registration(address(1), false, 'CORE'))); + function test_requireRegisteredSpoke_revertsWith_SpokeNotRegistered_hubTag() public { + _provider.setCanonicalHub('MAIN', SPOKE); + + vm.expectRevert(abi.encodeWithSelector(EngineUtils.SpokeNotRegistered.selector, SPOKE)); + _harness.requireRegisteredSpoke(_provider, SPOKE); } - function test_fuzz_isConsistentRegistration( - address addressesProvider, - bool register, - string memory name - ) public view { - bool fieldsSet = addressesProvider != address(0) && bytes(name).length > 0; - bool fieldsUnset = addressesProvider == address(0) && bytes(name).length == 0; - assertEq( - _harness.isConsistentRegistration(_registration(addressesProvider, register, name)), - register ? fieldsSet : fieldsUnset + function test_requireRegisteredCanonicalSpoke() public { + _provider.setCanonicalSpoke('MAIN', SPOKE); + _harness.requireRegisteredCanonicalSpoke(_provider, SPOKE); + } + + function test_requireRegisteredCanonicalSpoke_revertsWith_CanonicalSpokeNotRegistered() public { + vm.expectRevert( + abi.encodeWithSelector(EngineUtils.CanonicalSpokeNotRegistered.selector, SPOKE) + ); + _harness.requireRegisteredCanonicalSpoke(_provider, SPOKE); + } + + function test_requireRegisteredCanonicalSpoke_revertsWith_CanonicalSpokeNotRegistered_tokenizationTag() + public + { + _provider.setTokenizationSpoke('MAIN', SPOKE); + + vm.expectRevert( + abi.encodeWithSelector(EngineUtils.CanonicalSpokeNotRegistered.selector, SPOKE) + ); + _harness.requireRegisteredCanonicalSpoke(_provider, SPOKE); + } + + function test_requireRegisteredCanonicalSpoke_revertsWith_CanonicalSpokeNotRegistered_treasuryTag() + public + { + _provider.setTreasurySpoke('MAIN', SPOKE); + + vm.expectRevert( + abi.encodeWithSelector(EngineUtils.CanonicalSpokeNotRegistered.selector, SPOKE) ); + _harness.requireRegisteredCanonicalSpoke(_provider, SPOKE); } } diff --git a/tests/config-engine/GovernanceTopology.t.sol b/tests/config-engine/GovernanceTopology.t.sol index 5fb12064f..c604cd9fa 100644 --- a/tests/config-engine/GovernanceTopology.t.sol +++ b/tests/config-engine/GovernanceTopology.t.sol @@ -30,6 +30,12 @@ contract ConfigEngineGovernanceTopologyTest is BaseConfigEngineTest { // in production the Executor, not the payload or the engine, holds the configurator permissions vm.prank(ADMIN); accessManager.grantRole(Roles.HUB_CONFIGURATOR_DOMAIN_ADMIN_ROLE, address(executor), 0); + + // in production the Executor owns the AddressesProvider; provider writes run in its context + vm.prank(address(engine)); + AddressesProviderInstance(address(addressesProvider)).transferOwnership(address(executor)); + vm.prank(address(executor)); + AddressesProviderInstance(address(addressesProvider)).acceptOwnership(); } function _executePayload(address target) internal { diff --git a/tests/config-engine/HubEngine.t.sol b/tests/config-engine/HubEngine.t.sol index 5d6194d96..63f8250dd 100644 --- a/tests/config-engine/HubEngine.t.sol +++ b/tests/config-engine/HubEngine.t.sol @@ -600,6 +600,7 @@ contract HubEngineTest is BaseConfigEngineTest { function test_executeHubSpokeToAssetsAdditions() public { (ISpoke newSpoke, ) = _deployNewSpoke(); + _registerSpokeOnProvider(newSpoke); IAaveV4ConfigEngine.SpokeAssetConfig[] memory assets = new IAaveV4ConfigEngine.SpokeAssetConfig[](2); @@ -762,7 +763,8 @@ contract HubEngineTest is BaseConfigEngineTest { addCap: 1000, proxyAdminOwner: PROXY_ADMIN_OWNER, name: 'Tokenized NEW', - symbol: 'tNEW' + symbol: 'tNEW', + registrationName: 'TOKENIZED_NEW' }); uint256 assetCountBefore = hub1().getAssetCount(); @@ -821,7 +823,8 @@ contract HubEngineTest is BaseConfigEngineTest { addCap: 1000, proxyAdminOwner: PROXY_ADMIN_OWNER, name: 'Tokenized NEW', - symbol: 'tNEW' + symbol: 'tNEW', + registrationName: 'TOKENIZED_NEW' }); address predictedProxy = TokenizationSpokeDeployer.computeProxyAddress( @@ -846,7 +849,8 @@ contract HubEngineTest is BaseConfigEngineTest { addCap: 1000, proxyAdminOwner: PROXY_ADMIN_OWNER, name: '', - symbol: 'tNEW' + symbol: 'tNEW', + registrationName: 'TOKENIZED_NEW' }); vm.expectRevert(HubEngine.InvalidTokenizationSpokeConfig.selector); @@ -860,7 +864,8 @@ contract HubEngineTest is BaseConfigEngineTest { addCap: 1000, proxyAdminOwner: PROXY_ADMIN_OWNER, name: 'Tokenized NEW', - symbol: '' + symbol: '', + registrationName: 'TOKENIZED_NEW' }); vm.expectRevert(HubEngine.InvalidTokenizationSpokeConfig.selector); @@ -874,7 +879,8 @@ contract HubEngineTest is BaseConfigEngineTest { addCap: 1000, proxyAdminOwner: address(0), name: '', - symbol: '' + symbol: '', + registrationName: '' }); vm.expectRevert(HubEngine.InvalidTokenizationSpokeConfig.selector); @@ -888,7 +894,8 @@ contract HubEngineTest is BaseConfigEngineTest { addCap: 1000, proxyAdminOwner: address(0), name: 'Tokenized NEW', - symbol: 'tNEW' + symbol: 'tNEW', + registrationName: 'TOKENIZED_NEW' }); vm.expectRevert(HubEngine.InvalidTokenizationSpokeConfig.selector); @@ -902,7 +909,8 @@ contract HubEngineTest is BaseConfigEngineTest { addCap: 0, proxyAdminOwner: PROXY_ADMIN_OWNER, name: '', - symbol: '' + symbol: '', + registrationName: '' }); vm.expectRevert(HubEngine.InvalidTokenizationSpokeConfig.selector); @@ -916,7 +924,8 @@ contract HubEngineTest is BaseConfigEngineTest { addCap: 0, proxyAdminOwner: PROXY_ADMIN_OWNER, name: 'Tokenized NEW', - symbol: 'tNEW' + symbol: 'tNEW', + registrationName: 'TOKENIZED_NEW' }); uint256 assetCountBefore = hub1().getAssetCount(); @@ -1165,7 +1174,8 @@ contract HubEngineTest is BaseConfigEngineTest { addCap: 1000, proxyAdminOwner: PROXY_ADMIN_OWNER, name: 'Tokenized NEW', - symbol: 'tNEW' + symbol: 'tNEW', + registrationName: 'TOKENIZED_NEW' }); engine.executeHubAssetListings(_toAssetListingArray(listing)); diff --git a/tests/config-engine/SpokeEngine.t.sol b/tests/config-engine/SpokeEngine.t.sol index 4d9131eb7..abfb1f8c7 100644 --- a/tests/config-engine/SpokeEngine.t.sol +++ b/tests/config-engine/SpokeEngine.t.sol @@ -632,11 +632,6 @@ contract SpokeEngineTest is BaseConfigEngineTest { collateralFactor: 80_00, maxLiquidationBonus: 105_00, liquidationFee: 2_00 - }), - spokeRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ - addressesProvider: IAddressesProvider(address(0)), - register: false, - name: '' }) }); @@ -930,12 +925,7 @@ contract SpokeEngineTest is BaseConfigEngineTest { underlying: address(tokenA), priceSource: priceFeedA, config: _defaultReserveConfig(), - dynamicConfig: _defaultDynamicReserveConfig(), - spokeRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ - addressesProvider: IAddressesProvider(address(0)), - register: false, - name: '' - }) + dynamicConfig: _defaultDynamicReserveConfig() }); listings[1] = IAaveV4ConfigEngine.ReserveListing({ @@ -945,12 +935,7 @@ contract SpokeEngineTest is BaseConfigEngineTest { underlying: address(tokenB), priceSource: priceFeedB, config: _defaultReserveConfig(), - dynamicConfig: _defaultDynamicReserveConfig(), - spokeRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ - addressesProvider: IAddressesProvider(address(0)), - register: false, - name: '' - }) + dynamicConfig: _defaultDynamicReserveConfig() }); engine.executeSpokeReserveListings(listings); diff --git a/tests/contracts/addresses-provider/AddressesProvider.t.sol b/tests/contracts/addresses-provider/AddressesProvider.t.sol index 2922e4644..a4fac1fe8 100644 --- a/tests/contracts/addresses-provider/AddressesProvider.t.sol +++ b/tests/contracts/addresses-provider/AddressesProvider.t.sol @@ -312,6 +312,43 @@ contract AddressesProviderTest is Test { }); } + function test_isRegistered() public { + address configEngine = makeAddr('CONFIG_ENGINE'); + + assertFalse(provider.isRegistered(configEngine, 'PERIPHERY')); + + vm.prank(OWNER); + provider.setEntry({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: configEngine}); + + assertTrue(provider.isRegistered(configEngine, 'PERIPHERY')); + assertFalse(provider.isRegistered(configEngine, 'MISC')); + assertFalse(provider.isRegistered(makeAddr('OTHER'), 'PERIPHERY')); + } + + function test_isRegistered_multipleTags() public { + address spoke = makeAddr('SPOKE'); + + vm.startPrank(OWNER); + provider.setCanonicalSpoke('MAIN', spoke); + provider.setEntry({name: 'MAIN', tag: 'BABYLON', newAddress: spoke}); + vm.stopPrank(); + + assertTrue(provider.isRegistered(spoke, provider.CANONICAL_SPOKE_TAG())); + assertTrue(provider.isRegistered(spoke, 'BABYLON')); + assertFalse(provider.isRegistered(spoke, provider.CANONICAL_HUB_TAG())); + } + + function test_isRegistered_afterRemove() public { + address configEngine = makeAddr('CONFIG_ENGINE'); + + vm.startPrank(OWNER); + provider.setEntry({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: configEngine}); + provider.setEntry({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); + vm.stopPrank(); + + assertFalse(provider.isRegistered(configEngine, 'PERIPHERY')); + } + function test_setCanonicalHub() public { address coreHub = makeAddr('CORE_HUB'); address plusHub = makeAddr('PLUS_HUB'); diff --git a/tests/helpers/mocks/config-engine/AaveV4PayloadWrapper.sol b/tests/helpers/mocks/config-engine/AaveV4PayloadWrapper.sol index 32854a2f5..955c7525d 100644 --- a/tests/helpers/mocks/config-engine/AaveV4PayloadWrapper.sol +++ b/tests/helpers/mocks/config-engine/AaveV4PayloadWrapper.sol @@ -14,6 +14,9 @@ contract AaveV4PayloadWrapper is AaveV4Payload { uint256 public postExecuteOrder; uint256 private _callCounter; + // AddressesProvider action storage + IAaveV4ConfigEngine.AddressesProviderEntryUpdate[] private _addressesProviderEntryUpdates; + // Hub action storage IAaveV4ConfigEngine.AssetListing[] private _hubAssetListings; IAaveV4ConfigEngine.AssetConfigUpdate[] private _hubAssetConfigUpdates; @@ -56,6 +59,16 @@ contract AaveV4PayloadWrapper is AaveV4Payload { postExecuteOrder = ++_callCounter; } + // AddressesProvider setters + function setAddressesProviderEntryUpdates( + IAaveV4ConfigEngine.AddressesProviderEntryUpdate[] memory items + ) external { + delete _addressesProviderEntryUpdates; + for (uint256 i = 0; i < items.length; i++) { + _addressesProviderEntryUpdates.push(items[i]); + } + } + // Hub setters function setHubAssetListings(IAaveV4ConfigEngine.AssetListing[] memory items) external { delete _hubAssetListings; @@ -223,6 +236,15 @@ contract AaveV4PayloadWrapper is AaveV4Payload { } } + function addressesProviderEntryUpdates() + public + view + override + returns (IAaveV4ConfigEngine.AddressesProviderEntryUpdate[] memory) + { + return _addressesProviderEntryUpdates; + } + function hubAssetListings() public view diff --git a/tests/helpers/mocks/config-engine/MockTokenizationListingPayload.sol b/tests/helpers/mocks/config-engine/MockTokenizationListingPayload.sol index 5d6b4dbfe..656c1f671 100644 --- a/tests/helpers/mocks/config-engine/MockTokenizationListingPayload.sol +++ b/tests/helpers/mocks/config-engine/MockTokenizationListingPayload.sol @@ -5,7 +5,6 @@ import {AaveV4Payload} from 'src/config-engine/AaveV4Payload.sol'; import {IAaveV4ConfigEngine} from 'src/config-engine/interfaces/IAaveV4ConfigEngine.sol'; import {IHubConfigurator} from 'src/hub/interfaces/IHubConfigurator.sol'; import {IAssetInterestRateStrategy} from 'src/hub/interfaces/IAssetInterestRateStrategy.sol'; -import {IAddressesProvider} from 'src/addresses-provider/interfaces/IAddressesProvider.sol'; /// @dev Production-style payload: all action data lives in immutables or literals. `execute()` /// runs via delegatecall inside the Executor, so payload storage is not readable at execution time. @@ -58,17 +57,8 @@ contract MockTokenizationListingPayload is AaveV4Payload { addCap: 1000, proxyAdminOwner: PROXY_ADMIN_OWNER, name: 'Tokenized NEW', - symbol: 'tNEW' - }), - hubRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ - addressesProvider: IAddressesProvider(address(0)), - register: false, - name: '' - }), - tokenizationSpokeRegistration: IAaveV4ConfigEngine.AddressesProviderRegistration({ - addressesProvider: IAddressesProvider(address(0)), - register: false, - name: '' + symbol: 'tNEW', + registrationName: 'TOKENIZED_NEW' }) }); return listings; From efa03cccbc99038aeb746aa9d7122d2cf222fb4d Mon Sep 17 00:00:00 2001 From: Alexandru Niculae <43644109+avniculae@users.noreply.github.com> Date: Thu, 6 Aug 2026 18:13:28 +0300 Subject: [PATCH 12/15] docs: document the registration requirement on every engine action --- src/config-engine/interfaces/IAaveV4ConfigEngine.sol | 2 ++ src/config-engine/libraries/HubEngine.sol | 4 ++++ src/config-engine/libraries/SpokeEngine.sol | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/src/config-engine/interfaces/IAaveV4ConfigEngine.sol b/src/config-engine/interfaces/IAaveV4ConfigEngine.sol index cca22c67b..109710234 100644 --- a/src/config-engine/interfaces/IAaveV4ConfigEngine.sol +++ b/src/config-engine/interfaces/IAaveV4ConfigEngine.sol @@ -14,6 +14,8 @@ import {IAddressesProvider} from 'src/addresses-provider/interfaces/IAddressesPr /// The engine is stateless and invoked via delegatecall from payload contracts. /// All numeric fields in config structs use uint256 so that type(uint256).max can serve as /// the universal KEEP_CURRENT sentinel. Boolean fields use uint256 (0=false, 1=true, KEEP_CURRENT=skip). +/// Hub and Spoke actions revert when the targeted Hub or Spoke is not registered on the +/// AddressesProvider; entries are managed via `executeAddressesProviderEntryUpdates`. interface IAaveV4ConfigEngine { /// @notice Parameters for tokenization of an asset on a Hub when listing the asset. /// @dev Tokenization is skipped only when all fields are unset. Otherwise `name`, `symbol`, diff --git a/src/config-engine/libraries/HubEngine.sol b/src/config-engine/libraries/HubEngine.sol index 69bd965d5..e80bb8e40 100644 --- a/src/config-engine/libraries/HubEngine.sol +++ b/src/config-engine/libraries/HubEngine.sol @@ -60,6 +60,7 @@ library HubEngine { /// IR: strategy set → updateInterestRateStrategy; strategy kept + non-sentinel irData fields → /// read-modify-write via updateInterestRateData. /// Reinvestment: address set → updateReinvestmentController. + /// @dev The Hub must be registered on the AddressesProvider as a canonical Hub. /// @param updates The asset config updates to execute. /// @param addressesProvider The AddressesProvider authorizing the actions. function executeHubAssetConfigUpdates( @@ -187,6 +188,7 @@ library HubEngine { } /// @notice Halts assets on Hubs. + /// @dev The Hub must be registered on the AddressesProvider as a canonical Hub. /// @param halts The asset halts to execute. /// @param addressesProvider The AddressesProvider authorizing the actions. function executeHubAssetHalts( @@ -203,6 +205,7 @@ library HubEngine { } /// @notice Deactivates assets on Hubs. + /// @dev The Hub must be registered on the AddressesProvider as a canonical Hub. /// @param deactivations The asset deactivations to execute. /// @param addressesProvider The AddressesProvider authorizing the actions. function executeHubAssetDeactivations( @@ -219,6 +222,7 @@ library HubEngine { } /// @notice Resets asset caps on Hubs. + /// @dev The Hub must be registered on the AddressesProvider as a canonical Hub. /// @param resets The asset caps resets to execute. /// @param addressesProvider The AddressesProvider authorizing the actions. function executeHubAssetCapsResets( diff --git a/src/config-engine/libraries/SpokeEngine.sol b/src/config-engine/libraries/SpokeEngine.sol index c9dff3ba0..e49f00154 100644 --- a/src/config-engine/libraries/SpokeEngine.sol +++ b/src/config-engine/libraries/SpokeEngine.sol @@ -40,6 +40,7 @@ library SpokeEngine { } /// @notice Updates reserve config on Spokes. + /// @dev The Spoke must be registered on the AddressesProvider as a canonical Spoke. /// @param updates The reserve config updates to execute. /// @param addressesProvider The AddressesProvider authorizing the actions. function executeSpokeReserveConfigUpdates( @@ -105,6 +106,7 @@ library SpokeEngine { /// @dev If all three fields (targetHealthFactor, healthFactorForMaxBonus, liquidationBonusFactor) /// are set, calls updateLiquidationConfig with the full struct. Otherwise, each non-KEEP_CURRENT /// field is updated individually via its dedicated setter. If no field is set, the update is skipped. + /// @dev The Spoke must be registered on the AddressesProvider as a canonical Spoke. /// @param updates The liquidation config updates to execute. /// @param addressesProvider The AddressesProvider authorizing the actions. function executeSpokeLiquidationConfigUpdates( @@ -152,6 +154,7 @@ library SpokeEngine { } /// @notice Adds dynamic reserve configs on Spokes. + /// @dev The Spoke must be registered on the AddressesProvider as a canonical Spoke. /// @param additions The dynamic reserve config additions to execute. /// @param addressesProvider The AddressesProvider authorizing the actions. function executeSpokeDynamicReserveConfigAdditions( @@ -178,6 +181,7 @@ library SpokeEngine { /// @notice Updates dynamic reserve configs on Spokes. /// @dev Reads the current config, applies only the fields that differ from KEEP_CURRENT, /// and writes back. If no field is modified the external call is skipped entirely. + /// @dev The Spoke must be registered on the AddressesProvider as a canonical Spoke. /// @param updates The dynamic reserve config updates to execute. /// @param addressesProvider The AddressesProvider authorizing the actions. function executeSpokeDynamicReserveConfigUpdates( @@ -225,6 +229,7 @@ library SpokeEngine { } /// @notice Updates position managers on Spokes. + /// @dev The Spoke must be registered on the AddressesProvider as a canonical Spoke. /// @param updates The position manager updates to execute on Spokes. /// @param addressesProvider The AddressesProvider authorizing the actions. function executeSpokePositionManagerUpdates( From e7c9097de478c933734d72105c51ad325052e709 Mon Sep 17 00:00:00 2001 From: Alexandru Niculae <43644109+avniculae@users.noreply.github.com> Date: Fri, 7 Aug 2026 15:03:36 +0300 Subject: [PATCH 13/15] perf: make isRegistered a constant-time lookup Maintain a per-address tag entry count in _setEntry so isRegistered reads a single mapping slot instead of walking the address's entries, keeping the engine's typed registration checks cheap regardless of how many entries an address has. --- src/addresses-provider/AddressesProvider.sol | 13 ++++--------- .../AddressesProviderStorage.sol | 3 +++ .../addresses-provider/AddressesProvider.t.sol | 15 +++++++++++++++ 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/addresses-provider/AddressesProvider.sol b/src/addresses-provider/AddressesProvider.sol index 0f4e4d8d0..4394f4f99 100644 --- a/src/addresses-provider/AddressesProvider.sol +++ b/src/addresses-provider/AddressesProvider.sol @@ -158,14 +158,7 @@ abstract contract AddressesProvider is /// @inheritdoc IAddressesProvider function isRegistered(address addr, string calldata tag) external view returns (bool) { - bytes32 tagHash = keccak256(bytes(tag)); - bytes32[] memory ids = _addressToIdSet[addr].values(); - for (uint256 i = 0; i < ids.length; i++) { - if (keccak256(bytes(_idToEntry[ids[i]].tag)) == tagHash) { - return true; - } - } - return false; + return _addressToTagCount[addr][keccak256(bytes(tag))] > 0; } /// @inheritdoc IAddressesProvider @@ -250,13 +243,15 @@ abstract contract AddressesProvider is _tagsSet.remove(oldEntry.tag); } _addressToIdSet[oldEntry.addr].remove(id); + _addressToTagCount[oldEntry.addr][keccak256(bytes(oldEntry.tag))]--; delete _idToEntry[id]; } else { require(oldEntry.addr == address(0), AddressAlreadySet(id)); - _idToEntry[id] = Entry({addr: newAddress, name: name, tag: tag}); + _idToEntry[id] = Entry({name: name, tag: tag, addr: newAddress}); _tagToIdSet[tag].add(id); _tagsSet.add(tag); _addressToIdSet[newAddress].add(id); + _addressToTagCount[newAddress][keccak256(bytes(tag))]++; } emit SetEntry(id, name, tag, oldEntry.addr, newAddress); diff --git a/src/addresses-provider/AddressesProviderStorage.sol b/src/addresses-provider/AddressesProviderStorage.sol index 021755210..effd0ebd2 100644 --- a/src/addresses-provider/AddressesProviderStorage.sol +++ b/src/addresses-provider/AddressesProviderStorage.sol @@ -23,6 +23,9 @@ abstract contract AddressesProviderStorage { /// @dev An address may be registered under more than one entry. mapping(address addr => EnumerableSet.Bytes32Set) internal _addressToIdSet; + /// @dev Map of registered addresses and tag hashes to their respective number of entries. + mapping(address addr => mapping(bytes32 tagHash => uint256 count)) internal _addressToTagCount; + /// @dev Reserved storage space to allow for future layout updates. uint256[50] private __gap; } diff --git a/tests/contracts/addresses-provider/AddressesProvider.t.sol b/tests/contracts/addresses-provider/AddressesProvider.t.sol index a4fac1fe8..a992de393 100644 --- a/tests/contracts/addresses-provider/AddressesProvider.t.sol +++ b/tests/contracts/addresses-provider/AddressesProvider.t.sol @@ -349,6 +349,21 @@ contract AddressesProviderTest is Test { assertFalse(provider.isRegistered(configEngine, 'PERIPHERY')); } + function test_isRegistered_multipleEntriesSameTag() public { + address configEngine = makeAddr('CONFIG_ENGINE'); + + vm.startPrank(OWNER); + provider.setEntry({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: configEngine}); + provider.setEntry({name: 'ENGINE', tag: 'PERIPHERY', newAddress: configEngine}); + + provider.setEntry({name: 'CONFIG_ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); + assertTrue(provider.isRegistered(configEngine, 'PERIPHERY')); + + provider.setEntry({name: 'ENGINE', tag: 'PERIPHERY', newAddress: address(0)}); + assertFalse(provider.isRegistered(configEngine, 'PERIPHERY')); + vm.stopPrank(); + } + function test_setCanonicalHub() public { address coreHub = makeAddr('CORE_HUB'); address plusHub = makeAddr('PLUS_HUB'); From b9847b49f136ec05310f0fc346fed112143b4542 Mon Sep 17 00:00:00 2001 From: Alexandru Niculae <43644109+avniculae@users.noreply.github.com> Date: Mon, 10 Aug 2026 14:39:22 +0300 Subject: [PATCH 14/15] chore: update gas snapshots for shifted signature digests Only *WithSig operations moved: test env address changes altered the EIP-712 digests and therefore the signature calldata encoding. Non-sig operations are unchanged. --- snapshots/SignatureGateway.Operations.json | 8 ++++---- snapshots/Spoke.Operations.ZeroRiskPremium.json | 2 +- snapshots/Spoke.Operations.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/snapshots/SignatureGateway.Operations.json b/snapshots/SignatureGateway.Operations.json index 93a4414b0..0d6756c1d 100644 --- a/snapshots/SignatureGateway.Operations.json +++ b/snapshots/SignatureGateway.Operations.json @@ -1,10 +1,10 @@ { - "borrowWithSig": "222144", - "repayWithSig": "192513", + "borrowWithSig": "222132", + "repayWithSig": "192501", "setSelfAsUserPositionManagerWithSig": "75138", "setUsingAsCollateralWithSig": "85380", - "supplyWithSig": "155914", + "supplyWithSig": "155904", "updateUserDynamicConfigWithSig": "63113", - "updateUserRiskPremiumWithSig": "61995", + "updateUserRiskPremiumWithSig": "62007", "withdrawWithSig": "135124" } \ No newline at end of file diff --git a/snapshots/Spoke.Operations.ZeroRiskPremium.json b/snapshots/Spoke.Operations.ZeroRiskPremium.json index fcf844086..f2db1de4b 100644 --- a/snapshots/Spoke.Operations.ZeroRiskPremium.json +++ b/snapshots/Spoke.Operations.ZeroRiskPremium.json @@ -12,7 +12,7 @@ "repay: full": "129276", "repay: partial": "134234", "setUserPositionManagersWithSig: disable": "46772", - "setUserPositionManagersWithSig: enable": "68684", + "setUserPositionManagersWithSig: enable": "68672", "supply + enable collateral (multicall)": "146316", "supply: 0 borrows, collateral disabled": "127753", "supply: 0 borrows, collateral enabled": "110724", diff --git a/snapshots/Spoke.Operations.json b/snapshots/Spoke.Operations.json index 086bec26b..c66dbf7b7 100644 --- a/snapshots/Spoke.Operations.json +++ b/snapshots/Spoke.Operations.json @@ -12,7 +12,7 @@ "repay: full": "123355", "repay: partial": "142713", "setUserPositionManagersWithSig: disable": "46772", - "setUserPositionManagersWithSig: enable": "68684", + "setUserPositionManagersWithSig: enable": "68672", "supply + enable collateral (multicall)": "146316", "supply: 0 borrows, collateral disabled": "127753", "supply: 0 borrows, collateral enabled": "110724", From f369c9a99b9aedd0d0f816dfbf5e759d8d509720 Mon Sep 17 00:00:00 2001 From: Alexandru Niculae <43644109+avniculae@users.noreply.github.com> Date: Mon, 10 Aug 2026 14:39:22 +0300 Subject: [PATCH 15/15] refactor: drop convenience functions from AddressesProvider Remove the typed canonical/tokenization/treasury setters and getters and the unbounded full-array getters. Consumers use setEntry and the general count/range getters, with the public tag constants identifying the canonical tags. The engine registers the deployed TokenizationSpoke via setEntry under TOKENIZATION_SPOKE_TAG. --- src/addresses-provider/AddressesProvider.sol | 108 ---- .../interfaces/IAddressesProvider.sol | 118 ---- src/config-engine/libraries/HubEngine.sol | 6 +- tests/config-engine/AaveV4Payload.t.sol | 5 +- .../AddressesProviderRegistration.t.sol | 18 +- tests/config-engine/BaseConfigEngine.t.sol | 28 +- tests/config-engine/EngineUtils.t.sol | 18 +- .../AddressesProvider.t.sol | 508 +++--------------- 8 files changed, 132 insertions(+), 677 deletions(-) diff --git a/src/addresses-provider/AddressesProvider.sol b/src/addresses-provider/AddressesProvider.sol index 4394f4f99..014d8dd72 100644 --- a/src/addresses-provider/AddressesProvider.sol +++ b/src/addresses-provider/AddressesProvider.sol @@ -40,26 +40,6 @@ abstract contract AddressesProvider is _setEntry({name: name, tag: tag, newAddress: newAddress}); } - /// @inheritdoc IAddressesProvider - function setCanonicalHub(string calldata name, address hub) external onlyOwner { - _setEntry({name: name, tag: CANONICAL_HUB_TAG, newAddress: hub}); - } - - /// @inheritdoc IAddressesProvider - function setCanonicalSpoke(string calldata name, address spoke) external onlyOwner { - _setEntry({name: name, tag: CANONICAL_SPOKE_TAG, newAddress: spoke}); - } - - /// @inheritdoc IAddressesProvider - function setTokenizationSpoke(string calldata name, address spoke) external onlyOwner { - _setEntry({name: name, tag: TOKENIZATION_SPOKE_TAG, newAddress: spoke}); - } - - /// @inheritdoc IAddressesProvider - function setTreasurySpoke(string calldata name, address spoke) external onlyOwner { - _setEntry({name: name, tag: TREASURY_SPOKE_TAG, newAddress: spoke}); - } - /// @inheritdoc IAddressesProvider function getAddress(bytes32 id) external view returns (address) { return _idToEntry[id].addr; @@ -80,11 +60,6 @@ abstract contract AddressesProvider is return _tagsSet.length(); } - /// @inheritdoc IAddressesProvider - function getTags() external view returns (string[] memory) { - return _tagsSet.values(); - } - /// @inheritdoc IAddressesProvider function getTags(uint256 start, uint256 end) external view returns (string[] memory) { return _tagsSet.values(start, end); @@ -95,11 +70,6 @@ abstract contract AddressesProvider is return _tagToIdSet[tag].length(); } - /// @inheritdoc IAddressesProvider - function getIds(string calldata tag) external view returns (bytes32[] memory) { - return _tagToIdSet[tag].values(); - } - /// @inheritdoc IAddressesProvider function getIds( string calldata tag, @@ -109,11 +79,6 @@ abstract contract AddressesProvider is return _tagToIdSet[tag].values(start, end); } - /// @inheritdoc IAddressesProvider - function getAddresses(string calldata tag) external view returns (address[] memory) { - return _toAddresses(_tagToIdSet[tag].values()); - } - /// @inheritdoc IAddressesProvider function getAddresses( string calldata tag, @@ -128,11 +93,6 @@ abstract contract AddressesProvider is return _addressToIdSet[addr].length(); } - /// @inheritdoc IAddressesProvider - function getAddressIds(address addr) external view returns (bytes32[] memory) { - return _addressToIdSet[addr].values(); - } - /// @inheritdoc IAddressesProvider function getAddressIds( address addr, @@ -142,11 +102,6 @@ abstract contract AddressesProvider is return _addressToIdSet[addr].values(start, end); } - /// @inheritdoc IAddressesProvider - function getEntries(address addr) external view returns (Entry[] memory) { - return _toEntries(_addressToIdSet[addr].values()); - } - /// @inheritdoc IAddressesProvider function getEntries( address addr, @@ -161,69 +116,6 @@ abstract contract AddressesProvider is return _addressToTagCount[addr][keccak256(bytes(tag))] > 0; } - /// @inheritdoc IAddressesProvider - function getCanonicalHub(string calldata name) external view returns (address) { - return _getAddress({name: name, tag: CANONICAL_HUB_TAG}); - } - - /// @inheritdoc IAddressesProvider - function getCanonicalHubs() external view returns (address[] memory) { - return _toAddresses(_tagToIdSet[CANONICAL_HUB_TAG].values()); - } - - /// @inheritdoc IAddressesProvider - function getCanonicalHubs(uint256 start, uint256 end) external view returns (address[] memory) { - return _toAddresses(_tagToIdSet[CANONICAL_HUB_TAG].values(start, end)); - } - - /// @inheritdoc IAddressesProvider - function getCanonicalSpoke(string calldata name) external view returns (address) { - return _getAddress({name: name, tag: CANONICAL_SPOKE_TAG}); - } - - /// @inheritdoc IAddressesProvider - function getCanonicalSpokes() external view returns (address[] memory) { - return _toAddresses(_tagToIdSet[CANONICAL_SPOKE_TAG].values()); - } - - /// @inheritdoc IAddressesProvider - function getCanonicalSpokes(uint256 start, uint256 end) external view returns (address[] memory) { - return _toAddresses(_tagToIdSet[CANONICAL_SPOKE_TAG].values(start, end)); - } - - /// @inheritdoc IAddressesProvider - function getTokenizationSpoke(string calldata name) external view returns (address) { - return _getAddress({name: name, tag: TOKENIZATION_SPOKE_TAG}); - } - - /// @inheritdoc IAddressesProvider - function getTokenizationSpokes() external view returns (address[] memory) { - return _toAddresses(_tagToIdSet[TOKENIZATION_SPOKE_TAG].values()); - } - - /// @inheritdoc IAddressesProvider - function getTokenizationSpokes( - uint256 start, - uint256 end - ) external view returns (address[] memory) { - return _toAddresses(_tagToIdSet[TOKENIZATION_SPOKE_TAG].values(start, end)); - } - - /// @inheritdoc IAddressesProvider - function getTreasurySpoke(string calldata name) external view returns (address) { - return _getAddress({name: name, tag: TREASURY_SPOKE_TAG}); - } - - /// @inheritdoc IAddressesProvider - function getTreasurySpokes() external view returns (address[] memory) { - return _toAddresses(_tagToIdSet[TREASURY_SPOKE_TAG].values()); - } - - /// @inheritdoc IAddressesProvider - function getTreasurySpokes(uint256 start, uint256 end) external view returns (address[] memory) { - return _toAddresses(_tagToIdSet[TREASURY_SPOKE_TAG].values(start, end)); - } - /// @inheritdoc IAddressesProvider function getId(string calldata name, string calldata tag) external pure returns (bytes32) { return _getId({name: name, tag: tag}); diff --git a/src/addresses-provider/interfaces/IAddressesProvider.sol b/src/addresses-provider/interfaces/IAddressesProvider.sol index 10a64b729..d79e10986 100644 --- a/src/addresses-provider/interfaces/IAddressesProvider.sol +++ b/src/addresses-provider/interfaces/IAddressesProvider.sol @@ -49,34 +49,6 @@ interface IAddressesProvider { /// @param newAddress The address to associate with the name and tag. function setEntry(string calldata name, string calldata tag, address newAddress) external; - /// @notice Registers the canonical Hub associated with a name. - /// @dev Registering the zero address removes the entry and its identifier from enumeration, it reverts if no address is registered. - /// @dev Reverts if an address is already registered under the identifier, it must be removed first. - /// @param name The name of the Hub. - /// @param hub The address of the Hub. - function setCanonicalHub(string calldata name, address hub) external; - - /// @notice Registers the canonical Spoke associated with a name. - /// @dev Registering the zero address removes the entry and its identifier from enumeration, it reverts if no address is registered. - /// @dev Reverts if an address is already registered under the identifier, it must be removed first. - /// @param name The name of the Spoke. - /// @param spoke The address of the Spoke. - function setCanonicalSpoke(string calldata name, address spoke) external; - - /// @notice Registers the tokenization Spoke associated with a name. - /// @dev Registering the zero address removes the entry and its identifier from enumeration, it reverts if no address is registered. - /// @dev Reverts if an address is already registered under the identifier, it must be removed first. - /// @param name The name of the Spoke. - /// @param spoke The address of the Spoke. - function setTokenizationSpoke(string calldata name, address spoke) external; - - /// @notice Registers the treasury Spoke associated with a name. - /// @dev Registering the zero address removes the entry and its identifier from enumeration, it reverts if no address is registered. - /// @dev Reverts if an address is already registered under the identifier, it must be removed first. - /// @param name The name of the Spoke. - /// @param spoke The address of the Spoke. - function setTreasurySpoke(string calldata name, address spoke) external; - /// @notice Returns the address associated with an identifier. /// @param id The identifier of the entry. /// @return The address of the entry, the zero address if none is registered. @@ -96,9 +68,6 @@ interface IAddressesProvider { /// @notice Returns the number of tags with at least one registered entry. function getTagCount() external view returns (uint256); - /// @notice Returns all tags with at least one registered entry. - function getTags() external view returns (string[] memory); - /// @notice Returns a slice of the tags with at least one registered entry. /// @dev Out-of-range bounds are clamped to the number of tags, it does not revert. /// @param start The start index of the slice. @@ -111,11 +80,6 @@ interface IAddressesProvider { /// @return The number of entries. function getIdCount(string calldata tag) external view returns (uint256); - /// @notice Returns the identifiers of all entries grouped under a tag. - /// @param tag The tag grouping the entries. - /// @return The list of identifiers. - function getIds(string calldata tag) external view returns (bytes32[] memory); - /// @notice Returns a slice of the identifiers of the entries grouped under a tag. /// @dev Out-of-range bounds are clamped to the number of entries, it does not revert. /// @param tag The tag grouping the entries. @@ -128,11 +92,6 @@ interface IAddressesProvider { uint256 end ) external view returns (bytes32[] memory); - /// @notice Returns the addresses of all entries grouped under a tag. - /// @param tag The tag grouping the entries. - /// @return The list of addresses. - function getAddresses(string calldata tag) external view returns (address[] memory); - /// @notice Returns a slice of the addresses of the entries grouped under a tag. /// @dev Out-of-range bounds are clamped to the number of entries, it does not revert. /// @param tag The tag grouping the entries. @@ -150,11 +109,6 @@ interface IAddressesProvider { /// @return The number of entries. function getAddressIdCount(address addr) external view returns (uint256); - /// @notice Returns the identifiers of all entries registered for an address. - /// @param addr The registered address. - /// @return The list of identifiers. - function getAddressIds(address addr) external view returns (bytes32[] memory); - /// @notice Returns a slice of the identifiers of the entries registered for an address. /// @dev Out-of-range bounds are clamped to the number of entries, it does not revert. /// @param addr The registered address. @@ -167,11 +121,6 @@ interface IAddressesProvider { uint256 end ) external view returns (bytes32[] memory); - /// @notice Returns all entries registered for an address. - /// @param addr The registered address. - /// @return The list of entries. - function getEntries(address addr) external view returns (Entry[] memory); - /// @notice Returns a slice of the entries registered for an address. /// @dev Out-of-range bounds are clamped to the number of entries, it does not revert. /// @param addr The registered address. @@ -190,73 +139,6 @@ interface IAddressesProvider { /// @return True if at least one entry associates the address with the tag. function isRegistered(address addr, string calldata tag) external view returns (bool); - /// @notice Returns the canonical Hub associated with a name. - /// @param name The name of the Hub. - /// @return The address of the Hub, the zero address if none is registered. - function getCanonicalHub(string calldata name) external view returns (address); - - /// @notice Returns the addresses of all registered canonical Hubs. - /// @return The list of canonical Hub addresses. - function getCanonicalHubs() external view returns (address[] memory); - - /// @notice Returns a slice of the addresses of the registered canonical Hubs. - /// @dev Out-of-range bounds are clamped to the number of entries, it does not revert. - /// @param start The start index of the slice. - /// @param end The end index of the slice. - /// @return The list of canonical Hub addresses in the slice. - function getCanonicalHubs(uint256 start, uint256 end) external view returns (address[] memory); - - /// @notice Returns the canonical Spoke associated with a name. - /// @param name The name of the Spoke. - /// @return The address of the Spoke, the zero address if none is registered. - function getCanonicalSpoke(string calldata name) external view returns (address); - - /// @notice Returns the addresses of all registered canonical Spokes. - /// @return The list of canonical Spoke addresses. - function getCanonicalSpokes() external view returns (address[] memory); - - /// @notice Returns a slice of the addresses of the registered canonical Spokes. - /// @dev Out-of-range bounds are clamped to the number of entries, it does not revert. - /// @param start The start index of the slice. - /// @param end The end index of the slice. - /// @return The list of canonical Spoke addresses in the slice. - function getCanonicalSpokes(uint256 start, uint256 end) external view returns (address[] memory); - - /// @notice Returns the tokenization Spoke associated with a name. - /// @param name The name of the Spoke. - /// @return The address of the Spoke, the zero address if none is registered. - function getTokenizationSpoke(string calldata name) external view returns (address); - - /// @notice Returns the addresses of all registered tokenization Spokes. - /// @return The list of tokenization Spoke addresses. - function getTokenizationSpokes() external view returns (address[] memory); - - /// @notice Returns a slice of the addresses of the registered tokenization Spokes. - /// @dev Out-of-range bounds are clamped to the number of entries, it does not revert. - /// @param start The start index of the slice. - /// @param end The end index of the slice. - /// @return The list of tokenization Spoke addresses in the slice. - function getTokenizationSpokes( - uint256 start, - uint256 end - ) external view returns (address[] memory); - - /// @notice Returns the treasury Spoke associated with a name. - /// @param name The name of the Spoke. - /// @return The address of the Spoke, the zero address if none is registered. - function getTreasurySpoke(string calldata name) external view returns (address); - - /// @notice Returns the addresses of all registered treasury Spokes. - /// @return The list of treasury Spoke addresses. - function getTreasurySpokes() external view returns (address[] memory); - - /// @notice Returns a slice of the addresses of the registered treasury Spokes. - /// @dev Out-of-range bounds are clamped to the number of entries, it does not revert. - /// @param start The start index of the slice. - /// @param end The end index of the slice. - /// @return The list of treasury Spoke addresses in the slice. - function getTreasurySpokes(uint256 start, uint256 end) external view returns (address[] memory); - /// @notice Returns the tag grouping all canonical Hubs. function CANONICAL_HUB_TAG() external view returns (string memory); diff --git a/src/config-engine/libraries/HubEngine.sol b/src/config-engine/libraries/HubEngine.sol index e80bb8e40..2393f0032 100644 --- a/src/config-engine/libraries/HubEngine.sol +++ b/src/config-engine/libraries/HubEngine.sol @@ -312,7 +312,11 @@ library HubEngine { proxyAdminOwner: tokenization.proxyAdminOwner }); - addressesProvider.setTokenizationSpoke(tokenization.registrationName, proxy); + addressesProvider.setEntry( + tokenization.registrationName, + addressesProvider.TOKENIZATION_SPOKE_TAG(), + proxy + ); listing.hubConfigurator.addSpoke( listing.hub, diff --git a/tests/config-engine/AaveV4Payload.t.sol b/tests/config-engine/AaveV4Payload.t.sol index 75e01be86..d1ce9b522 100644 --- a/tests/config-engine/AaveV4Payload.t.sol +++ b/tests/config-engine/AaveV4Payload.t.sol @@ -673,7 +673,10 @@ contract AaveV4PayloadTest is BaseConfigEngineTest { payload.execute(); - assertEq(addressesProvider.getCanonicalSpoke('NEW'), address(newSpoke)); + assertEq( + addressesProvider.getAddress({name: 'NEW', tag: addressesProvider.CANONICAL_SPOKE_TAG()}), + address(newSpoke) + ); assertEq(newSpoke.getReserveCount(), 1); } diff --git a/tests/config-engine/AddressesProviderRegistration.t.sol b/tests/config-engine/AddressesProviderRegistration.t.sol index affd341ef..e4ee675c4 100644 --- a/tests/config-engine/AddressesProviderRegistration.t.sol +++ b/tests/config-engine/AddressesProviderRegistration.t.sol @@ -74,8 +74,14 @@ contract AddressesProviderRegistrationTest is BaseConfigEngineTest { engine.executeAddressesProviderEntryUpdates(updates); - assertEq(addressesProvider.getCanonicalHub('HUB_1'), address(0)); - assertEq(addressesProvider.getCanonicalHub('CORE'), address(hub1())); + assertEq( + addressesProvider.getAddress({name: 'HUB_1', tag: addressesProvider.CANONICAL_HUB_TAG()}), + address(0) + ); + assertEq( + addressesProvider.getAddress({name: 'CORE', tag: addressesProvider.CANONICAL_HUB_TAG()}), + address(hub1()) + ); } function test_executeAddressesProviderEntryUpdates_revertsWith_AddressAlreadySet() public { @@ -477,7 +483,13 @@ contract AddressesProviderRegistrationTest is BaseConfigEngineTest { engine.executeHubAssetListings(_toAssetListingArray(listing)); - assertEq(addressesProvider.getTokenizationSpoke('HUB1_WETH'), expectedProxy); + assertEq( + addressesProvider.getAddress({ + name: 'HUB1_WETH', + tag: addressesProvider.TOKENIZATION_SPOKE_TAG() + }), + expectedProxy + ); assertTrue( addressesProvider.isRegistered(expectedProxy, addressesProvider.TOKENIZATION_SPOKE_TAG()) ); diff --git a/tests/config-engine/BaseConfigEngine.t.sol b/tests/config-engine/BaseConfigEngine.t.sol index 30989c310..1838f47a4 100644 --- a/tests/config-engine/BaseConfigEngine.t.sol +++ b/tests/config-engine/BaseConfigEngine.t.sol @@ -218,16 +218,18 @@ abstract contract BaseConfigEngineTest is Test, Create2TestHelper { /// engine, which is the actor making the provider calls when tests invoke it directly. function _registerTestEnv() internal { for (uint256 i; i < NUM_HUBS; ++i) { - addressesProvider.setCanonicalHub( - string.concat('HUB_', vm.toString(i + 1)), - address(hubs[i]) - ); + addressesProvider.setEntry({ + name: string.concat('HUB_', vm.toString(i + 1)), + tag: addressesProvider.CANONICAL_HUB_TAG(), + newAddress: address(hubs[i]) + }); } for (uint256 i; i < NUM_SPOKES; ++i) { - addressesProvider.setCanonicalSpoke( - string.concat('SPOKE_', vm.toString(i + 1)), - address(spokes[i]) - ); + addressesProvider.setEntry({ + name: string.concat('SPOKE_', vm.toString(i + 1)), + tag: addressesProvider.CANONICAL_SPOKE_TAG(), + newAddress: address(spokes[i]) + }); } AddressesProviderInstance(address(addressesProvider)).transferOwnership(address(engine)); vm.prank(address(engine)); @@ -236,11 +238,13 @@ abstract contract BaseConfigEngineTest is Test, Create2TestHelper { /// @dev Registers a Spoke on the AddressesProvider as the engine, the provider owner after setUp. function _registerSpokeOnProvider(ISpoke spoke) internal { + string memory tag = addressesProvider.CANONICAL_SPOKE_TAG(); vm.prank(address(engine)); - addressesProvider.setCanonicalSpoke( - string.concat('SPOKE_', vm.toString(address(spoke))), - address(spoke) - ); + addressesProvider.setEntry({ + name: string.concat('SPOKE_', vm.toString(address(spoke))), + tag: tag, + newAddress: address(spoke) + }); } function _deployNewSpoke() internal returns (ISpoke, IAaveOracle) { diff --git a/tests/config-engine/EngineUtils.t.sol b/tests/config-engine/EngineUtils.t.sol index 06fec3dce..4948f2065 100644 --- a/tests/config-engine/EngineUtils.t.sol +++ b/tests/config-engine/EngineUtils.t.sol @@ -50,7 +50,7 @@ contract EngineUtilsTest is Test { } function test_requireRegisteredHub() public { - _provider.setCanonicalHub('CORE', HUB); + _provider.setEntry({name: 'CORE', tag: _provider.CANONICAL_HUB_TAG(), newAddress: HUB}); _harness.requireRegisteredHub(_provider, HUB); } @@ -67,24 +67,24 @@ contract EngineUtilsTest is Test { } function test_requireRegisteredHub_revertsWith_HubNotRegistered_spokeTag() public { - _provider.setCanonicalSpoke('CORE', HUB); + _provider.setEntry({name: 'CORE', tag: _provider.CANONICAL_SPOKE_TAG(), newAddress: HUB}); vm.expectRevert(abi.encodeWithSelector(EngineUtils.HubNotRegistered.selector, HUB)); _harness.requireRegisteredHub(_provider, HUB); } function test_requireRegisteredSpoke_canonicalTag() public { - _provider.setCanonicalSpoke('MAIN', SPOKE); + _provider.setEntry({name: 'MAIN', tag: _provider.CANONICAL_SPOKE_TAG(), newAddress: SPOKE}); _harness.requireRegisteredSpoke(_provider, SPOKE); } function test_requireRegisteredSpoke_tokenizationTag() public { - _provider.setTokenizationSpoke('MAIN', SPOKE); + _provider.setEntry({name: 'MAIN', tag: _provider.TOKENIZATION_SPOKE_TAG(), newAddress: SPOKE}); _harness.requireRegisteredSpoke(_provider, SPOKE); } function test_requireRegisteredSpoke_treasuryTag() public { - _provider.setTreasurySpoke('MAIN', SPOKE); + _provider.setEntry({name: 'MAIN', tag: _provider.TREASURY_SPOKE_TAG(), newAddress: SPOKE}); _harness.requireRegisteredSpoke(_provider, SPOKE); } @@ -94,14 +94,14 @@ contract EngineUtilsTest is Test { } function test_requireRegisteredSpoke_revertsWith_SpokeNotRegistered_hubTag() public { - _provider.setCanonicalHub('MAIN', SPOKE); + _provider.setEntry({name: 'MAIN', tag: _provider.CANONICAL_HUB_TAG(), newAddress: SPOKE}); vm.expectRevert(abi.encodeWithSelector(EngineUtils.SpokeNotRegistered.selector, SPOKE)); _harness.requireRegisteredSpoke(_provider, SPOKE); } function test_requireRegisteredCanonicalSpoke() public { - _provider.setCanonicalSpoke('MAIN', SPOKE); + _provider.setEntry({name: 'MAIN', tag: _provider.CANONICAL_SPOKE_TAG(), newAddress: SPOKE}); _harness.requireRegisteredCanonicalSpoke(_provider, SPOKE); } @@ -115,7 +115,7 @@ contract EngineUtilsTest is Test { function test_requireRegisteredCanonicalSpoke_revertsWith_CanonicalSpokeNotRegistered_tokenizationTag() public { - _provider.setTokenizationSpoke('MAIN', SPOKE); + _provider.setEntry({name: 'MAIN', tag: _provider.TOKENIZATION_SPOKE_TAG(), newAddress: SPOKE}); vm.expectRevert( abi.encodeWithSelector(EngineUtils.CanonicalSpokeNotRegistered.selector, SPOKE) @@ -126,7 +126,7 @@ contract EngineUtilsTest is Test { function test_requireRegisteredCanonicalSpoke_revertsWith_CanonicalSpokeNotRegistered_treasuryTag() public { - _provider.setTreasurySpoke('MAIN', SPOKE); + _provider.setEntry({name: 'MAIN', tag: _provider.TREASURY_SPOKE_TAG(), newAddress: SPOKE}); vm.expectRevert( abi.encodeWithSelector(EngineUtils.CanonicalSpokeNotRegistered.selector, SPOKE) diff --git a/tests/contracts/addresses-provider/AddressesProvider.t.sol b/tests/contracts/addresses-provider/AddressesProvider.t.sol index a992de393..6debd0dc3 100644 --- a/tests/contracts/addresses-provider/AddressesProvider.t.sol +++ b/tests/contracts/addresses-provider/AddressesProvider.t.sol @@ -91,17 +91,14 @@ contract AddressesProviderTest is Test { assertEq(entry.name, 'CONFIG_ENGINE'); assertEq(entry.tag, 'PERIPHERY'); - bytes32[] memory ids = provider.getIds('PERIPHERY'); - assertEq(ids.length, 1); - assertEq(ids[0], id); + assertEq(provider.getIdCount('PERIPHERY'), 1); + assertEq(provider.getIds('PERIPHERY', 0, 1)[0], id); - string[] memory tags = provider.getTags(); - assertEq(tags.length, 1); - assertEq(tags[0], 'PERIPHERY'); + assertEq(provider.getTagCount(), 1); + assertEq(provider.getTags(0, 1)[0], 'PERIPHERY'); - bytes32[] memory addressIds = provider.getAddressIds(configEngine); - assertEq(addressIds.length, 1); - assertEq(addressIds[0], id); + assertEq(provider.getAddressIdCount(configEngine), 1); + assertEq(provider.getAddressIds(configEngine, 0, 1)[0], id); } function test_setEntry_remove() public { @@ -116,9 +113,9 @@ contract AddressesProviderTest is Test { assertEq(provider.getAddress(id), address(0)); assertEq(provider.getEntry(id).tag, ''); assertEq(provider.getEntry(id).name, ''); - assertEq(provider.getIds('PERIPHERY').length, 0); - assertEq(provider.getTags().length, 0); - assertEq(provider.getAddressIds(configEngine).length, 0); + assertEq(provider.getIdCount('PERIPHERY'), 0); + assertEq(provider.getTagCount(), 0); + assertEq(provider.getAddressIdCount(configEngine), 0); } function test_setEntry_removeThenSet() public { @@ -137,9 +134,8 @@ contract AddressesProviderTest is Test { assertEq(provider.getAddress(id), newConfigEngine); - bytes32[] memory ids = provider.getIds('PERIPHERY'); - assertEq(ids.length, 1); - assertEq(ids[0], id); + assertEq(provider.getIdCount('PERIPHERY'), 1); + assertEq(provider.getIds('PERIPHERY', 0, 1)[0], id); } function test_setEntry_revertsWith_AddressAlreadySet() public { @@ -196,27 +192,27 @@ contract AddressesProviderTest is Test { assertEq(provider.getAddress({name: 'CONFIG_ENGINE', tag: 'ENGINE'}), configEngine); assertEq(provider.getAddress({name: 'V3_CONFIG_ENGINE', tag: 'V3_PERIPHERY'}), configEngine); - bytes32[] memory peripheryIds = provider.getIds('PERIPHERY'); - assertEq(peripheryIds.length, 2); + assertEq(provider.getIdCount('PERIPHERY'), 2); + bytes32[] memory peripheryIds = provider.getIds('PERIPHERY', 0, 2); assertEq(peripheryIds[0], _id('CONFIG_ENGINE', 'PERIPHERY')); assertEq(peripheryIds[1], _id('ENGINE', 'PERIPHERY')); - string[] memory tags = provider.getTags(); - assertEq(tags.length, 3); + assertEq(provider.getTagCount(), 3); + string[] memory tags = provider.getTags(0, 3); assertEq(tags[0], 'PERIPHERY'); assertEq(tags[1], 'ENGINE'); assertEq(tags[2], 'V3_PERIPHERY'); // the reverse map tracks every identifier the address is registered under assertEq(provider.getAddressIdCount(configEngine), 4); - bytes32[] memory addressIds = provider.getAddressIds(configEngine); + bytes32[] memory addressIds = provider.getAddressIds(configEngine, 0, 4); assertEq(addressIds.length, 4); assertEq(addressIds[0], _id('CONFIG_ENGINE', 'PERIPHERY')); assertEq(addressIds[1], _id('ENGINE', 'PERIPHERY')); assertEq(addressIds[2], _id('CONFIG_ENGINE', 'ENGINE')); assertEq(addressIds[3], _id('V3_CONFIG_ENGINE', 'V3_PERIPHERY')); - IAddressesProvider.Entry[] memory entries = provider.getEntries(configEngine); + IAddressesProvider.Entry[] memory entries = provider.getEntries(configEngine, 0, 4); assertEq(entries.length, 4); assertEq(entries[0].name, 'CONFIG_ENGINE'); assertEq(entries[0].tag, 'PERIPHERY'); @@ -231,36 +227,31 @@ contract AddressesProviderTest is Test { assertEq(provider.getAddress({name: 'ENGINE', tag: 'PERIPHERY'}), address(0)); assertEq(provider.getAddress({name: 'CONFIG_ENGINE', tag: 'PERIPHERY'}), configEngine); assertEq(provider.getAddress({name: 'CONFIG_ENGINE', tag: 'ENGINE'}), configEngine); - assertEq(provider.getIds('PERIPHERY').length, 1); + assertEq(provider.getIdCount('PERIPHERY'), 1); assertEq(provider.getAddressIdCount(configEngine), 3); } - function test_setHubAndSpoke_sameAddressAcrossTags() public { + function test_setEntry_sameAddressAcrossTags() public { address sharedSpoke = makeAddr('SHARED_SPOKE'); vm.startPrank(OWNER); - provider.setCanonicalSpoke('MAIN', sharedSpoke); - provider.setTokenizationSpoke('MAIN', sharedSpoke); - provider.setTreasurySpoke('MAIN', sharedSpoke); + provider.setEntry({name: 'MAIN', tag: 'CANONICAL_SPOKE', newAddress: sharedSpoke}); + provider.setEntry({name: 'MAIN', tag: 'TOKENIZATION_SPOKE', newAddress: sharedSpoke}); + provider.setEntry({name: 'MAIN', tag: 'TREASURY_SPOKE', newAddress: sharedSpoke}); vm.stopPrank(); - assertEq(provider.getCanonicalSpoke('MAIN'), sharedSpoke); - assertEq(provider.getTokenizationSpoke('MAIN'), sharedSpoke); - assertEq(provider.getTreasurySpoke('MAIN'), sharedSpoke); - - address[] memory canonicalSpokes = provider.getCanonicalSpokes(); - assertEq(canonicalSpokes.length, 1); - assertEq(canonicalSpokes[0], sharedSpoke); - - address[] memory tokenizationSpokes = provider.getTokenizationSpokes(); - assertEq(tokenizationSpokes.length, 1); - assertEq(tokenizationSpokes[0], sharedSpoke); + assertEq(provider.getAddress({name: 'MAIN', tag: 'CANONICAL_SPOKE'}), sharedSpoke); + assertEq(provider.getAddress({name: 'MAIN', tag: 'TOKENIZATION_SPOKE'}), sharedSpoke); + assertEq(provider.getAddress({name: 'MAIN', tag: 'TREASURY_SPOKE'}), sharedSpoke); - address[] memory treasurySpokes = provider.getTreasurySpokes(); - assertEq(treasurySpokes.length, 1); - assertEq(treasurySpokes[0], sharedSpoke); + assertEq(provider.getIdCount('CANONICAL_SPOKE'), 1); + assertEq(provider.getAddresses('CANONICAL_SPOKE', 0, 1)[0], sharedSpoke); + assertEq(provider.getIdCount('TOKENIZATION_SPOKE'), 1); + assertEq(provider.getAddresses('TOKENIZATION_SPOKE', 0, 1)[0], sharedSpoke); + assertEq(provider.getIdCount('TREASURY_SPOKE'), 1); + assertEq(provider.getAddresses('TREASURY_SPOKE', 0, 1)[0], sharedSpoke); - IAddressesProvider.Entry[] memory entries = provider.getEntries(sharedSpoke); + IAddressesProvider.Entry[] memory entries = provider.getEntries(sharedSpoke, 0, 3); assertEq(entries.length, 3); assertEq(entries[0].tag, 'CANONICAL_SPOKE'); assertEq(entries[1].tag, 'TOKENIZATION_SPOKE'); @@ -329,7 +320,7 @@ contract AddressesProviderTest is Test { address spoke = makeAddr('SPOKE'); vm.startPrank(OWNER); - provider.setCanonicalSpoke('MAIN', spoke); + provider.setEntry({name: 'MAIN', tag: provider.CANONICAL_SPOKE_TAG(), newAddress: spoke}); provider.setEntry({name: 'MAIN', tag: 'BABYLON', newAddress: spoke}); vm.stopPrank(); @@ -364,350 +355,56 @@ contract AddressesProviderTest is Test { vm.stopPrank(); } - function test_setCanonicalHub() public { - address coreHub = makeAddr('CORE_HUB'); - address plusHub = makeAddr('PLUS_HUB'); - address primeHub = makeAddr('PRIME_HUB'); - - vm.startPrank(OWNER); - vm.expectEmit(address(provider)); - emit IAddressesProvider.SetEntry( - _id('CORE', 'CANONICAL_HUB'), - 'CORE', - 'CANONICAL_HUB', - address(0), - coreHub - ); - provider.setCanonicalHub('CORE', coreHub); - provider.setCanonicalHub('PLUS', plusHub); - provider.setCanonicalHub('PRIME', primeHub); - vm.stopPrank(); - - assertEq(provider.getCanonicalHub('CORE'), coreHub); - assertEq(provider.getCanonicalHub('PLUS'), plusHub); - assertEq(provider.getCanonicalHub('PRIME'), primeHub); - assertEq(provider.getAddress(_id('CORE', 'CANONICAL_HUB')), coreHub); - - IAddressesProvider.Entry memory entry = provider.getEntry(_id('CORE', 'CANONICAL_HUB')); - assertEq(entry.name, 'CORE'); - assertEq(entry.tag, 'CANONICAL_HUB'); - - bytes32[] memory hubIds = provider.getIds('CANONICAL_HUB'); - assertEq(hubIds.length, 3); - assertEq(hubIds[0], _id('CORE', 'CANONICAL_HUB')); - assertEq(hubIds[1], _id('PLUS', 'CANONICAL_HUB')); - assertEq(hubIds[2], _id('PRIME', 'CANONICAL_HUB')); - - address[] memory hubs = provider.getCanonicalHubs(); - assertEq(hubs.length, 3); - assertEq(hubs[0], coreHub); - assertEq(hubs[1], plusHub); - assertEq(hubs[2], primeHub); - assertEq(provider.getAddresses(provider.CANONICAL_HUB_TAG()), hubs); - } - - function test_setCanonicalHub_removeThenSet() public { - address coreHub = makeAddr('CORE_HUB'); - address newCoreHub = makeAddr('NEW_CORE_HUB'); - - vm.startPrank(OWNER); - provider.setCanonicalHub('CORE', coreHub); - provider.setCanonicalHub('CORE', address(0)); - - vm.expectEmit(address(provider)); - emit IAddressesProvider.SetEntry( - _id('CORE', 'CANONICAL_HUB'), - 'CORE', - 'CANONICAL_HUB', - address(0), - newCoreHub - ); - provider.setCanonicalHub('CORE', newCoreHub); - vm.stopPrank(); - - assertEq(provider.getCanonicalHub('CORE'), newCoreHub); - assertEq(provider.getCanonicalHubs().length, 1); - } - - function test_setCanonicalHub_revertsWith_AddressAlreadySet() public { - vm.startPrank(OWNER); - provider.setCanonicalHub('CORE', makeAddr('CORE_HUB')); - - vm.expectRevert( - abi.encodeWithSelector( - IAddressesProvider.AddressAlreadySet.selector, - _id('CORE', 'CANONICAL_HUB') - ) - ); - provider.setCanonicalHub('CORE', makeAddr('NEW_CORE_HUB')); - vm.stopPrank(); - } - - function test_setCanonicalHub_remove() public { - vm.startPrank(OWNER); - provider.setCanonicalHub('CORE', makeAddr('CORE_HUB')); - provider.setCanonicalHub('PLUS', makeAddr('PLUS_HUB')); - provider.setCanonicalHub('CORE', address(0)); - vm.stopPrank(); - - assertEq(provider.getCanonicalHub('CORE'), address(0)); - - address[] memory hubs = provider.getCanonicalHubs(); - assertEq(hubs.length, 1); - assertEq(hubs[0], provider.getCanonicalHub('PLUS')); - } - - function test_setCanonicalHub_remove_revertsWith_AddressNotSet() public { - vm.expectRevert( - abi.encodeWithSelector( - IAddressesProvider.AddressNotSet.selector, - _id('CORE', 'CANONICAL_HUB') - ) - ); - vm.prank(OWNER); - provider.setCanonicalHub('CORE', address(0)); - } - - function test_setCanonicalHub_revertsWith_InvalidName() public { - vm.expectRevert(IAddressesProvider.InvalidName.selector); - vm.prank(OWNER); - provider.setCanonicalHub('', makeAddr('CORE_HUB')); - } - - function test_setCanonicalHub_revertsWith_OwnableUnauthorizedAccount() public { - address caller = makeAddr('caller'); - - vm.expectRevert( - abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, caller) - ); - vm.prank(caller); - provider.setCanonicalHub('CORE', makeAddr('CORE_HUB')); - } - - function test_setCanonicalHub_fuzz(string memory name, address hub) public { - vm.assume(bytes(name).length > 0); - vm.assume(hub != address(0)); - - vm.prank(OWNER); - provider.setCanonicalHub(name, hub); - - assertEq(provider.getCanonicalHub(name), hub); - - address[] memory hubs = provider.getCanonicalHubs(); - assertEq(hubs.length, 1); - assertEq(hubs[0], hub); - } - - function test_setCanonicalSpoke() public { - address mainSpoke = makeAddr('MAIN_SPOKE'); - address bluechipSpoke = makeAddr('BLUECHIP_SPOKE'); - address forexSpoke = makeAddr('FOREX_SPOKE'); - - vm.startPrank(OWNER); - vm.expectEmit(address(provider)); - emit IAddressesProvider.SetEntry( - _id('MAIN', 'CANONICAL_SPOKE'), - 'MAIN', - 'CANONICAL_SPOKE', - address(0), - mainSpoke - ); - provider.setCanonicalSpoke('MAIN', mainSpoke); - provider.setCanonicalSpoke('BLUECHIP', bluechipSpoke); - provider.setCanonicalSpoke('FOREX', forexSpoke); - vm.stopPrank(); - - assertEq(provider.getCanonicalSpoke('MAIN'), mainSpoke); - assertEq(provider.getCanonicalSpoke('BLUECHIP'), bluechipSpoke); - assertEq(provider.getCanonicalSpoke('FOREX'), forexSpoke); - assertEq(provider.getAddress(_id('MAIN', 'CANONICAL_SPOKE')), mainSpoke); - - address[] memory canonicalSpokes = provider.getCanonicalSpokes(); - assertEq(canonicalSpokes.length, 3); - assertEq(canonicalSpokes[0], mainSpoke); - assertEq(canonicalSpokes[1], bluechipSpoke); - assertEq(canonicalSpokes[2], forexSpoke); - } - - function test_setTokenizationSpoke() public { - address coreWethSpoke = makeAddr('CORE_WETH_TOKENIZATION_SPOKE'); - address primeGhoSpoke = makeAddr('PRIME_GHO_TOKENIZATION_SPOKE'); - - vm.startPrank(OWNER); - provider.setTokenizationSpoke('CORE_WETH', coreWethSpoke); - provider.setTokenizationSpoke('PRIME_GHO', primeGhoSpoke); - vm.stopPrank(); - - assertEq(provider.getTokenizationSpoke('CORE_WETH'), coreWethSpoke); - assertEq(provider.getTokenizationSpoke('PRIME_GHO'), primeGhoSpoke); - assertEq(provider.getAddress(_id('CORE_WETH', 'TOKENIZATION_SPOKE')), coreWethSpoke); - - address[] memory tokenizationSpokes = provider.getTokenizationSpokes(); - assertEq(tokenizationSpokes.length, 2); - assertEq(tokenizationSpokes[0], coreWethSpoke); - assertEq(tokenizationSpokes[1], primeGhoSpoke); - } - - function test_setTreasurySpoke() public { - address treasurySpoke = makeAddr('TREASURY_SPOKE'); - - vm.prank(OWNER); - provider.setTreasurySpoke('MAIN', treasurySpoke); - - assertEq(provider.getTreasurySpoke('MAIN'), treasurySpoke); - assertEq(provider.getAddress(_id('MAIN', 'TREASURY_SPOKE')), treasurySpoke); - - address[] memory treasurySpokes = provider.getTreasurySpokes(); - assertEq(treasurySpokes.length, 1); - assertEq(treasurySpokes[0], treasurySpoke); - } - - function test_setSpoke_sameNameAcrossTags() public { + function test_setEntry_sameNameAcrossTags() public { address mainSpoke = makeAddr('MAIN_SPOKE'); address treasurySpoke = makeAddr('TREASURY_SPOKE'); vm.startPrank(OWNER); - provider.setCanonicalSpoke('MAIN', mainSpoke); - provider.setTreasurySpoke('MAIN', treasurySpoke); + provider.setEntry({name: 'MAIN', tag: 'CANONICAL_SPOKE', newAddress: mainSpoke}); + provider.setEntry({name: 'MAIN', tag: 'TREASURY_SPOKE', newAddress: treasurySpoke}); vm.stopPrank(); - assertEq(provider.getCanonicalSpoke('MAIN'), mainSpoke); - assertEq(provider.getTreasurySpoke('MAIN'), treasurySpoke); + assertEq(provider.getAddress({name: 'MAIN', tag: 'CANONICAL_SPOKE'}), mainSpoke); + assertEq(provider.getAddress({name: 'MAIN', tag: 'TREASURY_SPOKE'}), treasurySpoke); } - function test_setSpoke_removeThenSet() public { - address mainSpoke = makeAddr('MAIN_SPOKE'); - address newMainSpoke = makeAddr('NEW_MAIN_SPOKE'); - + function test_setEntry_removeLastEntryOfTag() public { vm.startPrank(OWNER); - provider.setCanonicalSpoke('MAIN', mainSpoke); - provider.setCanonicalSpoke('MAIN', address(0)); - provider.setCanonicalSpoke('MAIN', newMainSpoke); - vm.stopPrank(); - - assertEq(provider.getCanonicalSpoke('MAIN'), newMainSpoke); - assertEq(provider.getCanonicalSpokes().length, 1); - } - - function test_setSpoke_revertsWith_AddressAlreadySet() public { - vm.startPrank(OWNER); - provider.setCanonicalSpoke('MAIN', makeAddr('MAIN_SPOKE')); - - vm.expectRevert( - abi.encodeWithSelector( - IAddressesProvider.AddressAlreadySet.selector, - _id('MAIN', 'CANONICAL_SPOKE') - ) - ); - provider.setCanonicalSpoke('MAIN', makeAddr('NEW_MAIN_SPOKE')); - vm.stopPrank(); - } - - function test_setSpoke_remove() public { - vm.startPrank(OWNER); - provider.setCanonicalSpoke('MAIN', makeAddr('MAIN_SPOKE')); - provider.setCanonicalSpoke('BLUECHIP', makeAddr('BLUECHIP_SPOKE')); - provider.setTreasurySpoke('MAIN', makeAddr('TREASURY_SPOKE')); - - provider.setCanonicalSpoke('MAIN', address(0)); - vm.stopPrank(); - - assertEq(provider.getCanonicalSpoke('MAIN'), address(0)); - - address[] memory canonicalSpokes = provider.getCanonicalSpokes(); - assertEq(canonicalSpokes.length, 1); - assertEq(canonicalSpokes[0], provider.getCanonicalSpoke('BLUECHIP')); - assertEq(provider.getTreasurySpokes().length, 1); - } - - function test_setSpoke_removeLastIdOfTag() public { - vm.startPrank(OWNER); - provider.setCanonicalSpoke('MAIN', makeAddr('MAIN_SPOKE')); - provider.setTreasurySpoke('MAIN', makeAddr('TREASURY_SPOKE')); - - provider.setTreasurySpoke('MAIN', address(0)); - vm.stopPrank(); - - assertEq(provider.getTreasurySpokes().length, 0); - - string[] memory tags = provider.getTags(); - assertEq(tags.length, 1); - assertEq(tags[0], 'CANONICAL_SPOKE'); - } - - function test_setSpoke_remove_revertsWith_AddressNotSet() public { - vm.expectRevert( - abi.encodeWithSelector( - IAddressesProvider.AddressNotSet.selector, - _id('MAIN', 'CANONICAL_SPOKE') - ) - ); - vm.prank(OWNER); - provider.setCanonicalSpoke('MAIN', address(0)); - } - - function test_setSpoke_revertsWith_InvalidName() public { - vm.startPrank(OWNER); - - vm.expectRevert(IAddressesProvider.InvalidName.selector); - provider.setCanonicalSpoke('', makeAddr('MAIN_SPOKE')); - - vm.expectRevert(IAddressesProvider.InvalidName.selector); - provider.setTokenizationSpoke('', makeAddr('CORE_WETH_TOKENIZATION_SPOKE')); - - vm.expectRevert(IAddressesProvider.InvalidName.selector); - provider.setTreasurySpoke('', makeAddr('TREASURY_SPOKE')); - - vm.stopPrank(); - } - - function test_setSpoke_revertsWith_OwnableUnauthorizedAccount() public { - address caller = makeAddr('caller'); - vm.startPrank(caller); - - vm.expectRevert( - abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, caller) - ); - provider.setCanonicalSpoke('MAIN', makeAddr('MAIN_SPOKE')); - - vm.expectRevert( - abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, caller) - ); - provider.setTokenizationSpoke('CORE_WETH', makeAddr('CORE_WETH_TOKENIZATION_SPOKE')); - - vm.expectRevert( - abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, caller) - ); - provider.setTreasurySpoke('MAIN', makeAddr('TREASURY_SPOKE')); + provider.setEntry({name: 'MAIN', tag: 'CANONICAL_SPOKE', newAddress: makeAddr('MAIN_SPOKE')}); + provider.setEntry({ + name: 'MAIN', + tag: 'TREASURY_SPOKE', + newAddress: makeAddr('TREASURY_SPOKE') + }); + provider.setEntry({name: 'MAIN', tag: 'TREASURY_SPOKE', newAddress: address(0)}); vm.stopPrank(); - } - - function test_setSpoke_fuzz(string memory name, address spoke) public { - vm.assume(bytes(name).length > 0); - vm.assume(spoke != address(0)); - - vm.prank(OWNER); - provider.setCanonicalSpoke(name, spoke); - assertEq(provider.getCanonicalSpoke(name), spoke); + assertEq(provider.getIdCount('TREASURY_SPOKE'), 0); - address[] memory canonicalSpokes = provider.getCanonicalSpokes(); - assertEq(canonicalSpokes.length, 1); - assertEq(canonicalSpokes[0], spoke); + assertEq(provider.getTagCount(), 1); + assertEq(provider.getTags(0, 1)[0], 'CANONICAL_SPOKE'); } function test_getTags() public { vm.startPrank(OWNER); - provider.setCanonicalHub('CORE', makeAddr('CORE_HUB')); - provider.setCanonicalSpoke('MAIN', makeAddr('MAIN_SPOKE')); - provider.setTokenizationSpoke('CORE_WETH', makeAddr('CORE_WETH_TOKENIZATION_SPOKE')); - provider.setTreasurySpoke('MAIN', makeAddr('TREASURY_SPOKE')); + provider.setEntry({name: 'CORE', tag: 'CANONICAL_HUB', newAddress: makeAddr('CORE_HUB')}); + provider.setEntry({name: 'MAIN', tag: 'CANONICAL_SPOKE', newAddress: makeAddr('MAIN_SPOKE')}); + provider.setEntry({ + name: 'CORE_WETH', + tag: 'TOKENIZATION_SPOKE', + newAddress: makeAddr('CORE_WETH_TOKENIZATION_SPOKE') + }); + provider.setEntry({ + name: 'MAIN', + tag: 'TREASURY_SPOKE', + newAddress: makeAddr('TREASURY_SPOKE') + }); vm.stopPrank(); assertEq(provider.getTagCount(), 4); - string[] memory tags = provider.getTags(); + string[] memory tags = provider.getTags(0, 4); assertEq(tags.length, 4); assertEq(tags[0], 'CANONICAL_HUB'); assertEq(tags[1], 'CANONICAL_SPOKE'); @@ -717,10 +414,18 @@ contract AddressesProviderTest is Test { function test_getTags_bounded() public { vm.startPrank(OWNER); - provider.setCanonicalHub('CORE', makeAddr('CORE_HUB')); - provider.setCanonicalSpoke('MAIN', makeAddr('MAIN_SPOKE')); - provider.setTokenizationSpoke('CORE_WETH', makeAddr('CORE_WETH_TOKENIZATION_SPOKE')); - provider.setTreasurySpoke('MAIN', makeAddr('TREASURY_SPOKE')); + provider.setEntry({name: 'CORE', tag: 'CANONICAL_HUB', newAddress: makeAddr('CORE_HUB')}); + provider.setEntry({name: 'MAIN', tag: 'CANONICAL_SPOKE', newAddress: makeAddr('MAIN_SPOKE')}); + provider.setEntry({ + name: 'CORE_WETH', + tag: 'TOKENIZATION_SPOKE', + newAddress: makeAddr('CORE_WETH_TOKENIZATION_SPOKE') + }); + provider.setEntry({ + name: 'MAIN', + tag: 'TREASURY_SPOKE', + newAddress: makeAddr('TREASURY_SPOKE') + }); vm.stopPrank(); string[] memory firstTwo = provider.getTags(0, 2); @@ -744,9 +449,9 @@ contract AddressesProviderTest is Test { function test_getIds_bounded() public { vm.startPrank(OWNER); - provider.setCanonicalHub('CORE', makeAddr('CORE_HUB')); - provider.setCanonicalHub('PLUS', makeAddr('PLUS_HUB')); - provider.setCanonicalHub('PRIME', makeAddr('PRIME_HUB')); + provider.setEntry({name: 'CORE', tag: 'CANONICAL_HUB', newAddress: makeAddr('CORE_HUB')}); + provider.setEntry({name: 'PLUS', tag: 'CANONICAL_HUB', newAddress: makeAddr('PLUS_HUB')}); + provider.setEntry({name: 'PRIME', tag: 'CANONICAL_HUB', newAddress: makeAddr('PRIME_HUB')}); vm.stopPrank(); assertEq(provider.getIdCount('CANONICAL_HUB'), 3); @@ -769,9 +474,9 @@ contract AddressesProviderTest is Test { address primeHub = makeAddr('PRIME_HUB'); vm.startPrank(OWNER); - provider.setCanonicalHub('CORE', coreHub); - provider.setCanonicalHub('PLUS', plusHub); - provider.setCanonicalHub('PRIME', primeHub); + provider.setEntry({name: 'CORE', tag: 'CANONICAL_HUB', newAddress: coreHub}); + provider.setEntry({name: 'PLUS', tag: 'CANONICAL_HUB', newAddress: plusHub}); + provider.setEntry({name: 'PRIME', tag: 'CANONICAL_HUB', newAddress: primeHub}); vm.stopPrank(); address[] memory firstTwo = provider.getAddresses('CANONICAL_HUB', 0, 2); @@ -784,60 +489,13 @@ contract AddressesProviderTest is Test { assertEq(last[0], primeHub); } - function test_getCanonicalHubs_bounded() public { - address coreHub = makeAddr('CORE_HUB'); - address plusHub = makeAddr('PLUS_HUB'); - address primeHub = makeAddr('PRIME_HUB'); - - vm.startPrank(OWNER); - provider.setCanonicalHub('CORE', coreHub); - provider.setCanonicalHub('PLUS', plusHub); - provider.setCanonicalHub('PRIME', primeHub); - vm.stopPrank(); - - address[] memory firstTwo = provider.getCanonicalHubs(0, 2); - assertEq(firstTwo.length, 2); - assertEq(firstTwo[0], coreHub); - assertEq(firstTwo[1], plusHub); - - address[] memory last = provider.getCanonicalHubs(2, 100); - assertEq(last.length, 1); - assertEq(last[0], primeHub); - } - - function test_getSpokes_bounded() public { - address mainSpoke = makeAddr('MAIN_SPOKE'); - address extraSpoke = makeAddr('EXTRA_SPOKE'); - address tokenizationSpoke = makeAddr('TOKENIZATION_SPOKE'); - address treasurySpoke = makeAddr('TREASURY_SPOKE'); - - vm.startPrank(OWNER); - provider.setCanonicalSpoke('MAIN', mainSpoke); - provider.setCanonicalSpoke('EXTRA', extraSpoke); - provider.setTokenizationSpoke('CORE_WETH', tokenizationSpoke); - provider.setTreasurySpoke('MAIN', treasurySpoke); - vm.stopPrank(); - - address[] memory canonicalSpokes = provider.getCanonicalSpokes(1, 100); - assertEq(canonicalSpokes.length, 1); - assertEq(canonicalSpokes[0], extraSpoke); - - address[] memory tokenizationSpokes = provider.getTokenizationSpokes(0, 100); - assertEq(tokenizationSpokes.length, 1); - assertEq(tokenizationSpokes[0], tokenizationSpoke); - - address[] memory treasurySpokes = provider.getTreasurySpokes(0, 100); - assertEq(treasurySpokes.length, 1); - assertEq(treasurySpokes[0], treasurySpoke); - } - function test_getAddressIds_bounded() public { address shared = makeAddr('SHARED'); vm.startPrank(OWNER); - provider.setCanonicalHub('CORE', shared); - provider.setCanonicalSpoke('MAIN', shared); - provider.setTreasurySpoke('MAIN', shared); + provider.setEntry({name: 'CORE', tag: 'CANONICAL_HUB', newAddress: shared}); + provider.setEntry({name: 'MAIN', tag: 'CANONICAL_SPOKE', newAddress: shared}); + provider.setEntry({name: 'MAIN', tag: 'TREASURY_SPOKE', newAddress: shared}); vm.stopPrank(); assertEq(provider.getAddressIdCount(shared), 3);