Skip to content

Commit

Permalink
Merge pull request #14 from rodoufu/feat/pipeline
Browse files Browse the repository at this point in the history
Feat/pipeline
  • Loading branch information
rodoufu authored Dec 8, 2020
2 parents b4815fc + c8c03fe commit 17c9ed2
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 58 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "erc20"
version = "0.2.0"
version = "0.1.1"
authors = ["Rodolfo Araujo <[email protected]>"]
edition = "2018"
license-file = "LICENSE"
Expand Down
76 changes: 53 additions & 23 deletions src/erc20.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
///! ERC20 specific information.
use crate::ERC20Error;
use maplit::hashmap;
use serde::{
Expand All @@ -10,19 +12,27 @@ use std::{
TryFrom,
TryInto,
},
str::FromStr,
};
use web3::types::H160;
use std::str::FromStr;

/// ERC20 method operation
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum ERC20Method {
/// Returns the amount which `spender` is still allowed to withdraw from `owner`.
Allowance,
/// Allows `spender` to withdraw from your account multiple times, up to the `value` amount. If this function is called again it overwrites the current allowance with `value`.
Approve,
/// Returns the account balance of another account with address `owner`.
BalanceOf,
/// Returns the total token supply.
TotalSupply,
/// Transfers `value` amount of tokens to address `to`, and MUST fire the Transfer event. The function SHOULD throw if the message caller’s account balance does not have enough tokens to spend.
Transfer,
/// Transfers `value` amount of tokens from address `from` to address `to`, and MUST fire the Transfer event.
TransferFrom,
/// In case it is not identified an ERC20 operation.
Unidentified,
}

Expand Down Expand Up @@ -75,6 +85,7 @@ impl From<Vec<u8>> for ERC20Method {
}
}

/// Known ERC20 contract addresses.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum ContractAddress {
Expand Down Expand Up @@ -115,35 +126,54 @@ pub enum ContractAddress {
Unidentified(H160),
}

