diff --git a/.github/workflows/bindings.yml b/.github/workflows/bindings.yml index 6b53b6c6..56226c4e 100644 --- a/.github/workflows/bindings.yml +++ b/.github/workflows/bindings.yml @@ -12,6 +12,7 @@ jobs: runs-on: macos-latest env: ALCHEMY_API_KEY: ${{ secrets.ALCHEMY_API_KEY }} + NOWNODES_API_KEY: ${{ secrets.NOWNODES_API_KEY }} steps: - name: Checkout repository @@ -30,8 +31,6 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 - with: - version: v1.7.0 - name: Show Foundry version run: forge --version diff --git a/.github/workflows/contracts.yml b/.github/workflows/contracts.yml index abeddaa5..228b09c7 100644 --- a/.github/workflows/contracts.yml +++ b/.github/workflows/contracts.yml @@ -22,8 +22,6 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 - with: - version: v1.7.0 - name: Show Foundry version run: forge --version diff --git a/bindings/src/addresses.rs b/bindings/src/addresses.rs index 3aec319e..845b883b 100644 --- a/bindings/src/addresses.rs +++ b/bindings/src/addresses.rs @@ -35,7 +35,19 @@ pub fn protocol_adapter_deployments_map() -> HashMap { ), ( NamedChain::Monad, - address!("0x2D2Fa19aFdbb20DC73737ca5f075cfAE00Cd90C2"), + address!("0x2D2Fa19aFdbb20DC73737ca5f075cfAE00Cd90C2"), // NOTE: Non-deterministic deployment + ), + ( + NamedChain::StableMainnet, + address!("0x2D2Fa19aFdbb20DC73737ca5f075cfAE00Cd90C2"), // NOTE: Non-deterministic deployment + ), + ( + NamedChain::MegaEth, + address!("0x2D2Fa19aFdbb20DC73737ca5f075cfAE00Cd90C2"), // NOTE: Non-deterministic deployment + ), + ( + NamedChain::Aurora, + address!("0x2D2Fa19aFdbb20DC73737ca5f075cfAE00Cd90C2"), // NOTE: Non-deterministic deployment ), ]) } diff --git a/bindings/src/helpers.rs b/bindings/src/helpers.rs index 64b4e653..a05d8e8d 100644 --- a/bindings/src/helpers.rs +++ b/bindings/src/helpers.rs @@ -3,57 +3,143 @@ use alloy_chains::NamedChain; use std::env; use thiserror::Error; -pub type AlchemyResult = Result; +pub type RpcUrlResult = Result; #[derive(Error, Debug, Clone)] -pub enum AlchemyError { - #[error("The alchemy subdomain was not found for this chain.")] +pub enum RpcUrlError { + #[error("No RPC provider subdomain was found for this chain.")] SubdomainNotFound, - #[error("The Alchemy API key is not set in the environment.")] + #[error("The RPC provider API key is not set in the environment.")] ApiKeyEnvVarNotSet, #[error("The url could not be parsed.")] UrlParsingError, } -/// Returns the Alchemy RPC URL for the given chain. -pub fn alchemy_url(chain: &NamedChain) -> AlchemyResult { - dotenvy::dotenv().ok(); +/// The RPC providers we can build URLs for. +/// +/// Each variant knows how to turn a `(subdomain, api_key)` pair into a URL and +/// which environment variable holds its API key. Add a variant here when +/// onboarding a new provider, then wire its chains up in [`rpc_provider`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RpcProvider { + Alchemy, + NowNodes, +} + +impl RpcProvider { + /// The environment variable holding this provider's API key. + pub const fn api_key_env_var(self) -> &'static str { + match self { + RpcProvider::Alchemy => "ALCHEMY_API_KEY", + RpcProvider::NowNodes => "NOWNODES_API_KEY", + } + } + + /// Builds the RPC URL for the given subdomain, reading the API key from the + /// environment. + fn build_url(self, subdomain: &str) -> RpcUrlResult { + let api_key = + env::var(self.api_key_env_var()).map_err(|_| RpcUrlError::ApiKeyEnvVarNotSet)?; - format!( - "https://{subdomain}.g.alchemy.com/v2/{api_key}", - subdomain = alchemy_subdomain(chain)?, - api_key = env::var("ALCHEMY_API_KEY").map_err(|_| AlchemyError::ApiKeyEnvVarNotSet)? - ) - .parse() - .map_err(|_| AlchemyError::UrlParsingError) + let url = match self { + RpcProvider::Alchemy => format!("https://{subdomain}.g.alchemy.com/v2/{api_key}"), + RpcProvider::NowNodes => format!("https://{subdomain}.nownodes.io/{api_key}"), + }; + + url.parse().map_err(|_| RpcUrlError::UrlParsingError) + } } -/// Returns the Alchemy subdomain for the given chain. -pub fn alchemy_subdomain(chain: &NamedChain) -> AlchemyResult<&'static str> { +/// Returns the RPC provider and subdomain configured for the given chain. +/// +/// This is the single place that maps chains to providers; extend it when a new +/// chain is added or moved between providers. Chains served by Alchemy and those +/// served by NowNodes are matched side by side here. +pub fn rpc_provider(chain: &NamedChain) -> RpcUrlResult<(RpcProvider, &'static str)> { use NamedChain::*; + use RpcProvider::*; - match chain { - Mainnet => Ok("eth-mainnet"), - Sepolia => Ok("eth-sepolia"), + Ok(match chain { + // Alchemy + Mainnet => (Alchemy, "eth-mainnet"), + Sepolia => (Alchemy, "eth-sepolia"), + // + Arbitrum => (Alchemy, "arb-mainnet"), + ArbitrumSepolia => (Alchemy, "arb-sepolia"), // - Arbitrum => Ok("arb-mainnet"), - ArbitrumSepolia => Ok("arb-sepolia"), + Optimism => (Alchemy, "opt-mainnet"), + OptimismSepolia => (Alchemy, "opt-sepolia"), // - Optimism => Ok("opt-mainnet"), - OptimismSepolia => Ok("opt-sepolia"), + Base => (Alchemy, "base-mainnet"), + BaseSepolia => (Alchemy, "base-sepolia"), // - Base => Ok("base-mainnet"), - BaseSepolia => Ok("base-sepolia"), + Polygon => (Alchemy, "polygon-mainnet"), + PolygonAmoy => (Alchemy, "polygon-amoy"), // - Polygon => Ok("polygon-mainnet"), - PolygonAmoy => Ok("polygon-amoy"), + BinanceSmartChain => (Alchemy, "bnb-mainnet"), + BinanceSmartChainTestnet => (Alchemy, "bnb-testnet"), // - BinanceSmartChain => Ok("bnb-mainnet"), - BinanceSmartChainTestnet => Ok("bnb-testnet"), + Monad => (Alchemy, "monad-mainnet"), + MonadTestnet => (Alchemy, "monad-testnet"), // - Monad => Ok("monad-mainnet"), - MonadTestnet => Ok("monad-testnet"), + StableMainnet => (Alchemy, "stable-mainnet"), + StableTestnet => (Alchemy, "stable-testnet"), + // + MegaEth => (Alchemy, "megaeth-mainnet"), + MegaEthTestnet => (Alchemy, "megaeth-testnet"), + + // NowNodes + Aurora => (NowNodes, "aurora"), + + _ => return Err(RpcUrlError::SubdomainNotFound), + }) +} + +/// Returns the RPC URL for the given chain, using whichever provider is +/// configured for it in [`rpc_provider`]. +pub fn rpc_url(chain: &NamedChain) -> RpcUrlResult { + dotenvy::dotenv().ok(); + + let (provider, subdomain) = rpc_provider(chain)?; + provider.build_url(subdomain) +} + +// --------------------------------------------------------------------------- +// Backwards-compatible API +// +// The helpers below predate [`rpc_url`]/[`rpc_provider`] and are kept so +// existing callers keep compiling. Prefer [`rpc_url`] for new code. +// --------------------------------------------------------------------------- + +/// Backwards-compatible alias for [`RpcUrlResult`]. +pub type AlchemyResult = RpcUrlResult; +/// Backwards-compatible alias for [`RpcUrlError`]. +pub type AlchemyError = RpcUrlError; + +/// Returns the Alchemy RPC URL for the given chain. +pub fn alchemy_url(chain: &NamedChain) -> RpcUrlResult { + dotenvy::dotenv().ok(); + RpcProvider::Alchemy.build_url(alchemy_subdomain(chain)?) +} + +/// Returns the Alchemy subdomain for the given chain. +pub fn alchemy_subdomain(chain: &NamedChain) -> RpcUrlResult<&'static str> { + match rpc_provider(chain)? { + (RpcProvider::Alchemy, subdomain) => Ok(subdomain), + _ => Err(RpcUrlError::SubdomainNotFound), + } +} + +/// Returns the NowNodes RPC URL for the given chain. +pub fn nownodes_url(chain: &NamedChain) -> RpcUrlResult { + dotenvy::dotenv().ok(); + RpcProvider::NowNodes.build_url(nownodes_subdomain(chain)?) +} - _ => Err(AlchemyError::SubdomainNotFound), +/// Returns the NowNodes subdomain for the given chain. +pub fn nownodes_subdomain(chain: &NamedChain) -> RpcUrlResult<&'static str> { + match rpc_provider(chain)? { + (RpcProvider::NowNodes, subdomain) => Ok(subdomain), + _ => Err(RpcUrlError::SubdomainNotFound), } } diff --git a/bindings/tests/contract.rs b/bindings/tests/contract.rs index a672a7a6..90d54b99 100644 --- a/bindings/tests/contract.rs +++ b/bindings/tests/contract.rs @@ -8,7 +8,7 @@ use anoma_pa_evm_bindings::addresses::protocol_adapter_deployments_map; use anoma_pa_evm_bindings::contract::protocol_adapter; use anoma_pa_evm_bindings::generated::protocol_adapter; use anoma_pa_evm_bindings::generated::versioning_lib_external; -use anoma_pa_evm_bindings::helpers::alchemy_url; +use anoma_pa_evm_bindings::helpers::rpc_url; #[tokio::test] async fn versions_of_deployed_protocol_adapters_match_the_expected_version() { @@ -72,7 +72,7 @@ async fn call_executes_the_empty_tx_on_all_supported_chains() { async fn pa_instance( chain: &NamedChain, ) -> protocol_adapter::ProtocolAdapter::ProtocolAdapterInstance { - let rpc_url = alchemy_url(chain).expect("Couldn't get RPC URL for chain"); + let rpc_url = rpc_url(chain).expect("Couldn't get RPC URL for chain"); let provider = ProviderBuilder::new() .connect_anvil_with_wallet_and_config(|a| a.fork(rpc_url)) diff --git a/contracts/.env-example b/contracts/.env-example index 33ce41bb..de4c019d 100644 --- a/contracts/.env-example +++ b/contracts/.env-example @@ -3,3 +3,7 @@ ## The Alchemy API key (see https://www.alchemy.com/) export ALCHEMY_API_KEY= + +## The Nownodes API key (see https://nownodes.io/) + +export NOWNODES_API_KEY= diff --git a/contracts/foundry.toml b/contracts/foundry.toml index b1b28054..b7c083c1 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -71,5 +71,9 @@ bsc = "https://bnb-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}" bsc-testnet = "https://bnb-testnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}" monad = "https://monad-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}" monad-testnet = "https://monad-testnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}" +stable-mainnet = "https://stable-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}" +megaeth = "https://megaeth-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}" + +aurora = "https://aurora.nownodes.io/${NOWNODES_API_KEY}" localhost = "http://localhost:8545" diff --git a/contracts/script/DeployProtocolAdapter.s.sol b/contracts/script/DeployProtocolAdapter.s.sol index 01f83225..a94e1901 100644 --- a/contracts/script/DeployProtocolAdapter.s.sol +++ b/contracts/script/DeployProtocolAdapter.s.sol @@ -64,9 +64,22 @@ contract DeployProtocolAdapter is Script { name: "bsc-testnet", chainId: 97, riscZeroVerifierRouter: 0x7C1B7b8fEB636eA9Ecd32152Bce2744a0EEf39C7 }); _supportNetwork({name: "bsc", chainId: 56, riscZeroVerifierRouter: 0x7C1B7b8fEB636eA9Ecd32152Bce2744a0EEf39C7}); + _supportNetwork({ name: "monad", chainId: 143, riscZeroVerifierRouter: 0x8cFdF6D8D1b141897D542aa07Afd27e37694dF7f }); + + _supportNetwork({ + name: "stable-mainnet", chainId: 988, riscZeroVerifierRouter: 0x8cFdF6D8D1b141897D542aa07Afd27e37694dF7f + }); + + _supportNetwork({ + name: "megaeth", chainId: 4326, riscZeroVerifierRouter: 0x8cFdF6D8D1b141897D542aa07Afd27e37694dF7f + }); + + _supportNetwork({ + name: "aurora", chainId: 1313161554, riscZeroVerifierRouter: 0x8cFdF6D8D1b141897D542aa07Afd27e37694dF7f + }); } /// @notice Deploys the protocol adapter contract on supported networks and allows for test deployments. diff --git a/justfile b/justfile index 0e877df7..003be86a 100644 --- a/justfile +++ b/justfile @@ -61,12 +61,14 @@ contracts-gen-bindings: contracts-simulate chain *args: @echo "IS_TEST_DEPLOYMENT: $IS_TEST_DEPLOYMENT" @echo "EMERGENCY_STOP_CALLER: $EMERGENCY_STOP_CALLER" + @just contracts-clean cd contracts && forge script script/DeployProtocolAdapter.s.sol:DeployProtocolAdapter \ --sig "run(bool,address)" $IS_TEST_DEPLOYMENT $EMERGENCY_STOP_CALLER \ --rpc-url {{chain}} {{ args }} # Deploy protocol adapter contracts-deploy deployer chain *args: + @just contracts-clean cd contracts && forge script script/DeployProtocolAdapter.s.sol:DeployProtocolAdapter \ --sig "run(bool,address)" $IS_TEST_DEPLOYMENT $EMERGENCY_STOP_CALLER \ --broadcast --rpc-url {{chain}} --account {{deployer}} {{ args }} @@ -89,6 +91,12 @@ contracts-verify-etherscan address chain *args: src/ProtocolAdapter.sol:ProtocolAdapter \ --chain {{chain}} --verifier etherscan --watch {{ args }} +# Verify on a custom explorer +contracts-verify-custom address chain verifier-url *args: + cd contracts && forge verify-contract {{address}} \ + src/ProtocolAdapter.sol:ProtocolAdapter \ + --chain {{chain}} --verifier-url {{verifier-url}} --watch {{ args }} + # Verify on both sourcify and etherscan contracts-verify address chain: (contracts-verify-sourcify address chain) (contracts-verify-etherscan address chain) @@ -96,34 +104,6 @@ contracts-verify address chain: (contracts-verify-sourcify address chain) (contr contracts-publish version *args: cd contracts && forge soldeer push anoma-pa-evm~{{version}} {{ args }} -# --- RISC Zero Verifier --- - -# Simulate RISC Zero verifier deployment (dry-run) -risczero-simulate admin guardian chain *args: - cd contracts && FOUNDRY_EVM_VERSION=cancun forge script \ - test/script/DeployRiscZeroContracts.s.sol:DeployRiscZeroContracts \ - --sig "run(address,address)" {{admin}} {{guardian}} \ - --rpc-url {{chain}} {{ args }} - -# Deploy RISC Zero verifier (router + groth16 + emergency stop) -risczero-deploy deployer admin guardian chain *args: - cd contracts && FOUNDRY_EVM_VERSION=cancun forge script \ - test/script/DeployRiscZeroContracts.s.sol:DeployRiscZeroContracts \ - --sig "run(address,address)" {{admin}} {{guardian}} \ - --broadcast --rpc-url {{chain}} --account {{deployer}} {{ args }} - -# Verify RISC Zero verifier contracts on Sourcify -risczero-verify groth16 estop router chain *args: - cd contracts && FOUNDRY_EVM_VERSION=cancun forge verify-contract {{groth16}} \ - dependencies/risc0-risc0-ethereum-3.0.1/contracts/src/groth16/RiscZeroGroth16Verifier.sol:RiscZeroGroth16Verifier \ - --chain {{chain}} --verifier sourcify {{ args }} - cd contracts && FOUNDRY_EVM_VERSION=cancun forge verify-contract {{estop}} \ - dependencies/risc0-risc0-ethereum-3.0.1/contracts/src/RiscZeroVerifierEmergencyStop.sol:RiscZeroVerifierEmergencyStop \ - --chain {{chain}} --verifier sourcify {{ args }} - cd contracts && FOUNDRY_EVM_VERSION=cancun forge verify-contract {{router}} \ - dependencies/risc0-risc0-ethereum-3.0.1/contracts/src/RiscZeroVerifierRouter.sol:RiscZeroVerifierRouter \ - --chain {{chain}} --verifier sourcify {{ args }} - # --- Bindings --- # Clean bindings