Skip to content

Commit

Permalink
Merge branch 'main' into bm/simulate-in-ci
Browse files Browse the repository at this point in the history
  • Loading branch information
blmalone authored Dec 17, 2024
2 parents b15b031 + 059f815 commit 67e9657
Show file tree
Hide file tree
Showing 57 changed files with 2,354 additions and 3 deletions.
11 changes: 11 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,16 @@ jobs:
just install
forge --version
forge fmt --check
forge_test:
docker:
- image: <<pipeline.parameters.ci_builder_image>>
steps:
- checkout
- run:
name: forge test
command: |
forge --version
forge test -vvv
print_versions:
docker:
Expand All @@ -228,6 +238,7 @@ workflows:
# Forge checks.
- forge_build
- forge_fmt
- forge_test
# RPC endpoint checks.
- check_sepolia_rpc_endpoints
- check_mainnet_rpc_endpoints
Expand Down
22 changes: 22 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
(The MIT License)

Copyright 2023-2024 Optimism

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
4 changes: 4 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ remappings = [

[profile.ci]
deny_warnings = true

[rpc_endpoints]
localhost = "http://127.0.0.1:8545"
mainnet = "https://ethereum.publicnode.com"
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ install-eip712sign:
PATH="$REPO_ROOT/bin:$PATH"
cd $REPO_ROOT
mkdir -p bin || true
GOBIN="$REPO_ROOT/bin" go install github.com/base-org/[email protected].7
GOBIN="$REPO_ROOT/bin" go install github.com/base-org/[email protected].8
# Bundle path should be provided including the .json file extension.
add-transaction bundlePath to sig *params:
Expand Down
184 changes: 184 additions & 0 deletions src/fps/AddressRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
pragma solidity 0.8.15;

import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

import {Test} from "forge-std/Test.sol";
import {IAddressRegistry} from "src/fps/IAddressRegistry.sol";
import {SUPERCHAIN_REGISTRY_PATH} from "src/fps/utils/Constants.sol";

/// @title Network Address Manager
/// @notice This contract provides a single source of truth for storing and retrieving addresses across multiple networks.
/// @dev Handles addresses for contracts and externally owned accounts (EOAs) while ensuring correctness and uniqueness.
contract AddressRegistry is IAddressRegistry, Test {
using EnumerableSet for EnumerableSet.UintSet;

/// @dev Structure for reading address details from JSON files.
struct InputAddress {
/// Blockchain network identifier
address addr;
/// contract identifier (name)
string identifier;
/// Address (contract or EOA)
/// Indicates if the address is a contract
bool isContract;
}

/// @dev Structure for storing address details in the contract.
struct RegistryEntry {
address addr;
/// Address (contract or EOA)
/// Indicates if the address is a contract
bool isContract;
}

/// @dev Structure for reading chain list details from toml file
struct Superchain {
uint256 chainId;
string name;
}

/// @notice Maps an identifier and l2 instance chain ID to a stored address entry.
/// All addresses will live on the same chain.
mapping(string => mapping(uint256 => RegistryEntry)) private registry;

/// @notice Supported L2 chain IDs for this Address Registry instance.
mapping(uint256 => bool) public supportedL2ChainIds;

/// @notice Array of supported superchains and their configurations
Superchain[] public superchains;

/// @notice Initializes the contract by loading addresses from TOML files and configuring the supported L2 chains.
/// @param addressFolderPath The path to the folder containing chain-specific TOML address files
/// @param superchainListFilePath The path to the TOML file containing the list of supported L2 chains
constructor(string memory addressFolderPath, string memory superchainListFilePath) {
bytes memory superchainListContent = vm.parseToml(vm.readFile(superchainListFilePath), ".chains");
superchains = abi.decode(superchainListContent, (Superchain[]));

string memory superchainAddressesContent = vm.readFile(SUPERCHAIN_REGISTRY_PATH);

for (uint256 i = 0; i < superchains.length; i++) {
uint256 superchainId = superchains[i].chainId;
string memory superchainName = superchains[i].name;
require(!supportedL2ChainIds[superchainId], "Duplicate chain ID in superchain config");
require(superchainId != 0, "Invalid chain ID in superchain config");
require(bytes(superchainName).length > 0, "Empty name in superchain config");

supportedL2ChainIds[superchainId] = true;

string memory filePath =
string(abi.encodePacked(addressFolderPath, "/", vm.toString(superchainId), ".toml"));
bytes memory fileContent = vm.parseToml(vm.readFile(filePath), ".addresses");

InputAddress[] memory parsedAddresses = abi.decode(fileContent, (InputAddress[]));

for (uint256 j = 0; j < parsedAddresses.length; j++) {
string memory identifier = parsedAddresses[j].identifier;
address contractAddress = parsedAddresses[j].addr;
bool isContract = parsedAddresses[j].isContract;

require(contractAddress != address(0), "Invalid address: cannot be zero");
require(
registry[identifier][superchainId].addr == address(0),
"Address already registered with this identifier and chain ID"
);

_typeCheckAddress(contractAddress, isContract);

registry[identifier][superchainId] = RegistryEntry(contractAddress, isContract);
string memory prefixedIdentifier =
string(abi.encodePacked(vm.replace(vm.toUppercase(superchainName), " ", "_"), "_", identifier));
vm.label(contractAddress, prefixedIdentifier); // Add label for debugging purposes
}

string[] memory keys =
vm.parseJsonKeys(superchainAddressesContent, string.concat("$.", vm.toString(superchainId)));

for (uint256 j = 0; j < keys.length; j++) {
string memory key = keys[j];
address addr = vm.parseJsonAddress(
superchainAddressesContent, string.concat("$.", vm.toString(superchainId), ".", key)
);

require(addr != address(0), "Invalid address: cannot be zero");
require(
registry[key][superchainId].addr == address(0),
"Address already registered with this identifier and chain ID"
);

registry[key][superchainId] = RegistryEntry(addr, addr.code.length > 0);
string memory prefixedIdentifier =
string(abi.encodePacked(vm.replace(vm.toUppercase(superchainName), " ", "_"), "_", key));
vm.label(addr, prefixedIdentifier);
}
}
}

/// @notice Retrieves an address by its identifier for a specified L2 chain
/// @param identifier The unique identifier associated with the address
/// @param l2ChainId The chain ID of the L2 network
/// @return The address associated with the given identifier on the specified chain
function getAddress(string memory identifier, uint256 l2ChainId) public view returns (address) {
_l2ChainIdSupported(l2ChainId);

// Fetch the stored registry entry
RegistryEntry memory entry = registry[identifier][l2ChainId];
address resolvedAddress = entry.addr;

require(resolvedAddress != address(0), "Address not found");

return resolvedAddress;
}

/// @notice Checks if an address is a contract for a given identifier and L2 chain
/// @param identifier The unique identifier associated with the address
/// @param l2ChainId The chain ID of the L2 network
/// @return True if the address is a contract, false otherwise
function isAddressContract(string memory identifier, uint256 l2ChainId) public view returns (bool) {
_l2ChainIdSupported(l2ChainId);
_checkAddressRegistered(identifier, l2ChainId);

return registry[identifier][l2ChainId].isContract;
}

/// @notice Checks if an address exists for a specified identifier and L2 chain
/// @param identifier The unique identifier associated with the address
/// @param l2ChainId The chain ID of the L2 network
/// @return True if the address exists, false otherwise
function isAddressRegistered(string memory identifier, uint256 l2ChainId) public view returns (bool) {
return registry[identifier][l2ChainId].addr != address(0);
}

/// @notice Verifies that an address is registered for a given identifier and chain
/// @dev Reverts if the address is not registered
/// @param identifier The unique identifier associated with the address
/// @param l2ChainId The chain ID of the L2 network
function _checkAddressRegistered(string memory identifier, uint256 l2ChainId) private view {
require(
isAddressRegistered(identifier, l2ChainId),
string(
abi.encodePacked("Address not found for identifier ", identifier, " on chain ", vm.toString(l2ChainId))
)
);
}

/// @notice Verifies that the given L2 chain ID is supported
/// @param l2ChainId The chain ID of the L2 network to verify
function _l2ChainIdSupported(uint256 l2ChainId) private view {
require(
supportedL2ChainIds[l2ChainId],
string(abi.encodePacked("L2 Chain ID ", vm.toString(l2ChainId), " not supported"))
);
}

/// @notice Validates whether an address matches its expected type (contract or EOA)
/// @dev Reverts if the address type does not match the expected type
/// @param addr The address to validate
/// @param isContract True if the address should be a contract, false if it should be an EOA
function _typeCheckAddress(address addr, bool isContract) private view {
if (isContract) {
require(addr.code.length > 0, "Address must contain code");
} else {
require(addr.code.length == 0, "Address must not contain code");
}
}
}
23 changes: 23 additions & 0 deletions src/fps/IAddressRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
pragma solidity 0.8.15;

/// @title Network Address Registry Interface
/// @notice Interface for managing and retrieving addresses across different networks.
interface IAddressRegistry {
/// @notice Retrieves an address by its identifier for a specified L2 chain
/// @param identifier The unique identifier associated with the address
/// @param l2ChainId The chain ID of the L2 network
/// @return The address associated with the given identifier on the specified chain
function getAddress(string memory identifier, uint256 l2ChainId) external view returns (address);

/// @notice Checks if an address is a contract for a given identifier and L2 chain
/// @param identifier The unique identifier associated with the address
/// @param l2ChainId The chain ID of the L2 network
/// @return True if the address is a contract, false otherwise
function isAddressContract(string memory identifier, uint256 l2ChainId) external view returns (bool);

/// @notice Checks if an address exists for a specified identifier and L2 chain
/// @param identifier The unique identifier associated with the address
/// @param l2ChainId The chain ID of the L2 network
/// @return True if the address exists, false otherwise
function isAddressRegistered(string memory identifier, uint256 l2ChainId) external view returns (bool);
}
14 changes: 14 additions & 0 deletions src/fps/addresses/10.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[[addresses]]
addr = "0x9679E26bf0C470521DE83Ad77BB1bf1e7312f739"
identifier = "DEPLOYER_EOA"
isContract = false

[[addresses]]
addr = "0xc0Da02939E1441F497fd74F78cE7Decb17B66529"
identifier = "COMPOUND_GOVERNOR_BRAVO"
isContract = true

[[addresses]]
addr = "0x316f9708bB98af7dA9c68C1C3b5e79039cD336E3"
identifier = "COMPOUND_CONFIGURATOR"
isContract = true
14 changes: 14 additions & 0 deletions src/fps/addresses/8453.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[[addresses]]
addr = "0x9679E26bf0C470521DE83Ad77BB1bf1e7312f739"
identifier = "DEPLOYER_EOA"
isContract = false

[[addresses]]
addr = "0xc0Da02939E1441F497fd74F78cE7Decb17B66529"
identifier = "COMPOUND_GOVERNOR_BRAVO"
isContract = true

[[addresses]]
addr = "0x316f9708bB98af7dA9c68C1C3b5e79039cD336E3"
identifier = "COMPOUND_CONFIGURATOR"
isContract = true
7 changes: 7 additions & 0 deletions src/fps/addresses/chainList.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[[chains]]
name = "OP Mainnet"
chain_id = 10

[[chains]]
name = "Base"
chain_id = 8453
13 changes: 13 additions & 0 deletions src/fps/doc/ADDRESS_REGISTRY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Address Registry

The address registry contract stores contract addresses on a single network. On construction, it reads in all of the configurations from the specified TOML configuration file. This TOML configuration file tells the address registry which L2 contracts to read in and store. As an example, if a task only touched the OP Mainnet contracts, the TOML file would only have a single entry

```toml
[[chains]]
name = "OP Mainnet"
chain_id = 10
```

## Usage

Addresses can be fetched by calling the `getAddress(string memory identifier, uint256 l2ChainId)` function. This function will return the address of the contract with the given identifier on the given chain. If the contract does not exist, the function will revert. If the l2ChainId is unsupported by this address registry instance, the function will revert.
26 changes: 26 additions & 0 deletions src/fps/utils/Constants.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
pragma solidity 0.8.15;

// Mainnet Chain Ids
uint256 constant BASE_CHAIN_ID = 8453;
uint256 constant OP_CHAIN_ID = 10;
uint256 constant MODE_CHAIN_ID = 34443;
uint256 constant ORDERLY_CHAIN_ID = 291;
uint256 constant RACE_CHAIN_ID = 6805;
uint256 constant ZORA_CHAIN_ID = 7777777;
uint256 constant LYRA_CHAIN_ID = 957;
uint256 constant METAL_CHAIN_ID = 1750;
uint256 constant BINARY_CHAIN_ID = 624;

// Testnet Chain Ids
uint256 constant BASE_SEPOLIA_CHAIN_ID = 84532;
uint256 constant OP_SEPOLIA_CHAIN_ID = 11155420;
uint256 constant MODE_SEPOLIA_CHAIN_ID = 919;
uint256 constant BASE_DEVNET_CHAIN_ID = 11763072;
uint256 constant METAL_SEPOLIA_CHAIN_ID = 1740;
uint256 constant RACE_SEPOLIA_CHAIN_ID = 6806;
uint256 constant ZORA_SEPOLIA_CHAIN_ID = 999999999;
uint256 constant OPLABS_DEVNET_CHAIN_ID = 11155421;

uint256 constant LOCAL_CHAIN_ID = 31337;

string constant SUPERCHAIN_REGISTRY_PATH = "lib/superchain-registry/superchain/extra/addresses/addresses.json";
3 changes: 3 additions & 0 deletions tasks/sep-dev-0/007-mt-cannon/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ETH_RPC_URL="https://ethereum-sepolia.publicnode.com"
OWNER_SAFE=0x4377BB0F0103992b31eC12b4d796a8687B8dC8E9
SAFE_NONCE=""
Loading

0 comments on commit 67e9657

Please sign in to comment.