impl ContractAddress {
fn contract_and_address() -> HashMap<ContractAddress, H160> {
hashmap! {
ContractAddress::TUSD => H160::from_str("0000000000085d4780B73119b644AE5ecd22b376").unwrap(),
ContractAddress::LINK => H160::from_str("514910771af9ca656af840dff83e8264ecf986ca").unwrap(),
ContractAddress::BNB => H160::from_str("B8c77482e45F1F44dE1745F52C74426C631bDD52").unwrap(),
ContractAddress::USDC => H160::from_str("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
ContractAddress::WBTC => H160::from_str("2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(),
ContractAddress::cDAI => H160::from_str("5d3a536E4D6DbD6114cc1Ead35777bAB948E3643").unwrap(),
ContractAddress::OKB => H160::from_str("75231f58b43240c9718dd58b4967c5114342a86c").unwrap(),
ContractAddress::CRO => H160::from_str("a0b73e1ff0b80914ab6fe0444e65848c4c34450b").unwrap(),
ContractAddress::WFIL => H160::from_str("6e1A19F235bE7ED8E3369eF73b196C07257494DE").unwrap(),
ContractAddress::BAT => H160::from_str("0d8775f648430679a709e98d2b0cb6250d2887ef").unwrap(),
ContractAddress::BUSD => H160::from_str("4fabb145d64652a948d72533023f6e7a623c7c53").unwrap(),
ContractAddress::USDT => H160::from_str("dac17f958d2ee523a2206206994597c13d831ec7").unwrap(),
ContractAddress::LEO => H160::from_str("2af5d2ad76741191d15dfe7bf6ac92d4bd912ca3").unwrap(),
ContractAddress::VEN => H160::from_str("d850942ef8811f2a866692a623011bde52a462c1").unwrap(),
ContractAddress::DAI => H160::from_str("6b175474e89094c44da98b954eedeac495271d0f").unwrap(),
ContractAddress::UNI => H160::from_str("1f9840a85d5af5bf1d1762f925bdaddc4201f984").unwrap(),
}
}
}

impl From<H160> for ContractAddress {
fn from(address: H160) -> Self {
let contract_and_address: HashMap<ContractAddress, &str> = hashmap! {
ContractAddress::TUSD => "0000000000085d4780B73119b644AE5ecd22b376",
ContractAddress::LINK => "514910771af9ca656af840dff83e8264ecf986ca",
ContractAddress::BNB => "B8c77482e45F1F44dE1745F52C74426C631bDD52",
ContractAddress::USDC => "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
ContractAddress::WBTC => "2260fac5e5542a773aa44fbcfedf7c193bc2c599",
ContractAddress::cDAI => "5d3a536E4D6DbD6114cc1Ead35777bAB948E3643",
ContractAddress::OKB => "75231f58b43240c9718dd58b4967c5114342a86c",
ContractAddress::CRO => "a0b73e1ff0b80914ab6fe0444e65848c4c34450b",
ContractAddress::WFIL => "6e1A19F235bE7ED8E3369eF73b196C07257494DE",
ContractAddress::BAT => "0d8775f648430679a709e98d2b0cb6250d2887ef",
ContractAddress::BUSD => "4fabb145d64652a948d72533023f6e7a623c7c53",
ContractAddress::USDT => "dac17f958d2ee523a2206206994597c13d831ec7",
ContractAddress::LEO => "2af5d2ad76741191d15dfe7bf6ac92d4bd912ca3",
ContractAddress::VEN => "d850942ef8811f2a866692a623011bde52a462c1",
ContractAddress::DAI => "6b175474e89094c44da98b954eedeac495271d0f",
ContractAddress::UNI => "1f9840a85d5af5bf1d1762f925bdaddc4201f984",
};
for (contract, address_str) in contract_and_address {
if H160::from_str(address_str).unwrap() == address {
for (contract, address_v) in Self::contract_and_address() {
if address_v == address {
return contract;
}
}
ContractAddress::Unidentified(address)
}
}

impl From<ContractAddress> for H160 {
fn from(contract_address: ContractAddress) -> Self {
for (contract, address) in ContractAddress::contract_and_address() {
if contract_address == contract {
return address;
}
}
match contract_address {
ContractAddress::Unidentified(address) => address,
_ => panic!("Unexpected contract {:?}", contract_address),
}
}
}

#[test]
fn creating_address() {
let tusd_address = H160::from_str("0000000000085d4780B73119b644AE5ecd22b376").unwrap();
Expand All @@ -152,6 +182,6 @@ fn creating_address() {
let contract_address: ContractAddress = tusd_address.into();
assert_eq!(ContractAddress::TUSD, contract_address);

// let tusd_from_contract: H160 = contract_address.into();
// assert_eq!(tusd_address, tusd_from_contract);
let tusd_from_contract: H160 = contract_address.into();
assert_eq!(tusd_address, tusd_from_contract);
}
9 changes: 8 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
//! Expected errors.
use serde::{
Deserialize,
Serialize,
};

/// Possible transaction errors.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum ERC20Error {
/// Returned when the transaction is not a Ethereum transfer neither an ERC20 transfer.
NoTransferTransaction,
/// Unexpected size for the input.
UnexpectedSize,
/// The end of the input was found before expected.
UnexpectedEndOfData,
/// Returned when the type or value used is not expected for the operation.
UnexpectedType,
}
}
11 changes: 11 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
#![warn(missing_docs)]
// #![warn(missing_doc_code_examples)]

//! A simple implementation for parsing ERC20 transactions
extern crate serde;
extern crate hex;

mod error;
/// A set of useful methods and abstractions.
pub mod util;
#[cfg(test)]
mod util_test;
/// Ethereum transfer abstraction.
pub mod transfer;
/// ERC20 specific information.
pub mod erc20;
/// web3 transaction specific operations.
pub mod transaction;
#[cfg(test)]
mod transaction_test;
Expand Down
13 changes: 13 additions & 0 deletions src/transaction.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
///! web3 transaction specific operations.
use crate::{
erc20::ERC20Method,
error::ERC20Error,
Expand All @@ -21,12 +23,17 @@ use web3::types::{
U256,
};

/// Identifies an Ethereum transaction as a transfer, contract invocation, creation, or other.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum ParsedTransaction {
/// Ether transfer transaction.
EthereumTransfer(Transaction),
/// Smart contract invocation transaction.
ContractInvocation(TransactionContractInvocation),
/// Smart contract creation transaction.
ContractCreation(Transaction),
/// Unidentified transaction.
Other(Transaction),
}

Expand All @@ -48,9 +55,12 @@ impl From<Transaction> for ParsedTransaction {
}
}

/// Smart contract invocation transaction.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum TransactionContractInvocation {
/// ERC20 contract invocation.
ERC20(ERC20Method, Transaction),
/// Any smart contract invocation that is not a ERC20 contract.
Other(Transaction),
}

Expand All @@ -64,6 +74,7 @@ impl From<Transaction> for TransactionContractInvocation {
}
}

