Skip to content

Commit

Permalink
Add Eth Transaction From Raw (#32)
Browse files Browse the repository at this point in the history
* add eth decode tx

* remove idea

* add unity test

* update format

* remove .to_vec() from data

* add from json method

* update tests
  • Loading branch information
EdsonHTJ authored Jan 22, 2024
1 parent 0e2b981 commit 05d560d
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 6 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ dist

.DS_Store
demo/.DS_Store
packages/.DS_Store
packages/.DS_Store
.idea/*
13 changes: 13 additions & 0 deletions packages/kos-sdk/src/chains/ethereum/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use kos_crypto::keypair::KeyPair;
use kos_types::error::Error;

use hex::FromHex;
use rlp::{DecoderError, Rlp};
use std::{fmt, str::FromStr};
use web3::types::Address as Web3Address;

Expand Down Expand Up @@ -165,6 +166,18 @@ impl AsRef<[u8]> for Address {
}
}

impl rlp::Decodable for Address {
fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
let mut data: Vec<u8> = rlp.as_val()?;
let mut bytes: [u8; ADDRESS_LEN] = [0; ADDRESS_LEN];
while data.len() < ADDRESS_LEN {
data.push(0);
}
bytes.copy_from_slice(&data[..]);
Ok(Address(bytes))
}
}

impl TryFrom<&Web3Address> for Address {
type Error = Error;

Expand Down
103 changes: 103 additions & 0 deletions packages/kos-sdk/src/chains/ethereum/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use kos_types::error::Error;
use kos_types::hash::Hash;
use kos_types::number::BigNumber;

use rlp::Rlp;
use secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
use std::{ops::Div, str::FromStr};
use wasm_bindgen::prelude::*;
Expand Down Expand Up @@ -377,6 +378,7 @@ impl ETH {
}),
chain_id: Some(chain_id),
nonce,
from: Some(sender),
to: Some(receiver),
value,
data: options.contract_data.unwrap_or_default(),
Expand Down Expand Up @@ -427,6 +429,50 @@ impl ETH {

Ok(true)
}

#[wasm_bindgen(js_name = "txFromRaw")]
pub fn tx_from_raw(raw: &str) -> Result<crate::models::Transaction, Error> {
let hex_tx = hex::decode(raw)?;
let rlp = Rlp::new(&hex_tx);

let tx = match transaction::Transaction::decode_legacy(&rlp) {
Ok(tx) => tx,
Err(_) => {
let rlp = Rlp::new(&hex_tx[2..]);
self::transaction::Transaction::decode_eip155(rlp).map_err(|e| {
Error::InvalidTransaction(format!("failed to decode transaction: {}", e))
})?
}
};

let digest = hash_transaction(&tx)?;
Ok(crate::models::Transaction {
chain: chain::Chain::ETH,
sender: "".to_string(), //TODO: implement sender on eth decode
hash: Hash::from_vec(digest)?,
data: Some(TransactionRaw::Ethereum(tx)),
})
}

#[wasm_bindgen(js_name = "txFromJson")]
pub fn tx_from_json(raw: &str) -> Result<crate::models::Transaction, Error> {
// build expected send result
let tx: transaction::Transaction = serde_json::from_str(raw)?;

let digest = hash_transaction(&tx)?;

let sender = match tx.from {
Some(addr) => addr.to_string(),
None => "".to_string(),
};

Ok(crate::models::Transaction {
chain: chain::Chain::ETH,
sender,
hash: Hash::from_vec(digest)?,
data: Some(TransactionRaw::Ethereum(tx)),
})
}
}

#[cfg(test)]
Expand Down Expand Up @@ -625,4 +671,61 @@ mod tests {
assert_eq!(valid, false, "address: {}", addr);
}
}

#[test]
fn test_decode_rlp_tx() {
let raw_tx = "af02ed0182012884019716f7850e60f86055827530944cbeee256240c92a9ad920ea6f4d7df6466d2cdc0180c0808080";
let tx = ETH::tx_from_raw(raw_tx).unwrap();

assert_eq!(tx.chain, chain::Chain::ETH);

let eth_tx = match tx.data {
Some(TransactionRaw::Ethereum(tx)) => tx,
_ => panic!("invalid tx"),
};

assert_eq!(eth_tx.chain_id, Some(1));
assert_eq!(eth_tx.nonce, U256::from_dec_str("296").unwrap());
assert_eq!(
eth_tx.to.unwrap().to_string(),
"0x4cBeee256240c92A9ad920ea6f4d7Df6466D2Cdc"
);
assert_eq!(eth_tx.gas, U256::from(30000));
assert_eq!(eth_tx.value, U256::from_dec_str("1").unwrap());
assert_eq!(eth_tx.signature, None);
}

#[test]
fn test_decode_json() {
let json = r#"{
"from":"0x4cbeee256240c92a9ad920ea6f4d7df6466d2cdc",
"maxPriorityFeePerGas":null,"maxFeePerGas":null,
"gas": "0x00",
"value": "0x00",
"data":"0xa9059cbb000000000000000000000000ac4145fef6c828e8ae017207ad944c988ccb2cf700000000000000000000000000000000000000000000000000000000000f4240",
"to":"0xdac17f958d2ee523a2206206994597c13d831ec7",
"nonce":"0x00"}"#;
let tx = ETH::tx_from_json(json).unwrap();

assert_eq!(tx.chain, chain::Chain::ETH);

let eth_tx = match tx.data {
Some(TransactionRaw::Ethereum(tx)) => tx,
_ => panic!("invalid tx"),
};

assert_eq!(eth_tx.chain_id, None);
assert_eq!(eth_tx.nonce, U256::from_dec_str("0").unwrap());
assert_eq!(
eth_tx.from.unwrap().to_string(),
"0x4cBeee256240c92A9ad920ea6f4d7Df6466D2Cdc"
);
assert_eq!(
eth_tx.to.unwrap().to_string(),
"0xdAC17F958D2ee523a2206206994597C13D831ec7"
);
assert_eq!(eth_tx.gas, U256::from(0));
assert_eq!(eth_tx.value, U256::from(0));
assert_eq!(eth_tx.signature, None);
}
}
83 changes: 78 additions & 5 deletions packages/kos-sdk/src/chains/ethereum/transaction.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use super::address::Address;
use std::str::FromStr;

use kos_types::error::Error;

use rlp::RlpStream;
use rlp::{DecoderError, Rlp, RlpStream};
use secp256k1::ecdsa::RecoverableSignature;
use serde::Serialize;
use serde::{Deserialize, Deserializer, Serialize};
use web3::types::U256;

#[derive(Serialize, Clone, Debug, PartialEq)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum TransactionType {
Legacy,
EIP1559,
Expand All @@ -25,19 +26,55 @@ where
}
}

#[derive(Serialize, Clone, Debug, PartialEq)]
pub fn deserialize_addr<'de, D>(deserializer: D) -> Result<Option<Address>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
if s.is_empty() {
Ok(None)
} else {
Ok(Some(
Address::from_str(&s).map_err(serde::de::Error::custom)?,
))
}
}

pub fn deserialize_data<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
if s.is_empty() {
Ok(Vec::new())
} else {
let s = if s.len() > 2 && (s.starts_with("0x") || s.starts_with("0X")) {
&s[2..]
} else {
&s
};

Ok(hex::decode(s).map_err(serde::de::Error::custom)?)
}
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct Transaction {
pub transaction_type: Option<TransactionType>,
pub nonce: U256,
#[serde(deserialize_with = "deserialize_addr")]
pub from: Option<Address>,
#[serde(deserialize_with = "deserialize_addr")]
pub to: Option<Address>,
pub gas: U256,
pub gas_price: Option<U256>,
pub value: U256,
#[serde(deserialize_with = "deserialize_data")]
pub data: Vec<u8>,
pub chain_id: Option<u64>,
pub max_fee_per_gas: Option<U256>,
pub max_priority_fee_per_gas: Option<U256>,
#[serde(serialize_with = "signature_serialize")]
#[serde(serialize_with = "signature_serialize", skip_deserializing)]
pub signature: Option<RecoverableSignature>,
}

Expand Down Expand Up @@ -144,6 +181,40 @@ impl Transaction {
}
}
}

pub fn decode_legacy(rlp: &Rlp) -> Result<Self, DecoderError> {
Ok(Transaction {
transaction_type: Some(TransactionType::Legacy),
nonce: rlp.val_at(0)?,
gas_price: Some(rlp.val_at(1)?),
gas: rlp.val_at(2)?,
from: None,
to: Some(rlp.val_at(3)?),
value: rlp.val_at(4)?,
data: rlp.val_at(5)?,
chain_id: None,
max_fee_per_gas: None,
max_priority_fee_per_gas: None,
signature: None,
})
}

pub fn decode_eip155(rlp: Rlp) -> Result<Self, DecoderError> {
Ok(Transaction {
transaction_type: Some(TransactionType::EIP1559),
chain_id: Some(rlp.val_at(0)?),
nonce: rlp.val_at(1)?,
max_priority_fee_per_gas: Some(rlp.val_at(2)?),
max_fee_per_gas: Some(rlp.val_at(3)?),
gas: rlp.val_at(4)?,
from: None,
to: Some(rlp.val_at(5)?), // Convert to Option
value: rlp.val_at(6)?,
data: rlp.val_at(7)?,
signature: None,
gas_price: None,
})
}
}

#[cfg(test)]
Expand All @@ -157,6 +228,7 @@ mod tests {
let tx = Transaction {
transaction_type: Some(TransactionType::Legacy),
nonce: U256::from_dec_str("691").unwrap(),
from: None,
to: Some(Address::try_from("0x4592D8f8D7B001e72Cb26A73e4Fa1806a51aC79d").unwrap()),
gas: U256::from(21000),
gas_price: Some(U256::from_dec_str("2000000000").unwrap()),
Expand Down Expand Up @@ -184,6 +256,7 @@ mod tests {
let tx = Transaction {
transaction_type: Some(TransactionType::EIP1559),
nonce: U256::from_dec_str("241").unwrap(),
from: None,
to: Some(Address::try_from("0xe0e5d2B4EDcC473b988b44b4d13c3972cb6694cb").unwrap()),
gas: U256::from(21000),
gas_price: None,
Expand Down

0 comments on commit 05d560d

Please sign in to comment.