Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 1 addition & 2 deletions .github/workflows/bindings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/contracts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 13 additions & 1 deletion bindings/src/addresses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,19 @@ pub fn protocol_adapter_deployments_map() -> HashMap<NamedChain, Address> {
),
(
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
),
])
}
Expand Down
150 changes: 118 additions & 32 deletions bindings/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,57 +3,143 @@ use alloy_chains::NamedChain;
use std::env;
use thiserror::Error;

pub type AlchemyResult<T> = Result<T, AlchemyError>;
pub type RpcUrlResult<T> = Result<T, RpcUrlError>;

#[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<Url> {
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<Url> {
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<Url> {
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<T> = RpcUrlResult<T>;
/// 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<Url> {
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<Url> {
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),
}
}
4 changes: 2 additions & 2 deletions bindings/tests/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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<DynProvider> {
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))
Expand Down
4 changes: 4 additions & 0 deletions contracts/.env-example
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@
## The Alchemy API key (see https://www.alchemy.com/)

export ALCHEMY_API_KEY=<ALCHEMY_API_KEY>

## The Nownodes API key (see https://nownodes.io/)

export NOWNODES_API_KEY=<NOWNODES_API_KEY>
4 changes: 4 additions & 0 deletions contracts/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
13 changes: 13 additions & 0 deletions contracts/script/DeployProtocolAdapter.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
36 changes: 8 additions & 28 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand All @@ -89,41 +91,19 @@ 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)

# Publish contracts
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
Expand Down
Loading