Skip to content

Commit

Permalink
Implement "Mixed-case checksum address encoding" for ETH. #394
Browse files Browse the repository at this point in the history
  • Loading branch information
artemii235 committed May 10, 2019
1 parent dbcd12e commit 0039f23
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 92 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions mm2src/coins/Cargo.toml
Expand Up @@ -44,6 +44,7 @@ serde_derive = "1.0"
serde_json = "1.0"
serialization = { git = "https://github.com/artemii235/parity-bitcoin.git" }
sha2 = "0.8"
sha3 = "0.8"
tokio-timer = "0.1"
tokio = "0.1.13"
# Need try_clone to shutdown and reconnect the tcp connection when there's no data received from Electrum after some time.
Expand Down
144 changes: 57 additions & 87 deletions mm2src/coins/eth.rs
Expand Up @@ -35,6 +35,7 @@ use rand::{Rng, thread_rng};
use rand::seq::SliceRandom;
use rpc::v1::types::{Bytes as BytesJson};
use serde_json::{self as json, Value as Json};
use sha3::{Keccak256, Digest};
use std::borrow::Cow;
use std::cmp::Ordering;
use std::ffi::CStr;
Expand All @@ -56,6 +57,9 @@ use self::web3_transport::Web3Transport;
use futures::future::Either;
use common::mm_ctx::MmArc;

#[cfg(test)]
mod eth_tests;

