refactor: move ethereum config to its own file, macro contract construction

buddies-main-deployment
James Prestwich 4 years ago
parent ec87181f32
commit 6536efb2a0
No known key found for this signature in database
GPG Key ID: 75A7F5C06D747046
  1. 2
      rust/Cargo.lock
  2. 2
      rust/optics-base/Cargo.toml
  3. 9
      rust/optics-base/config/default.toml
  4. 31
      rust/optics-base/src/abis/mod.rs
  5. 7
      rust/optics-base/src/main.rs
  6. 155
      rust/optics-base/src/settings.rs
  7. 109
      rust/optics-base/src/settings/ethereum.rs
  8. 101
      rust/optics-base/src/settings/mod.rs

2
rust/Cargo.lock generated

@ -1133,7 +1133,9 @@ dependencies = [
"ethers",
"ethers-contract",
"ethers-core",
"ethers-middleware",
"ethers-providers",
"ethers-signers",
"optics-core",
"serde 1.0.120",
"serde_json",

@ -14,6 +14,8 @@ serde = "1.0.120"
serde_json = { version = "1.0.61", default-features = false }
ethers = { git = "https://github.com/gakonst/ethers-rs" }
ethers-signers = { git = "https://github.com/gakonst/ethers-rs" }
ethers-middleware = { git = "https://github.com/gakonst/ethers-rs" }
ethers-core = { git = "https://github.com/gakonst/ethers-rs" }
ethers-providers = { git = "https://github.com/gakonst/ethers-rs" }
ethers-contract = { git = "https://github.com/gakonst/ethers-rs", features = ["abigen"] }

@ -2,12 +2,15 @@
slip44 = 52752
address = "0x0000000000000000000000000000000000000000"
chain = "Ethereum"
connection = { type = "Http", url = "http://localhost:8545" }
[home.config]
signer = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
connection = { type = "Http", url = "http://localhost:8545" }
[replicas]
[replicas.Celo]
[replicas.Ethereum]
slip44 = 60
address = "0x0000000000000000000000000000000000000000"
chain = "Ethereum"
connection = { type = "Ws", url = "wss://" }
[replicas.Ethereum.config]
connection = { type = "Ws", url = "wss://"}

@ -1,5 +1,4 @@
use async_trait::async_trait;
use ethers_contract::abigen;
use ethers_core::types::{Address, H256, U256};
use std::sync::Arc;
@ -8,17 +7,24 @@ use optics_core::{
Encode, Message, SignedUpdate, Update,
};
abigen!(
ReplicaContractInternal,
"optics-base/src/abis/ProcessingReplica.abi.json"
);
#[allow(missing_docs)]
mod contracts {
use ethers_contract::abigen;
abigen!(
ReplicaContractInternal,
"optics-base/src/abis/ProcessingReplica.abi.json"
);
abigen!(HomeContractInternal, "optics-base/src/abis/Home.abi.json");
}
/// A struct that provides access to an Ethereum replica contract
#[derive(Debug)]
pub struct ReplicaContract<M>
where
M: ethers_providers::Middleware,
{
contract: ReplicaContractInternal<M>,
contract: contracts::ReplicaContractInternal<M>,
slip44: u32,
}
@ -26,9 +32,11 @@ impl<M> ReplicaContract<M>
where
M: ethers_providers::Middleware,
{
/// Create a reference to a Replica at a specific Ethereum address on some
/// chain
pub fn at(slip44: u32, address: Address, provider: Arc<M>) -> Self {
Self {
contract: ReplicaContractInternal::new(address, provider),
contract: contracts::ReplicaContractInternal::new(address, provider),
slip44,
}
}
@ -170,14 +178,13 @@ where
}
}
abigen!(HomeContractInternal, "optics-base/src/abis/Home.abi.json");
/// A reference to a Home contract on some Ethereum chain
#[derive(Debug)]
pub struct HomeContract<M>
where
M: ethers_providers::Middleware,
{
contract: HomeContractInternal<M>,
contract: contracts::HomeContractInternal<M>,
slip44: u32,
}
@ -185,9 +192,11 @@ impl<M> HomeContract<M>
where
M: ethers_providers::Middleware,
{
/// Create a reference to a Home at a specific Ethereum address on some
/// chain
pub fn at(slip44: u32, address: Address, provider: Arc<M>) -> Self {
Self {
contract: HomeContractInternal::new(address, provider),
contract: contracts::HomeContractInternal::new(address, provider),
slip44,
}
}

@ -11,8 +11,11 @@
#![warn(missing_docs)]
#![warn(unused_extern_crates)]
mod abis;
mod settings;
/// Interfaces to the ethereum contracts
pub mod abis;
/// Settings and configuration from file
pub mod settings;
use optics_core::traits::{Home, Replica};
use std::collections::HashMap;

@ -1,155 +0,0 @@
use config::{Config, ConfigError, Environment, File};
use std::{collections::HashMap, convert::TryFrom, env};
use ethers_core::types::Address;
use ethers_providers::{Http, Provider, Ws};
use optics_core::traits::{Home, Replica};
/// Ethereum connection configuration
#[derive(Debug, serde::Deserialize)]
#[serde(tag = "type")]
pub enum EthereumConf {
Http { url: String },
Ws { url: String },
}
impl EthereumConf {
/// Try to convert this into a home contract
async fn try_into_home(&self, slip44: u32, address: Address) -> Result<Box<dyn Home>, String> {
let b: Box<dyn Home> = match self {
Self::Http { url } => {
let provider = Provider::<Http>::try_from(url.as_ref()).map_err(|_| "!url")?;
Box::new(crate::abis::HomeContract::at(
slip44,
address,
provider.into(),
))
}
Self::Ws { url } => {
let ws = Ws::connect(url).await.map_err(|_| "!ws connect")?;
let provider = Provider::new(ws);
Box::new(crate::abis::HomeContract::at(
slip44,
address,
provider.into(),
))
}
};
Ok(b)
}
/// Try to convert this into a replica contract
pub async fn try_into_replica(
&self,
slip44: u32,
address: Address,
) -> Result<Box<dyn Replica>, String> {
let b: Box<dyn Replica> = match self {
Self::Http { url } => {
let provider = Provider::<Http>::try_from(url.as_ref()).map_err(|_| "!url")?;
Box::new(crate::abis::ReplicaContract::at(
slip44,
address,
provider.into(),
))
}
Self::Ws { url } => {
let ws = Ws::connect(url).await.map_err(|_| "!ws connect")?;
let provider = Provider::new(ws);
Box::new(crate::abis::ReplicaContract::at(
slip44,
address,
provider.into(),
))
}
};
Ok(b)
}
}
/// A connection to _some_ blockchain.
///
/// Specify the chain name (enum variant) in toml under the `chain` key
/// Specify the connection details as a toml object under the `connection` key.
#[derive(Debug, serde::Deserialize)]
#[serde(tag = "chain", content = "connection")]
pub enum ChainConnection {
Ethereum(EthereumConf),
}
/// A chain setup is a slip44 ID, an address on that chain (where the home or
/// replica is deployed) and details for connecting to the chain API.
#[derive(Debug, serde::Deserialize)]
pub struct ChainSetup {
slip44: u32,
address: String,
#[serde(flatten)]
connection: ChainConnection,
}
impl ChainSetup {
pub async fn try_into_home(&self) -> Result<Box<dyn Home>, String> {
match &self.connection {
ChainConnection::Ethereum(conf) => {
conf.try_into_home(self.slip44, self.address.parse().map_err(|_| "!address")?)
.await
}
}
}
pub async fn try_into_replica(&self) -> Result<Box<dyn Replica>, String> {
match &self.connection {
ChainConnection::Ethereum(conf) => {
conf.try_into_replica(self.slip44, self.address.parse().map_err(|_| "!address")?)
.await
}
}
}
}
/// Settings. Usually this should be treated as a base config and used as
/// follows:
///
/// ```
/// use optics_base::settings::*;
///
/// pub struct OtherSettings { /* anything */ };
///
/// #[derive(Debug, serde::Deseirialize)]
/// pub struct MySettings {
/// #[serde(flatten)]
/// base_settings: Settings,
/// #[serde(flatten)]
/// other_settings: (),
/// }
///
/// // Make sure to define MySettings::new()
/// impl MySettings {
/// fn new() -> Self {
/// unimplemented!()
/// }
/// }
/// ```
#[derive(Debug, serde::Deserialize)]
pub struct Settings {
pub home: ChainSetup,
pub replicas: HashMap<String, ChainSetup>,
}
impl Settings {
pub fn new() -> Result<Self, ConfigError> {
let mut s = Config::new();
s.merge(File::with_name("config/default"))?;
let env = env::var("RUN_MODE").unwrap_or_else(|_| "development".into());
s.merge(File::with_name(&format!("config/{}", env)).required(false))?;
// Add in settings from the environment (with a prefix of OPTRELAY)
// Eg.. `OPTRELAY_DEBUG=1 would set the `debug` key
s.merge(Environment::with_prefix("OPTRELAY"))?;
s.try_into()
}
}

@ -0,0 +1,109 @@
use std::convert::TryFrom;
use ethers_core::types::Address;
use optics_core::traits::{Home, Replica};
/// Ethereum connection configuration
#[derive(Debug, serde::Deserialize)]
#[serde(tag = "type")]
pub enum EthereumConnection {
/// HTTP connection details
Http {
/// Fully qualified string to connect to
url: String,
},
/// Websocket connection details
Ws {
/// Fully qualified string to connect to
url: String,
},
}
// Construct boxed contracts in a big "if-else" chain to handle multiple
// combinations of middleware.
macro_rules! construct_box_contract {
($contract:ident, $slip44:expr, $address:expr, $provider:expr, $signer:expr) => {{
if let Some(signer) = $signer {
let provider = ethers_middleware::SignerMiddleware::new($provider, signer);
Box::new(crate::abis::$contract::at(
$slip44,
$address,
provider.into(),
))
} else {
Box::new(crate::abis::$contract::at(
$slip44,
$address,
$provider.into(),
))
}
}};
}
macro_rules! construct_ws_box_contract {
($contract:ident, $slip44:expr, $address:expr, $url:expr, $signer:expr) => {{
let ws = ethers_providers::Ws::connect($url)
.await
.map_err(|_| "!ws connect")?;
let provider = ethers_providers::Provider::new(ws);
construct_box_contract!($contract, $slip44, $address, provider, $signer)
}};
}
macro_rules! construct_http_box_contract {
($contract:ident, $slip44:expr, $address:expr, $url:expr, $signer:expr) => {{
let provider =
ethers_providers::Provider::<ethers_providers::Http>::try_from($url.as_ref())
.map_err(|_| "!url")?;
construct_box_contract!($contract, $slip44, $address, provider, $signer)
}};
}
/// Ethereum configuration
#[derive(Debug, serde::Deserialize)]
pub struct EthereumConf {
connection: EthereumConnection,
signer: Option<String>,
}
impl EthereumConf {
fn signer(&self) -> Option<ethers_signers::LocalWallet> {
self.signer.clone().map(|s| s.parse().expect("!valid key"))
}
/// Try to convert this into a home contract
pub async fn try_into_home(
&self,
slip44: u32,
address: Address,
) -> Result<Box<dyn Home>, String> {
let b: Box<dyn Home> = match &self.connection {
EthereumConnection::Http { url } => {
construct_http_box_contract!(HomeContract, slip44, address, url, self.signer())
}
EthereumConnection::Ws { url } => {
construct_ws_box_contract!(HomeContract, slip44, address, url, self.signer())
}
};
Ok(b)
}
/// Try to convert this into a replica contract
pub async fn try_into_replica(
&self,
slip44: u32,
address: Address,
) -> Result<Box<dyn Replica>, String> {
let b: Box<dyn Replica> = match &self.connection {
EthereumConnection::Http { url } => {
construct_http_box_contract!(ReplicaContract, slip44, address, url, self.signer())
}
EthereumConnection::Ws { url } => {
construct_ws_box_contract!(ReplicaContract, slip44, address, url, self.signer())
}
};
Ok(b)
}
}

@ -0,0 +1,101 @@
use config::{Config, ConfigError, Environment, File};
use std::{collections::HashMap, env};
use optics_core::traits::{Home, Replica};
/// Ethereum configuration
pub mod ethereum;
use ethereum::EthereumConf;
/// A connection to _some_ blockchain.
///
/// Specify the chain name (enum variant) in toml under the `chain` key
/// Specify the connection details as a toml object under the `connection` key.
#[derive(Debug, serde::Deserialize)]
#[serde(tag = "chain", content = "config")]
pub enum ChainConf {
/// Ethereum configuration
Ethereum(EthereumConf),
}
/// A chain setup is a slip44 ID, an address on that chain (where the home or
/// replica is deployed) and details for connecting to the chain API.
#[derive(Debug, serde::Deserialize)]
pub struct ChainSetup {
slip44: u32,
address: String,
#[serde(flatten)]
chain: ChainConf,
}
impl ChainSetup {
/// Try to convert the chain setting into a Home contract
pub async fn try_into_home(&self) -> Result<Box<dyn Home>, String> {
match &self.chain {
ChainConf::Ethereum(conf) => {
conf.try_into_home(self.slip44, self.address.parse().map_err(|_| "!address")?)
.await
}
}
}
/// Try to convert the chain setting into a replica contract
pub async fn try_into_replica(&self) -> Result<Box<dyn Replica>, String> {
match &self.chain {
ChainConf::Ethereum(conf) => {
conf.try_into_replica(self.slip44, self.address.parse().map_err(|_| "!address")?)
.await
}
}
}
}
/// Settings. Usually this should be treated as a base config and used as
/// follows:
///
/// ```
/// use optics_base::settings::*;
///
/// pub struct OtherSettings { /* anything */ };
///
/// #[derive(Debug, serde::Deseirialize)]
/// pub struct MySettings {
/// #[serde(flatten)]
/// base_settings: Settings,
/// #[serde(flatten)]
/// other_settings: (),
/// }
///
/// // Make sure to define MySettings::new()
/// impl MySettings {
/// fn new() -> Self {
/// unimplemented!()
/// }
/// }
/// ```
#[derive(Debug, serde::Deserialize)]
pub struct Settings {
/// The home configuration
pub home: ChainSetup,
/// The replica configurations
pub replicas: HashMap<String, ChainSetup>,
}
impl Settings {
/// Read settings from the config file
pub fn new() -> Result<Self, ConfigError> {
let mut s = Config::new();
s.merge(File::with_name("config/default"))?;
let env = env::var("RUN_MODE").unwrap_or_else(|_| "development".into());
s.merge(File::with_name(&format!("config/{}", env)).required(false))?;
// Add in settings from the environment (with a prefix of OPTRELAY)
// Eg.. `OPTRELAY_DEBUG=1 would set the `debug` key
s.merge(Environment::with_prefix("OPTRELAY"))?;
s.try_into()
}
}
Loading…
Cancel
Save