Skip to main content

evmlib/
utils.rs

1// Copyright 2024 MaidSafe.net limited.
2//
3// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
4// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
5// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
6// KIND, either express or implied. Please review the Licences for the specific language governing
7// permissions and limitations relating to use of the SAFE Network Software.
8
9#![allow(dead_code)]
10
11use crate::common::{Address, Hash};
12use crate::{CustomNetwork, Network};
13use alloy::network::Ethereum;
14use alloy::providers::fillers::{
15    BlobGasFiller, ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller,
16    SimpleNonceManager,
17};
18use alloy::providers::{Identity, ProviderBuilder, RootProvider};
19use alloy::transports::http::reqwest;
20use std::env;
21
22const MAINNET_ID: u8 = 1;
23const ALPHANET_ID: u8 = 2;
24
25/// environment variable to connect to a custom EVM network
26pub const RPC_URL: &str = "RPC_URL";
27const RPC_URL_BUILD_TIME_VAL: Option<&str> = option_env!("RPC_URL");
28pub const PAYMENT_TOKEN_ADDRESS: &str = "PAYMENT_TOKEN_ADDRESS";
29const PAYMENT_TOKEN_ADDRESS_BUILD_TIME_VAL: Option<&str> = option_env!("PAYMENT_TOKEN_ADDRESS");
30pub const DATA_PAYMENTS_ADDRESS: &str = "DATA_PAYMENTS_ADDRESS";
31const DATA_PAYMENTS_ADDRESS_BUILD_TIME_VAL: Option<&str> = option_env!("DATA_PAYMENTS_ADDRESS");
32pub const MERKLE_PAYMENTS_ADDRESS: &str = "MERKLE_PAYMENTS_ADDRESS";
33const MERKLE_PAYMENTS_ADDRESS_BUILD_TIME_VAL: Option<&str> = option_env!("MERKLE_PAYMENTS_ADDRESS");
34
35#[derive(thiserror::Error, Debug)]
36pub enum Error {
37    #[error("Failed to get EVM network: {0}")]
38    FailedToGetEvmNetwork(String),
39}
40
41/// Generate a random Address.
42pub fn dummy_address() -> Address {
43    use rand::Rng;
44    Address::new(rand::rngs::OsRng.r#gen())
45}
46
47/// Generate a random Hash.
48pub fn dummy_hash() -> Hash {
49    use rand::Rng;
50    Hash::new(rand::rngs::OsRng.r#gen())
51}
52
53use std::sync::OnceLock;
54
55static EVM_NETWORK: OnceLock<Network> = OnceLock::new();
56
57/// Initialize the EVM Network.
58///
59/// Try to obtain it first from environment variables. If that fails and `local` is true,
60/// try to get it from hardcoded values. Lastly, attempt to obtain it based on the network ID,
61/// where 1 is reserved for the mainnet, 2 is reserved for the alpha network, and any other value
62/// between 3 and 255 is reserved for testnets. In the case of a testnet, the network to use must
63/// be configured via the environment variables. We can't just default to Sepolia because sometimes
64/// we want to use Anvil.
65///
66/// If all of these fail an error will be returned. It doesn't really make sense to have a default
67/// for the EVM network. Doing so actually results in confusion for users where sometimes payments
68/// can be rejected because they are on the wrong network.
69pub fn get_evm_network(local: bool, network_id: Option<u8>) -> Result<Network, Error> {
70    if let Some(network) = EVM_NETWORK.get() {
71        return Ok(network.clone());
72    }
73
74    let res = match get_evm_network_from_env() {
75        Ok(evm_network) => Ok(evm_network),
76        Err(_) if local => Ok(local_evm_network_hardcoded()),
77        Err(_) => {
78            if let Some(id) = network_id {
79                match id {
80                    MAINNET_ID => {
81                        info!("Using Arbitrum One based on network ID {}", id);
82                        Ok(Network::ArbitrumOne)
83                    }
84                    ALPHANET_ID => {
85                        info!("Using Arbitrum Sepolia Test based on network ID {}", id);
86                        Ok(Network::ArbitrumSepoliaTest)
87                    }
88                    _ => {
89                        error!(
90                            "Network ID {} requires EVM network configuration via environment variables",
91                            id
92                        );
93                        Err(Error::FailedToGetEvmNetwork(format!(
94                            "Network ID {id} requires EVM network to be configured via environment variables"
95                        )))
96                    }
97                }
98            } else {
99                error!("Failed to obtain the desired EVM network via any means");
100                Err(Error::FailedToGetEvmNetwork(
101                    "Failed to obtain the desired EVM network via any means".to_string(),
102                ))
103            }
104        }
105    };
106
107    if let Ok(network) = res.as_ref() {
108        let _ = EVM_NETWORK.set(network.clone());
109    }
110
111    res
112}
113
114/// Get the `Network` from environment variables.
115///
116/// Returns an error if we cannot obtain the network from any means.
117fn get_evm_network_from_env() -> Result<Network, Error> {
118    let evm_vars = [
119        env::var(RPC_URL)
120            .ok()
121            .or_else(|| RPC_URL_BUILD_TIME_VAL.map(|s| s.to_string())),
122        env::var(PAYMENT_TOKEN_ADDRESS)
123            .ok()
124            .or_else(|| PAYMENT_TOKEN_ADDRESS_BUILD_TIME_VAL.map(|s| s.to_string())),
125        env::var(DATA_PAYMENTS_ADDRESS)
126            .ok()
127            .or_else(|| DATA_PAYMENTS_ADDRESS_BUILD_TIME_VAL.map(|s| s.to_string())),
128    ]
129    .into_iter()
130    .map(|var| {
131        var.ok_or(Error::FailedToGetEvmNetwork(format!(
132            "missing env var, make sure to set all of: {RPC_URL}, {PAYMENT_TOKEN_ADDRESS}, {DATA_PAYMENTS_ADDRESS}"
133        )))
134    })
135    .collect::<Result<Vec<String>, Error>>();
136
137    let use_local_evm = std::env::var("EVM_NETWORK")
138        .map(|v| v == "local")
139        .unwrap_or(false);
140    if use_local_evm {
141        info!("Using local EVM network as EVM_NETWORK is set to 'local'");
142    }
143
144    let use_arbitrum_one = std::env::var("EVM_NETWORK")
145        .map(|v| v == "arbitrum-one")
146        .unwrap_or(false);
147
148    let use_arbitrum_sepolia_test = std::env::var("EVM_NETWORK")
149        .map(|v| v == "arbitrum-sepolia-test")
150        .unwrap_or(false);
151
152    if use_arbitrum_one {
153        info!("Using Arbitrum One EVM network as EVM_NETWORK is set to 'arbitrum-one'");
154        Ok(Network::ArbitrumOne)
155    } else if use_arbitrum_sepolia_test {
156        info!(
157            "Using Arbitrum Sepolia Test EVM network as EVM_NETWORK is set to 'arbitrum-sepolia-test'"
158        );
159        Ok(Network::ArbitrumSepoliaTest)
160    } else if let Ok(evm_vars) = evm_vars {
161        info!("Using custom EVM network from environment variables");
162        let merkle_addr = env::var(MERKLE_PAYMENTS_ADDRESS)
163            .ok()
164            .or_else(|| MERKLE_PAYMENTS_ADDRESS_BUILD_TIME_VAL.map(|s| s.to_string()));
165
166        let network = CustomNetwork::new(
167            &evm_vars[0],
168            &evm_vars[1],
169            &evm_vars[2],
170            merkle_addr.as_deref(),
171        );
172        Ok(Network::Custom(network))
173    } else if use_local_evm {
174        Ok(local_evm_network_hardcoded())
175    } else {
176        error!("Failed to obtain the desired EVM network through environment variables");
177        Err(Error::FailedToGetEvmNetwork(
178            "Failed to obtain the desired EVM network through environment variables".to_string(),
179        ))
180    }
181}
182
183/// Get the `Network::Custom` from the hardcoded values.
184fn local_evm_network_hardcoded() -> Network {
185    // Merkle payments address is deterministic when deployed by Anvil's third default account (Charlie)
186    // Deployed at nonce 0 by account 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
187    let network = CustomNetwork::new(
188        "http://localhost:61611",
189        "0x5FbDB2315678afecb367f032d93F642f64180aa3",
190        "0x8464135c8F25Da09e49BC8782676a84730C318bC",
191        Some("0x663F3ad617193148711d28f5334eE4Ed07016602"),
192    );
193    Network::Custom(network)
194}
195
196#[allow(clippy::type_complexity)]
197pub fn http_provider(
198    rpc_url: reqwest::Url,
199) -> FillProvider<
200    JoinFill<
201        JoinFill<
202            Identity,
203            JoinFill<GasFiller, JoinFill<BlobGasFiller, JoinFill<NonceFiller, ChainIdFiller>>>,
204        >,
205        NonceFiller<SimpleNonceManager>,
206    >,
207    RootProvider,
208    Ethereum,
209> {
210    ProviderBuilder::new()
211        .with_simple_nonce_management()
212        .connect_http(rpc_url)
213}