/// https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol
/// Dev chain (195.201.0.6:8565) contract address: 0xa09ad3cd7e96586ebd05a2607ee56b56fb2db8fd
/// Ropsten: https://ropsten.etherscan.io/address/0x7bc1bbdd6a0a722fc9bffc49c921b685ecb84b94
Expand Down Expand Up @@ -368,7 +372,7 @@ impl SwapOps for EthCoin {

impl MarketCoinOps for EthCoin {
fn my_address(&self) -> Cow<str> {
format!("{:#02x}", self.my_address).into()
checksum_address(&format!("{:#02x}", self.my_address)).into()
}

fn my_balance(&self) -> Box<Future<Item=f64, Error=String> + Send> {
Expand Down Expand Up @@ -1066,8 +1070,8 @@ impl EthCoin {
spent_by_me,
received_by_me,
total_amount,
to: vec![format!("{:#02x}", to_addr)],
from: vec![format!("{:#02x}", from_addr)],
to: vec![checksum_address(&format!("{:#02x}", to_addr))],
from: vec![checksum_address(&format!("{:#02x}", from_addr))],
coin: self.ticker.clone(),
fee_details: unwrap!(json::to_value(fee_details)),
block_height: block_number.into(),
Expand Down Expand Up @@ -1279,8 +1283,8 @@ impl EthCoin {
spent_by_me,
received_by_me,
total_amount,
to: vec![format!("{:#02x}", call_data.to)],
from: vec![format!("{:#02x}", call_data.from)],
to: vec![checksum_address(&format!("{:#02x}", call_data.to))],
from: vec![checksum_address(&format!("{:#02x}", call_data.from))],
coin: self.ticker.clone(),
fee_details: unwrap!(json::to_value(fee_details)),
block_height: trace.block_number,
Expand Down Expand Up @@ -1439,7 +1443,7 @@ impl MmCoin for EthCoin {
let fee_details = try_s!(json::to_value(fee_details));
drop(nonce_lock);
Ok(TransactionDetails {
to: vec![format!("{:#02x}", to_addr)],
to: vec![checksum_address(&format!("{:#02x}", to_addr))],
from: vec![arc.my_address().into()],
total_amount: amount_f64,
spent_by_me,
Expand Down Expand Up @@ -1471,7 +1475,7 @@ impl MmCoin for EthCoin {
let mut spent_by_me = 0f64;

let to = match tx.to {
Some(addr) => vec![format!("{:#02x}", addr)],
Some(addr) => vec![checksum_address(&format!("{:#02x}", addr))],
None => vec![],
};
let total_amount = try_s!(display_u256_with_decimal_point(tx.value, self.decimals).parse());
Expand All @@ -1487,7 +1491,7 @@ impl MmCoin for EthCoin {
}

Ok(TransactionDetails {
from: vec![format!("{:#02x}", tx.from)],
from: vec![checksum_address(&format!("{:#02x}", tx.from))],
to,
coin: self.ticker.clone(),
block_height: tx.block_number.unwrap_or(U256::from(0)).into(),
Expand All @@ -1504,7 +1508,7 @@ impl MmCoin for EthCoin {
},
EthCoinType::Erc20(_addr) => {
Ok(TransactionDetails {
from: vec![format!("{:#02x}", tx.from)],
from: vec![checksum_address(&format!("{:#02x}", tx.from))],
to,
coin: self.ticker.clone(),
block_height: tx.block_number.unwrap_or(U256::from(0)).into(),
Expand Down Expand Up @@ -1555,37 +1559,6 @@ fn display_u256_with_decimal_point(number: U256, decimals: u8) -> String {
string.trim_end_matches('0').into()
}

#[test]
fn display_u256_with_point() {
let number = U256::from_dec_str("1000000000000000000").unwrap();
let string = display_u256_with_decimal_point(number, 18);
assert_eq!("1.", string);

let number = U256::from_dec_str("1234567890000000000").unwrap();
let string = display_u256_with_decimal_point(number, 18);
assert_eq!("1.23456789", string);

let number = U256::from_dec_str("1234567890000000000").unwrap();
let string = display_u256_with_decimal_point(number, 16);
assert_eq!("123.456789", string);

let number = U256::from_dec_str("1234567890000000000").unwrap();
let string = display_u256_with_decimal_point(number, 0);
assert_eq!("1234567890000000000.", string);

let number = U256::from_dec_str("1000000000000000").unwrap();
let string = display_u256_with_decimal_point(number, 18);
assert_eq!("0.001", string);

let number = U256::from_dec_str("0").unwrap();
let string = display_u256_with_decimal_point(number, 18);
assert_eq!("0.", string);

let number = U256::from_dec_str("0").unwrap();
let string = display_u256_with_decimal_point(number, 0);
assert_eq!("0.", string);
}

fn u256_to_f64(number: U256, decimals: u8) -> Result<f64, String> {
let string = display_u256_with_decimal_point(number, decimals);
Ok(try_s!(string.parse()))
Expand All @@ -1610,49 +1583,6 @@ fn wei_from_f64(amount: f64, decimals: u8) -> Result<U256, String> {
Ok(try_s!(U256::from_dec_str(&amount).map_err(|e| ERRL!("{:?}", e))))
}

#[test]
fn test_wei_from_f64() {
let amount = 0.000001;
let wei = wei_from_f64(amount, 18).unwrap();
let expected_wei: U256 = 1000000000000u64.into();
assert_eq!(expected_wei, wei);

let amount = 1.000001;
let wei = wei_from_f64(amount, 18).unwrap();
let expected_wei: U256 = 1000001000000000000u64.into();
assert_eq!(expected_wei, wei);

let amount = 1.;
let wei = wei_from_f64(amount, 18).unwrap();
let expected_wei: U256 = 1000000000000000000u64.into();
assert_eq!(expected_wei, wei);

let amount = 0.000000000000000001;
let wei = wei_from_f64(amount, 18).unwrap();
let expected_wei: U256 = 1u64.into();
assert_eq!(expected_wei, wei);

let amount = 1234.;
let wei = wei_from_f64(amount, 9).unwrap();
let expected_wei: U256 = 1234000000000u64.into();
assert_eq!(expected_wei, wei);

let amount = 1234.;
let wei = wei_from_f64(amount, 0).unwrap();
let expected_wei: U256 = 1234u64.into();
assert_eq!(expected_wei, wei);

let amount = 1234.;
let wei = wei_from_f64(amount, 1).unwrap();
let expected_wei: U256 = 12340u64.into();
assert_eq!(expected_wei, wei);

let amount = 1234.12345;
let wei = wei_from_f64(amount, 1).unwrap();
let expected_wei: U256 = 12341u64.into();
assert_eq!(expected_wei, wei);
}

impl Transaction for SignedEthTx {
fn tx_hex(&self) -> Vec<u8> { rlp::encode(self).to_vec() }

Expand Down Expand Up @@ -1768,11 +1698,14 @@ fn get_token_decimals(web3: &Web3<Web3Transport>, token_addr: Address) -> Result
Ok(decimals as u8)
}

fn addr_from_str(mut addr_str: &str) -> Result<Address, String> {
if addr_str.starts_with("0x") {
addr_str = &addr_str[2..];
fn addr_from_str(addr_str: &str) -> Result<Address, String> {
if !addr_str.starts_with("0x") {
return ERR!("Address must be prefixed with 0x");
};
if !is_valid_checksum_addr(addr_str) {
return ERR!("Invalid address checksum");
}
Ok(try_s!(Address::from_str(addr_str)))
Ok(try_s!(Address::from_str(&addr_str[2..])))
}

pub fn eth_coin_from_iguana_info(info: *mut lp::iguana_info, req: &Json) -> Result<EthCoin, String> {
Expand Down Expand Up @@ -1822,3 +1755,40 @@ pub fn eth_coin_from_iguana_info(info: *mut lp::iguana_info, req: &Json) -> Resu
};
Ok(EthCoin(Arc::new(coin)))
}

/// Displays the address in mixed-case checksum form
/// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md
fn checksum_address(addr: &str) -> String {
let mut addr = addr.to_lowercase();
if addr.starts_with("0x") {
addr.replace_range(..2, "");
}

let mut hasher = Keccak256::default();
hasher.input(&addr);
let hash = hasher.result();
let mut result: String = "0x".into();
for (i, c) in addr.chars().enumerate() {
if c.is_digit(10) {
result.push(c);
} else {
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md#specification
// Convert the address to hex, but if the ith digit is a letter (ie. it's one of abcdef)
// print it in uppercase if the 4*ith bit of the hash of the lowercase hexadecimal
// address is 1 otherwise print it in lowercase.
if hash[i / 2] & (1 << (7 - 4 * (i % 2))) != 0 {
result.push(c.to_ascii_uppercase());
} else {
result.push(c.to_ascii_lowercase());
}
}
}

result
}

/// Checks that input is valid mixed-case checksum form address
/// The input must be 0x prefixed hex string
fn is_valid_checksum_addr(addr: &str) -> bool {
addr == &checksum_address(addr)
}
102 changes: 102 additions & 0 deletions mm2src/coins/eth/eth_tests.rs
@@ -0,0 +1,102 @@
use super::*;

fn check_sum(addr: &str, expected: &str) {
let actual = checksum_address(addr);
assert_eq!(expected, actual);
}

#[test]
/// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md#test-cases
fn test_check_sum_address() {
check_sum("0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359", "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359");
check_sum("0x52908400098527886e0f7030069857d2e4169ee7", "0x52908400098527886E0F7030069857D2E4169EE7");
check_sum("0x8617e340b3d01fa5f11f306f4090fd50e238070d", "0x8617E340B3D01FA5F11F306F4090FD50E238070D");
check_sum("0xde709f2102306220921060314715629080e2fb77", "0xde709f2102306220921060314715629080e2fb77");
check_sum("0x27b1fdb04752bbc536007a920d24acb045561c26", "0x27b1fdb04752bbc536007a920d24acb045561c26");
check_sum("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed");
check_sum("0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359");
check_sum("0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB");
check_sum("0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb");
}

#[test]
fn test_is_valid_checksum_addr() {
assert!(is_valid_checksum_addr("0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359"));
assert!(is_valid_checksum_addr("0x52908400098527886E0F7030069857D2E4169EE7"));
assert!(!is_valid_checksum_addr("0x8617e340B3D01FA5F11F306F4090FD50E238070D"));
assert!(!is_valid_checksum_addr("0xd1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb"));
}

#[test]
fn display_u256_with_point() {
let number = U256::from_dec_str("1000000000000000000").unwrap();
let string = display_u256_with_decimal_point(number, 18);
assert_eq!("1.", string);

let number = U256::from_dec_str("1234567890000000000").unwrap();
let string = display_u256_with_decimal_point(number, 18);
assert_eq!("1.23456789", string);

let number = U256::from_dec_str("1234567890000000000").unwrap();
let string = display_u256_with_decimal_point(number, 16);
assert_eq!("123.456789", string);

let number = U256::from_dec_str("1234567890000000000").unwrap();
let string = display_u256_with_decimal_point(number, 0);
assert_eq!("1234567890000000000.", string);

let number = U256::from_dec_str("1000000000000000").unwrap();
let string = display_u256_with_decimal_point(number, 18);
assert_eq!("0.001", string);

let number = U256::from_dec_str("0").unwrap();
let string = display_u256_with_decimal_point(number, 18);
assert_eq!("0.", string);

let number = U256::from_dec_str("0").unwrap();
let string = display_u256_with_decimal_point(number, 0);
assert_eq!("0.", string);
}

#[test]
fn test_wei_from_f64() {
let amount = 0.000001;
let wei = wei_from_f64(amount, 18).unwrap();
let expected_wei: U256 = 1000000000000u64.into();
assert_eq!(expected_wei, wei);

let amount = 1.000001;
let wei = wei_from_f64(amount, 18).unwrap();
let expected_wei: U256 = 1000001000000000000u64.into();
assert_eq!(expected_wei, wei);

let amount = 1.;
let wei = wei_from_f64(amount, 18).unwrap();
let expected_wei: U256 = 1000000000000000000u64.into();
assert_eq!(expected_wei, wei);

let amount = 0.000000000000000001;
let wei = wei_from_f64(amount, 18).unwrap();
let expected_wei: U256 = 1u64.into();
assert_eq!(expected_wei, wei);

let amount = 1234.;
let wei = wei_from_f64(amount, 9).unwrap();
let expected_wei: U256 = 1234000000000u64.into();
assert_eq!(expected_wei, wei);

let amount = 1234.;
let wei = wei_from_f64(amount, 0).unwrap();
let expected_wei: U256 = 1234u64.into();
assert_eq!(expected_wei, wei);

let amount = 1234.;
let wei = wei_from_f64(amount, 1).unwrap();
let expected_wei: U256 = 12340u64.into();
assert_eq!(expected_wei, wei);

let amount = 1234.12345;
let wei = wei_from_f64(amount, 1).unwrap();
let expected_wei: U256 = 12341u64.into();
assert_eq!(expected_wei, wei);
}
10 changes: 5 additions & 5 deletions mm2src/mm2_tests.rs
Expand Up @@ -417,7 +417,7 @@ fn test_check_balance_on_order_post() {
{"coin":"PIZZA","asset":"PIZZA","rpcport":11608,"txversion":4},
{"coin":"ETOMIC","asset":"ETOMIC","rpcport":10271,"txversion":4},
{"coin":"ETH","name":"ethereum","etomic":"0x0000000000000000000000000000000000000000","rpcport":80},
{"coin":"JST","name":"jst","etomic":"0x2b294f029fde858b2c62184e8390591755521d8e"}
{"coin":"JST","name":"jst","etomic":"0x2b294F029Fde858b2c62184e8390591755521d8E"}
]);

// start bob and immediately place the order
Expand Down Expand Up @@ -727,7 +727,7 @@ fn trade_base_rel_electrum(pairs: Vec<(&str, &str)>) {
{"coin":"PIZZA","asset":"PIZZA"},
{"coin":"ETOMIC","asset":"ETOMIC"},
{"coin":"ETH","name":"ethereum","etomic":"0x0000000000000000000000000000000000000000"},
{"coin":"JST","name":"jst","etomic":"0x2b294f029fde858b2c62184e8390591755521d8e"}
{"coin":"JST","name":"jst","etomic":"0x2b294F029Fde858b2c62184e8390591755521d8E"}
]);

let mut mm_bob = unwrap! (MarketMakerIt::start (
Expand Down Expand Up @@ -1135,7 +1135,7 @@ fn test_withdraw_and_send() {
{"coin":"PIZZA","asset":"PIZZA"},
{"coin":"ETOMIC","asset":"ETOMIC"},
{"coin":"ETH","name":"ethereum","etomic":"0x0000000000000000000000000000000000000000"},
{"coin":"JST","name":"jst","etomic":"0x2b294f029fde858b2c62184e8390591755521d8e"}
{"coin":"JST","name":"jst","etomic":"0x2b294F029Fde858b2c62184e8390591755521d8E"}
]);

let mut mm_alice = unwrap! (MarketMakerIt::start (
Expand Down Expand Up @@ -1164,8 +1164,8 @@ fn test_withdraw_and_send() {
log! ("enable_coins (alice): " [enable_res]);
withdraw_and_send(&mm_alice, "PIZZA", "RJTYiYeJ8eVvJ53n2YbrVmxWNNMVZjDGLh", &enable_res, -0.00101);
// dev chain gas price is 0 so ETH expected balance change doesn't include the fee
withdraw_and_send(&mm_alice, "ETH", "0x657980d55733b41c0c64c06003864e1aad917ca7", &enable_res, -0.001);
withdraw_and_send(&mm_alice, "JST", "0x657980d55733b41c0c64c06003864e1aad917ca7", &enable_res, -0.001);
withdraw_and_send(&mm_alice, "ETH", "0x657980d55733B41c0C64c06003864e1aAD917Ca7", &enable_res, -0.001);
withdraw_and_send(&mm_alice, "JST", "0x657980d55733B41c0C64c06003864e1aAD917Ca7", &enable_res, -0.001);
unwrap!(mm_alice.stop());
}

Expand Down

0 comments on commit 0039f23

Please sign in to comment.