Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/comment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
8 changes: 4 additions & 4 deletions .github/workflows/tests-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
12 changes: 6 additions & 6 deletions .github/workflows/tests-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
288 changes: 288 additions & 0 deletions src/addresses-provider/AddressesProvider.sol

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idk if this should be in a separate folder, perhaps misc? nbd either way

Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
// SPDX-License-Identifier: LicenseRef-BUSL

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add it to the Deploy procedures, and the Config Engine (tho it might be managed by a different admin than Gov, can be useful to have support if Gov ends up controling the Provider)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I'll add a commit to have procedures as well, although I was thinking to not be part of the config engine. can be separate and can be configured in an AIP, wdyt?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the Config Engine part can be done later on when needed.
Keeping this open as a reminder.

@avniculae avniculae Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it needs to be part of config engine, I'm thinking it can be configured in an AIP, wdyt?

@avniculae avniculae Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm I see, you mean when you add a new hub/spoke. I thought on the initialisation at the moment, since we already deployed and we need to do it retroactively. I will add in config engine for future listings

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we also have the option to do this in the configurator instead

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its more fool proof if we do it in the configurator @avniculae

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

settled offline with Dhairya: the engine now reverts if a hub/spoke it targets is not registered in the provider, and registering/unregistering is a dedicated engine action (executeAddressesProviderEntryUpdates, runs before hub/spoke actions in the payload). 5650778

pragma solidity 0.8.28;

import {Ownable2StepUpgradeable} from 'src/dependencies/openzeppelin-upgradeable/Ownable2StepUpgradeable.sol';
import {EnumerableSet} from 'src/dependencies/openzeppelin/EnumerableSet.sol';
import {AddressesProviderStorage} from 'src/addresses-provider/AddressesProviderStorage.sol';
import {IAddressesProvider} from 'src/addresses-provider/interfaces/IAddressesProvider.sol';

/// @title AddressesProvider
/// @author Aave Labs
/// @notice Main registry of Aave V4 contract addresses.
abstract contract AddressesProvider is
AddressesProviderStorage,
Ownable2StepUpgradeable,
IAddressesProvider
{
using EnumerableSet for *;

/// @inheritdoc IAddressesProvider
string public constant CANONICAL_HUB_TAG = 'CANONICAL_HUB';

/// @inheritdoc IAddressesProvider
string public constant CANONICAL_SPOKE_TAG = 'CANONICAL_SPOKE';

/// @inheritdoc IAddressesProvider
string public constant TOKENIZATION_SPOKE_TAG = 'TOKENIZATION_SPOKE';

/// @inheritdoc IAddressesProvider
string public constant TREASURY_SPOKE_TAG = 'TREASURY_SPOKE';

/// @dev To be overridden by the inheriting AddressesProvider instance contract.
function initialize(address owner) external virtual;

/// @inheritdoc IAddressesProvider
function setEntry(
string calldata name,
string calldata tag,
address newAddress
) external onlyOwner {
_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;
}

/// @inheritdoc IAddressesProvider
function getAddress(string calldata name, string calldata tag) external view returns (address) {
return _getAddress({name: name, tag: tag});
}

/// @inheritdoc IAddressesProvider
function getEntry(bytes32 id) external view returns (Entry memory) {
return _idToEntry[id];
}

/// @inheritdoc IAddressesProvider
function getTagCount() external view returns (uint256) {
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);
}

/// @inheritdoc IAddressesProvider
function getIdCount(string calldata tag) external view returns (uint256) {
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,
uint256 start,
uint256 end
) external view returns (bytes32[] memory) {
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,
uint256 start,
uint256 end
) external view returns (address[] memory) {
return _toAddresses(_tagToIdSet[tag].values(start, end));
}

/// @inheritdoc IAddressesProvider
function getAddressIdCount(address addr) external view returns (uint256) {
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,
uint256 start,
uint256 end
) external view returns (bytes32[] memory) {
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,
uint256 start,
uint256 end
) external view returns (Entry[] memory) {
return _toEntries(_addressToIdSet[addr].values(start, end));
}

/// @inheritdoc IAddressesProvider
function isRegistered(address addr, string calldata tag) external view returns (bool) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flagging: this walks every id of the address and hashes each tag, so it degrades if one address is registered under many names/tags. The engine can also check the same address several times in one payload (requireRegisteredSpoke does up to 3 isRegistered calls per item). Probably fine to leave for now since it is owner-driven and the sets stay small, but if AIP gas becomes a concern we may want to optimise (e.g. a tag-keyed lookup).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

config engine can just check if its registered on any tag, if so it passes no?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the optional tag input case

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather keep strongly checking the type — an entry under an unrelated tag shouldn't authorize hub/spoke actions. Made the typed check O(1) instead by tracking a per-address tag count in setEntry: e7c9097

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});
}

/// @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) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these dont have pointer getters, i would just keep those and not have full arr return one's for all

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can add range getters, but would keep these for convenience as well, wdyt?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would kill these, unnecessary take space

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we NEED range getters bc of potential out of gas, these arent needed

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not aligned here. What's the concern if we have both range-based and full getters?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overpopulation

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});
}

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});
Entry memory oldEntry = _idToEntry[id];

if (newAddress == address(0)) {
require(oldEntry.addr != address(0), AddressNotSet(id));
_tagToIdSet[oldEntry.tag].remove(id);
if (_tagToIdSet[oldEntry.tag].length() == 0) {
_tagsSet.remove(oldEntry.tag);
}
_addressToIdSet[oldEntry.addr].remove(id);
delete _idToEntry[id];
} else {
require(oldEntry.addr == address(0), AddressAlreadySet(id));
_idToEntry[id] = Entry({addr: newAddress, name: name, tag: tag});
_tagToIdSet[tag].add(id);
_tagsSet.add(tag);
_addressToIdSet[newAddress].add(id);
}

emit SetEntry(id, name, tag, oldEntry.addr, newAddress);
}

function _getAddress(string memory name, string memory tag) internal view returns (address) {
return _idToEntry[_getId({name: name, tag: tag})].addr;
}

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] = _idToEntry[ids[i]].addr;
}
return addresses;
}

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] = _idToEntry[ids[i]];
}
return entries;
}

function _getId(string memory name, string memory tag) internal pure returns (bytes32) {
return keccak256(abi.encode(name, tag));
}
}
28 changes: 28 additions & 0 deletions src/addresses-provider/AddressesProviderStorage.sol
Original file line number Diff line number Diff line change
@@ -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;
}
Loading
Loading