Skip to content

Commit

Permalink
Re-organize Multicoin
Browse files Browse the repository at this point in the history
  • Loading branch information
lucemans committed Sep 9, 2023
1 parent ec8513c commit 2ea0fb6
Show file tree
Hide file tree
Showing 16 changed files with 231 additions and 167 deletions.
4 changes: 2 additions & 2 deletions .github/.k8s/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ metadata:
name: enstate
namespace: enstate
spec:
replicas: 1
replicas: 2
selector:
matchLabels:
app: enstate
Expand All @@ -49,7 +49,7 @@ spec:
spec:
containers:
- name: enstate
image: ghcr.io/v3xlabs/enstate:0.0.8-6
image: ghcr.io/v3xlabs/enstate:0.0.8-8
imagePullPolicy: Always
ports:
- containerPort: 3000
Expand Down
6 changes: 3 additions & 3 deletions src/models/lookup/addr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ impl ENSLookup for Addr {

fn decode(&self, data: &[u8]) -> Result<String, ENSLookupError> {
let decoded_abi = ethers_core::abi::decode(&[ParamType::Address], data)
.map_err(|_| ENSLookupError::AbiError)?;
.map_err(|_| ENSLookupError::AbiDecodeError)?;
let address = decoded_abi
.get(0)
.ok_or(ENSLookupError::AbiError)?
.ok_or(ENSLookupError::AbiDecodeError)?
.clone()
.into_address()
.ok_or(ENSLookupError::InvalidPayload("yup".to_string()))?;
.ok_or(ENSLookupError::AbiDecodeError)?;

Ok(format!("{address:?}"))
}
Expand Down
4 changes: 2 additions & 2 deletions src/models/lookup/avatar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ impl ENSLookup for Avatar {

fn decode(&self, data: &[u8]) -> Result<String, ENSLookupError> {
let decoded_abi = ethers_core::abi::decode(&[ParamType::String], data)
.map_err(|_| ENSLookupError::AbiError)?;
let value = decoded_abi.get(0).ok_or(ENSLookupError::AbiError)?;
.map_err(|_| ENSLookupError::AbiDecodeError)?;
let value = decoded_abi.get(0).ok_or(ENSLookupError::AbiDecodeError)?;
let value = value.to_string();

// If IPFS
Expand Down
8 changes: 5 additions & 3 deletions src/models/lookup/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use ethers_core::types::H256;
use thiserror::Error;

use super::multicoin::decoding::MulticoinDecoderError;

pub mod addr;
pub mod avatar;
pub mod multicoin;
Expand All @@ -9,10 +11,10 @@ pub mod text;
#[derive(Error, Debug)]
pub enum ENSLookupError {
#[error("ABI error")]
AbiError,
AbiDecodeError,

#[error("Invalid payload: {0}")]
InvalidPayload(String),
#[error("MulticoinDecoderError: {0}")]
MulticoinDecoder(#[from] MulticoinDecoderError),

#[error("Unsupported: {0}")]
Unsupported(String),
Expand Down
58 changes: 10 additions & 48 deletions src/models/lookup/multicoin.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
use anyhow::anyhow;
use ethers_core::{
abi::{ParamType, Token},
k256::U256,
types::{H160, H256},
types::H256,
};
use hex_literal::hex;
use tracing::info;

use crate::models::multicoin::{
cointype::{coins::CoinType, evm::ChainId, slip44::SLIP44},
decoding::bitcoin::decode_btc,
};
use crate::models::multicoin::cointype::{coins::CoinType, evm::ChainId, slip44::SLIP44};

use super::{ENSLookup, ENSLookupError};

Expand All @@ -32,56 +26,24 @@ impl ENSLookup for Multicoin {

fn decode(&self, data: &[u8]) -> Result<String, ENSLookupError> {
let decoded_abi = ethers_core::abi::decode(&[ParamType::Bytes], data)
.map_err(|_| ENSLookupError::AbiError)?;
.map_err(|_| ENSLookupError::AbiDecodeError)?;
let value = decoded_abi
.get(0)
.ok_or(ENSLookupError::AbiError)?
.ok_or(ENSLookupError::AbiDecodeError)?
.clone()
.into_bytes();

let value = value.unwrap();

// TODO: If value is empty

match &self.coin_type {
// SLIP-044 Chain Address Decoding (see ensip-9)
CoinType::Slip44(slip44) => match slip44 {
// Bitcoin Decoding
SLIP44::Bitcoin => {
decode_btc(value.as_slice()).map_err(|x| ENSLookupError::Unknown(anyhow!(x)))
}
// Lightcoin Decoding
SLIP44::Litecoin => Err(ENSLookupError::Unknown(anyhow!(
"Litecoin Decoding Not Implemented"
))),

// Unsupported SLIP44 Chain
_ => {
// Raw Dump
// Ok(format!("SLIP-{:?}", value))

// Unsupported
Err(ENSLookupError::Unsupported(
"Chain Not Supported".to_string(),
))
}
},
// Implement EVM Chain Address Decoding (mostly ChecksummedHex, sometimes ChecksummedHex(chainId)) (see ensip-11)
CoinType::Evm(_evm) => {
// Verify length is 20 bytes
if value.len() != 20 {
// TODO: throw invalid length
return Ok("Invalid Length".to_string());
}

let address = hex::encode(value);

Ok(format!("0x{address}"))
}
if value.is_empty() {
// Empty field
return Ok(String::new());
}

Ok(self.coin_type.decode(&value)?)
}

fn name(&self) -> String {
format!("chains.{:?}", self.coin_type)
format!("chains.{}", self.coin_type.to_string())
}
}
4 changes: 2 additions & 2 deletions src/models/lookup/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ impl ENSLookup for Text {

fn decode(&self, data: &[u8]) -> Result<String, ENSLookupError> {
let decoded_abi = ethers_core::abi::decode(&[ParamType::String], data)
.map_err(|_| ENSLookupError::AbiError)?;
let value = decoded_abi.get(0).ok_or(ENSLookupError::AbiError)?;
.map_err(|_| ENSLookupError::AbiDecodeError)?;
let value = decoded_abi.get(0).ok_or(ENSLookupError::AbiDecodeError)?;
let value = value.to_string();

Ok(value)
Expand Down
13 changes: 13 additions & 0 deletions src/models/multicoin/cointype/coins.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use std::fmt::Display;

use ethers_core::types::U256;

use crate::models::multicoin::decoding::{MulticoinDecoder, MulticoinDecoderError};

use super::{evm::ChainId, slip44::SLIP44};

#[derive(Debug, Clone, PartialEq, Eq)]
Expand All @@ -17,6 +21,15 @@ impl From<CoinType> for U256 {
}
}

impl ToString for CoinType {
fn to_string(&self) -> String {
match self {
Self::Slip44(slip44) => slip44.to_string(),
Self::Evm(chain) => chain.to_string(),
}
}
}

#[cfg(test)]
mod tests {
use super::super::{evm::ChainId, slip44::SLIP44};
Expand Down
18 changes: 18 additions & 0 deletions src/models/multicoin/cointype/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,21 @@ impl From<ChainId> for CoinType {
Self::Evm(val)
}
}

impl ToString for ChainId {
fn to_string(&self) -> String {
match self {
ChainId::Ethereum => "eth".to_string(),
ChainId::Optimism => "optimism".to_string(),
ChainId::BinanceSmartChain => "bsc".to_string(),
ChainId::Gnosis => "gnosis".to_string(),
ChainId::Polygon => "polygon".to_string(),
ChainId::Fantom => "fantom".to_string(),
ChainId::Moonbeam => "moonbeam".to_string(),
ChainId::Arbitrum => "arbitrum".to_string(),
ChainId::Avalanche => "avalanche".to_string(),
ChainId::Celo => "celo".to_string(),
ChainId::Other(id) => format!("SLIP44:{}", id),
}
}
}
1 change: 1 addition & 0 deletions src/models/multicoin/cointype/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ impl Default for Coins {
SLIP44::Hedera.into(),
SLIP44::Stellar.into(),
ChainId::Polygon.into(),
ChainId::Optimism.into(),
]
}
}
Expand Down
20 changes: 20 additions & 0 deletions src/models/multicoin/cointype/slip44.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,23 @@ impl From<SLIP44> for CoinType {
CoinType::Slip44(val)
}
}

impl ToString for SLIP44 {
fn to_string(&self) -> String {
match self {
Self::Bitcoin => "btc".to_string(),
Self::Litecoin => "ltc".to_string(),
Self::Dogecoin => "doge".to_string(),
Self::Ethereum => "eth".to_string(),
Self::BitcoinCash => "bch".to_string(),
Self::EthereumClassic => "etc".to_string(),
Self::Monero => "xmr".to_string(),
Self::Ripple => "ripple".to_string(),
Self::Stellar => "stellar".to_string(),
Self::Tezos => "tezos".to_string(),
Self::Hedera => "hedera".to_string(),
Self::Cardano => "cardano".to_string(),
Self::Other(u256) => u256.to_string(),
}
}
}
66 changes: 34 additions & 32 deletions src/models/multicoin/decoding/bitcoin.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,23 @@
use thiserror::Error;
use super::{p2pkh::P2PKHDecoder, p2sh::P2SHDecoder, MulticoinDecoder, MulticoinDecoderError};

use super::{p2pkh, p2sh};
pub struct BitcoinDecoder {}

#[derive(Error, Debug)]
pub enum BtcDecodeError {
#[error("Invalid address")]
InvalidAddress,
impl MulticoinDecoder for BitcoinDecoder {
fn decode(&self, data: &[u8]) -> Result<String, MulticoinDecoderError> {
if data.len() == 25 {
return P2PKHDecoder { version: 0x00 }.decode(data);
}

#[error("Address type not supported")]
NotSupported
}

pub fn decode_btc(bytes: &[u8]) -> Result<String, BtcDecodeError> {
if bytes.len() == 25 {
return p2pkh::decode(bytes, 0x00).map_err(|_| BtcDecodeError::InvalidAddress)
}
if data.len() == 23 {
return P2SHDecoder { version: 0x05 }.decode(data);
}

if bytes.len() == 23 {
return p2sh::decode(bytes, 0x05).map_err(|_| BtcDecodeError::InvalidAddress)
}
if data.starts_with(&[0x98, 0x99]) {
return Err(MulticoinDecoderError::NotSupported);
}

if bytes.starts_with(&[0x98, 0x99]) {
return Err(BtcDecodeError::NotSupported)
Err(MulticoinDecoderError::NotSupported)
}

Err(BtcDecodeError::NotSupported)
}

#[cfg(test)]
Expand All @@ -33,28 +26,37 @@ mod tests {

#[tokio::test]
async fn test_btc_p2pkh() {
let decoded = decode_btc(
&hex::decode("76a91462e907b15cbf27d5425399ebf6f0fb50ebb88f1888ac").unwrap()
).unwrap();
let decoded = BitcoinDecoder {}
.decode(&hex_literal::hex!(
"76a91462e907b15cbf27d5425399ebf6f0fb50ebb88f1888ac"
))
.unwrap();

assert_eq!(decoded, "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa".to_string());
}

#[tokio::test]
async fn test_btc_p2sh() {
let decoded = decode_btc(
&hex::decode("a91462e907b15cbf27d5425399ebf6f0fb50ebb88f1887").unwrap()
).unwrap();
let decoded = BitcoinDecoder {}
.decode(&hex_literal::hex!(
"a91462e907b15cbf27d5425399ebf6f0fb50ebb88f1887"
))
.unwrap();

assert_eq!(decoded, "3Ai1JZ8pdJb2ksieUV8FsxSNVJCpoPi8W6".to_string());
}

#[tokio::test]
async fn test_btc_segwit() {
let decoded = decode_btc(
&hex::decode("0014751e76e8199196d454941c45d1b3a323f1433bd6").unwrap()
).unwrap();

assert_eq!(decoded, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4".to_string());
let decoded = BitcoinDecoder {}
.decode(&hex_literal::hex!(
"0014751e76e8199196d454941c45d1b3a323f1433bd6"
))
.unwrap();

assert_eq!(
decoded,
"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4".to_string()
);
}
}
11 changes: 11 additions & 0 deletions src/models/multicoin/decoding/checksum_address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use super::{MulticoinDecoder, MulticoinDecoderError};

pub struct EvmDecoder {}

impl MulticoinDecoder for EvmDecoder {
fn decode(&self, data: &[u8]) -> Result<String, MulticoinDecoderError> {
let hex = hex::encode(data);

Ok(format!("0x{}", hex))
}
}
36 changes: 35 additions & 1 deletion src/models/multicoin/decoding/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,37 @@
use thiserror::Error;

use self::{bitcoin::BitcoinDecoder, checksum_address::EvmDecoder};

use super::cointype::{coins::CoinType, slip44::SLIP44};

pub mod bitcoin;
pub mod checksum_address;
pub mod p2pkh;
pub mod p2sh;
pub mod bitcoin;

#[derive(Debug, Error)]
pub enum MulticoinDecoderError {
#[error("Invalid Structure {0}")]
InvalidStructure(String),

#[error("Not supported")]
NotSupported,
}

pub trait MulticoinDecoder {
fn decode(&self, data: &[u8]) -> Result<String, MulticoinDecoderError>;
}

impl CoinType {
pub fn decode(&self, data: &[u8]) -> Result<String, MulticoinDecoderError> {
let decoder: Box<dyn MulticoinDecoder> = match self {
Self::Slip44(slip44) => match slip44 {
SLIP44::Bitcoin => Box::new(BitcoinDecoder {}),
_ => return Err(MulticoinDecoderError::NotSupported),
},
Self::Evm(chain) => Box::new(EvmDecoder {}),
};

decoder.decode(data)
}
}
Loading

0 comments on commit 2ea0fb6

Please sign in to comment.