/// Transaction and transaction type information for asset transfers.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionAndTransferType {
Expand Down Expand Up @@ -107,6 +118,8 @@ impl TryFrom<Transaction> for TransactionAndTransferType {
}

impl TransactionAndTransferType {
/// Gets information from the transaction.
/// The `from`, `to`, and `value` regardless if it is an ERC20 or Ether transfer.
pub fn get_from_to_value(&self) -> Result<(H160, H160, U256), ERC20Error> {
let from_v: H160;
let to_v: H160;
Expand Down
22 changes: 17 additions & 5 deletions src/transaction_test.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
use crate::{transfer::Transfer, transaction::TransactionAndTransferType, util::string_to_h256, ERC20Error};
use std::convert::TryInto;
use web3::types::{Bytes, Transaction, H160};
use crate::{
transfer::Transfer,
transaction::TransactionAndTransferType,
ERC20Error,
};
use std::{
convert::TryInto,
str::FromStr,
};
use web3::types::{
Bytes,
H160,
H256,
Transaction,
};

#[test]
fn parse_no_transfer_transaction() {
let serialized_str = "a9059cbb0000000000000000000000006748f50f686bfbca6fe8ad62b22228b87f31ff2b00000000000000000000000000000000000000000000003635c9adc5dea00000";

let transaction = Transaction {
hash: string_to_h256("43a5d6d13b6a9dca381e3f4b4677a4b9e5d9f80d1a5b6cfa2b1404fab733bcee".to_string()).unwrap(),
hash: H256::from_str("43a5d6d13b6a9dca381e3f4b4677a4b9e5d9f80d1a5b6cfa2b1404fab733bcee").unwrap(),
nonce: Default::default(),
block_hash: None,
block_number: None,
Expand All @@ -31,7 +43,7 @@ fn parse_erc20() {
let serialized_str = "a9059cbb0000000000000000000000006748f50f686bfbca6fe8ad62b22228b87f31ff2b00000000000000000000000000000000000000000000003635c9adc5dea00000";

let transaction = Transaction {
hash: string_to_h256("43a5d6d13b6a9dca381e3f4b4677a4b9e5d9f80d1a5b6cfa2b1404fab733bcee".to_string()).unwrap(),
hash: H256::from_str("43a5d6d13b6a9dca381e3f4b4677a4b9e5d9f80d1a5b6cfa2b1404fab733bcee").unwrap(),
nonce: Default::default(),
block_hash: None,
block_number: None,
Expand Down
18 changes: 18 additions & 0 deletions src/transfer.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
///! Ethereum transfer abstraction.
use serde::{
Deserialize,
Serialize,
Expand All @@ -13,38 +15,54 @@ use web3::types::{
U256,
};

/// Type of the asset transfer.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum TransferType {
/// Indicates an Ether transfer.
Ethereum,
/// Indicates an ERC20 transfer.
ERC20,
}

/// Asset transfer abstraction.
pub trait Transfer {
/// Returns the sender of the transfer.
fn from(&self) -> H160;
/// Returns the recipient of the transfer.
fn to(&self) -> H160;
/// Returns the ERC20 contract address for ERC20 transfers.
fn contract(&self) -> Option<H160>;
/// Returns the value of the transfer.
fn value(&self) -> U256;
/// Returns the transaction hash for the transfer.
fn tx_hash(&self) -> H256;
/// Returns the block hash for the transfer, if available.
fn block_hash(&self) -> Option<H256>;
/// Returns the block number for the transfer, if available.
fn block_number(&self) -> Option<U64>;
/// Returns the transaction index for the transfer, if available.
fn transaction_index(&self) -> Option<Index>;
}

impl dyn Transfer {
/// The kind of transfer.
pub fn kind(&self) -> TransferType {
match self.contract() {
None => TransferType::Ethereum,
Some(_) => TransferType::ERC20,
}
}

/// Checks if it is an Ether transfer.
pub fn is_ethereum(&self) -> bool {
self.kind() == TransferType::Ethereum
}

/// Checks if it is an ERC20 transfer.
pub fn is_erc20(&self) -> bool { !self.is_ethereum() }

/// Retrieves the transaction id.
#[allow(dead_code)]
fn transaction_id(&self) -> TransactionId {
if let Some(block_num) = self.block_number() {
Expand Down
Loading

0 comments on commit 17c9ed2

Please sign in to comment.