Skip to content
This repository has been archived by the owner on Mar 23, 2021. It is now read-only.

Commit

Permalink
Infer DAI token from Ethereum chain ID
Browse files Browse the repository at this point in the history
This introduces the ethereum::Address type into our config::File
struct. Unfortunately, this opens a can of worms with a bug in
the serde-hex library. See [0] for more details.

We work around this by removing the dependency on serde-hex from our
Ethereum deserialization code and instead hand-roll everything for
our usecase.

This also has the advantage that we can now again just use our structs
directly in the route handlers of warp instead of serde_json::Value.
See [1] for more on this.

[0]: fspmarshall/serde-hex#8
[1]: #2405
  • Loading branch information
thomaseizinger committed Aug 18, 2020
1 parent 3ec3cec commit 7228d36
Show file tree
Hide file tree
Showing 16 changed files with 209 additions and 135 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 20 additions & 16 deletions api_tests/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,8 @@ export class E2ETestActorConfig {
};
}

private createLedgerConnectors(
ledgerConfig: LedgerConfig
): LedgerConnectors {
const config: LedgerConnectors = {};
private createLedgerConnectors(ledgerConfig: LedgerConfig): LedgerConfigs {
const config: LedgerConfigs = {};

if (ledgerConfig.bitcoin) {
config.bitcoin = bitcoinConnector(ledgerConfig.bitcoin);
Expand Down Expand Up @@ -106,26 +104,31 @@ export class E2ETestActorConfig {
}
}

interface LedgerConnectors {
bitcoin?: BitcoinConnector;
ethereum?: EthereumConnector;
lightning?: LightningConnector;
interface LedgerConfigs {
bitcoin?: BitcoinConfig;
ethereum?: EthereumConfig;
lightning?: LightningConfig;
}

interface Geth {
node_url: string;
}

interface EthereumConnector {
interface EthereumConfig {
chain_id: number;
geth: Geth;
tokens: Tokens;
}

interface Tokens {
dai: string;
}

interface Bitcoind {
node_url: string;
}

interface BitcoinConnector {
interface BitcoinConfig {
network: string;
bitcoind: Bitcoind;
}
Expand All @@ -135,12 +138,12 @@ interface Lnd {
dir: string;
}

interface LightningConnector {
interface LightningConfig {
network: string;
lnd: Lnd;
}

function bitcoinConnector(nodeConfig: BitcoinNodeConfig): BitcoinConnector {
function bitcoinConnector(nodeConfig: BitcoinNodeConfig): BitcoinConfig {
return {
bitcoind: {
node_url: nodeConfig.rpcUrl,
Expand All @@ -149,18 +152,19 @@ function bitcoinConnector(nodeConfig: BitcoinNodeConfig): BitcoinConnector {
};
}

function ethereumConnector(nodeConfig: EthereumNodeConfig): EthereumConnector {
function ethereumConnector(nodeConfig: EthereumNodeConfig): EthereumConfig {
return {
chain_id: nodeConfig.chain_id,
geth: {
node_url: nodeConfig.rpc_url,
},
tokens: {
dai: nodeConfig.tokenContract,
},
};
}

function lightningConnector(
nodeConfig: LightningNodeConfig
): LightningConnector {
function lightningConnector(nodeConfig: LightningNodeConfig): LightningConfig {
return {
network: "regtest",
lnd: {
Expand Down
59 changes: 57 additions & 2 deletions cnd/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ mod serde_bitcoin_network;
mod settings;
mod validation;

use crate::ethereum::ChainId;
use crate::{ethereum, ethereum::ChainId};
use anyhow::{Context, Result};
use conquer_once::Lazy;
use libp2p::Multiaddr;
Expand All @@ -26,6 +26,18 @@ static LND_URL: Lazy<Url> = Lazy::new(|| parse_unchecked("https://localhost:8080

static WEB3_URL: Lazy<Url> = Lazy::new(|| parse_unchecked("http://localhost:8545"));

/// The DAI token contract on Ethereum mainnet.
///
/// Source: https://developer.makerdao.com/dai/1/api/
static DAI_MAINNET: Lazy<ethereum::Address> =
Lazy::new(|| parse_unchecked("0x6b175474e89094c44da98b954eedeac495271d0f"));

/// The DAI token contract on the Ethereum testnet "kovan".
///
/// Source: https://developer.makerdao.com/dai/1/api/
static DAI_KOVAN: Lazy<ethereum::Address> =
Lazy::new(|| parse_unchecked("0xc4375b7de8af5a38a93548eb8453a498222c4ff2"));

static COMIT_SOCKET: Lazy<Multiaddr> = Lazy::new(|| parse_unchecked("/ip4/0.0.0.0/tcp/9939"));

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
Expand Down Expand Up @@ -109,6 +121,7 @@ impl From<file::Bitcoin> for Bitcoin {
pub struct Ethereum {
pub chain_id: ChainId,
pub geth: Geth,
pub tokens: Tokens,
}

impl Ethereum {
Expand All @@ -117,8 +130,16 @@ impl Ethereum {
let geth = ethereum.geth.unwrap_or_else(|| Geth {
node_url: WEB3_URL.clone(),
});
let tokens = ethereum.tokens.map_or_else(
|| Tokens::from_config_or_chain_id(None, chain_id),
|tokens| Tokens::from_config_or_chain_id(tokens.dai, chain_id),
)?;

Ok(Ethereum { chain_id, geth })
Ok(Ethereum {
chain_id,
geth,
tokens,
})
}
}

Expand All @@ -127,6 +148,15 @@ impl From<Ethereum> for file::Ethereum {
file::Ethereum {
chain_id: ethereum.chain_id,
geth: Some(ethereum.geth),
tokens: Some(ethereum.tokens.into()),
}
}
}

impl From<Tokens> for file::Tokens {
fn from(tokens: Tokens) -> Self {
file::Tokens {
dai: Some(tokens.dai),
}
}
}
Expand All @@ -138,6 +168,7 @@ impl Default for Ethereum {
geth: Geth {
node_url: WEB3_URL.clone(),
},
tokens: Tokens { dai: *DAI_MAINNET },
}
}
}
Expand All @@ -147,6 +178,30 @@ pub struct Geth {
pub node_url: Url,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct Tokens {
pub dai: ethereum::Address,
}

impl Tokens {
fn from_config_or_chain_id(dai: Option<ethereum::Address>, id: ChainId) -> Result<Self> {
let dai = dai.map_or_else(|| dai_address_from_chain_id(id), Ok)?;

Ok(Self { dai })
}
}

fn dai_address_from_chain_id(id: ChainId) -> Result<ethereum::Address> {
Ok(match id {
ChainId::MAINNET => *DAI_MAINNET,
ChainId::KOVAN => *DAI_KOVAN,
id => anyhow::bail!(
"unable to infer DAI token contract from chain-ID {}",
u32::from(id)
),
})
}

#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct Lightning {
pub network: bitcoin::Network,
Expand Down
48 changes: 46 additions & 2 deletions cnd/src/config/file.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{
config::{Bitcoind, Data, Geth, Network},
ethereum,
ethereum::ChainId,
};
use log::LevelFilter;
Expand Down Expand Up @@ -37,6 +38,12 @@ pub struct Bitcoin {
pub struct Ethereum {
pub chain_id: ChainId,
pub geth: Option<Geth>,
pub tokens: Option<Tokens>,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct Tokens {
pub dai: Option<ethereum::Address>,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
Expand Down Expand Up @@ -230,6 +237,9 @@ chain_id = 1337
[ethereum.geth]
node_url = "http://localhost:8545/"
[ethereum.tokens]
dai = "0x6b175474e89094c44da98b954eedeac495271d0f"
[lightning]
network = "regtest"
Expand Down Expand Up @@ -264,6 +274,13 @@ dir = "/foo/bar"
geth: Some(Geth {
node_url: "http://localhost:8545".parse().unwrap(),
}),
tokens: Some(Tokens {
dai: Some(
"0x6b175474e89094c44da98b954eedeac495271d0f"
.parse()
.unwrap(),
),
}),
}),
lightning: Some(Lightning {
network: bitcoin::Network::Regtest,
Expand Down Expand Up @@ -349,40 +366,67 @@ dir = "/foo/bar"
fn ethereum_deserializes_correctly() {
let file_contents = vec![
r#"
chain_id = 1337
chain_id = 42
[geth]
node_url = "http://example.com:8545"
[tokens]
dai = "0xc4375b7de8af5a38a93548eb8453a498222c4ff2"
"#,
r#"
chain_id = 3
[geth]
node_url = "http://example.com:8545"
[tokens]
dai = "0xaD6D458402F60fD3Bd25163575031ACDce07538D"
"#,
r#"
chain_id = 1
[geth]
node_url = "http://example.com:8545"
[tokens]
dai = "0x6b175474e89094c44da98b954eedeac495271d0f"
"#,
];

let expected = vec![
Ethereum {
chain_id: ChainId::REGTEST,
chain_id: ChainId::KOVAN,
geth: Some(Geth {
node_url: Url::parse("http://example.com:8545").unwrap(),
}),
tokens: Some(Tokens {
dai: Some(
"0xc4375b7de8af5a38a93548eb8453a498222c4ff2"
.parse()
.unwrap(),
),
}),
},
Ethereum {
chain_id: ChainId::ROPSTEN,
geth: Some(Geth {
node_url: Url::parse("http://example.com:8545").unwrap(),
}),
tokens: Some(Tokens {
dai: Some(
"0xaD6D458402F60fD3Bd25163575031ACDce07538D"
.parse()
.unwrap(),
),
}),
},
Ethereum {
chain_id: ChainId::MAINNET,
geth: Some(Geth {
node_url: Url::parse("http://example.com:8545").unwrap(),
}),
tokens: Some(Tokens {
dai: Some(
"0x6b175474e89094c44da98b954eedeac495271d0f"
.parse()
.unwrap(),
),
}),
},
];

Expand Down
3 changes: 2 additions & 1 deletion cnd/src/config/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ impl Settings {
mod tests {
use super::*;
use crate::{
config::{file, Bitcoind, Geth, Lnd},
config::{file, Bitcoind, Geth, Lnd, Tokens, DAI_MAINNET},
ethereum::ChainId,
};
use spectral::prelude::*;
Expand Down Expand Up @@ -298,6 +298,7 @@ mod tests {
geth: Geth {
node_url: "http://localhost:8545".parse().unwrap(),
},
tokens: Tokens { dai: *DAI_MAINNET },
})
}

Expand Down
11 changes: 4 additions & 7 deletions cnd/src/http_api/halbit_herc20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,13 @@ use crate::{
storage::Save,
Facade, LocalSwapId,
};
use serde::Deserialize;
use warp::{http::StatusCode, Rejection, Reply};

#[allow(clippy::needless_pass_by_value)]
pub async fn post_swap(body: serde_json::Value, facade: Facade) -> Result<impl Reply, Rejection> {
let body = PostBody::<Halbit, Herc20>::deserialize(&body)
.map_err(anyhow::Error::new)
.map_err(problem::from_anyhow)
.map_err(warp::reject::custom)?;

pub async fn post_swap(
body: PostBody<Halbit, Herc20>,
facade: Facade,
) -> Result<impl Reply, Rejection> {
let swap_id = LocalSwapId::default();
let reply = warp::reply::reply();

Expand Down
11 changes: 4 additions & 7 deletions cnd/src/http_api/hbit_herc20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,13 @@ use crate::{
storage::Save,
Facade, LocalSwapId, Side,
};
use serde::Deserialize;
use warp::{http::StatusCode, Rejection, Reply};

#[allow(clippy::needless_pass_by_value)]
pub async fn post_swap(body: serde_json::Value, facade: Facade) -> Result<impl Reply, Rejection> {
let body = PostBody::<Hbit, Herc20>::deserialize(&body)
.map_err(anyhow::Error::new)
.map_err(problem::from_anyhow)
.map_err(warp::reject::custom)?;

pub async fn post_swap(
body: PostBody<Hbit, Herc20>,
facade: Facade,
) -> Result<impl Reply, Rejection> {
let swap_id = LocalSwapId::default();
let reply = warp::reply::reply();

Expand Down
Loading

0 comments on commit 7228d36

Please sign in to comment.