Skip to content

Commit 10c8d40

Browse files
Add support to faucet for 0x addresses (#67)
Co-authored-by: Hubert <lesny.rumcajs@gmail.com>
1 parent 8c0ddf7 commit 10c8d40

File tree

4 files changed

+150
-22
lines changed

4 files changed

+150
-22
lines changed

src/address.rs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
use anyhow::ensure;
2+
use fvm_shared::address::{Address, Network};
3+
use fvm_shared::ActorID;
4+
5+
// '0x' + 20bytes
6+
const ETH_ADDRESS_LENGTH: usize = 42;
7+
const EAM_NAMESPACE: ActorID = 10;
8+
9+
fn is_valid_prefix(s: &str, n: Network) -> bool {
10+
if s.len() < 2 {
11+
return false;
12+
}
13+
14+
match n {
15+
Network::Mainnet => s.starts_with("f") || s.starts_with("0x"),
16+
Network::Testnet => s.starts_with("t") || s.starts_with("0x"),
17+
}
18+
}
19+
20+
pub fn parse_address(raw: &str, n: Network) -> anyhow::Result<Address> {
21+
let s = raw.trim().to_lowercase();
22+
23+
ensure!(is_valid_prefix(&s, n), "Not a valid {:?} address", n);
24+
25+
if s.len() > 2 && s.starts_with("0x") {
26+
// Expecting an eth address, perform further validation
27+
ensure!(s.len() == ETH_ADDRESS_LENGTH, "Invalid address length");
28+
ensure!(
29+
s.chars().skip(2).all(|c| c.is_ascii_hexdigit()),
30+
"Invalid characters in address"
31+
);
32+
33+
let addr = hex::decode(&s[2..])?;
34+
Ok(Address::new_delegated(EAM_NAMESPACE, &addr)?)
35+
} else {
36+
Ok(n.parse_address(&s)?)
37+
}
38+
}
39+
40+
#[cfg(test)]
41+
mod tests {
42+
use super::*;
43+
use fvm_shared::address::set_current_network;
44+
45+
#[test]
46+
fn test_check_address_prefix() {
47+
// Valid cases
48+
assert!(is_valid_prefix("f123...", Network::Mainnet));
49+
assert!(is_valid_prefix("0x123...", Network::Mainnet));
50+
assert!(is_valid_prefix("t456...", Network::Testnet));
51+
assert!(is_valid_prefix("0x789...", Network::Testnet));
52+
53+
// Wrong network
54+
assert!(!is_valid_prefix("f123...", Network::Testnet));
55+
assert!(!is_valid_prefix("t456...", Network::Mainnet));
56+
57+
// Bad length
58+
assert!(!is_valid_prefix("f", Network::Mainnet));
59+
assert!(!is_valid_prefix("t", Network::Testnet));
60+
assert!(!is_valid_prefix("", Network::Mainnet)); // Empty string
61+
assert!(!is_valid_prefix("abc", Network::Mainnet)); // Short address
62+
63+
// Invalid prefixes
64+
assert!(!is_valid_prefix("g123...", Network::Mainnet));
65+
assert!(!is_valid_prefix("h456...", Network::Testnet));
66+
assert!(!is_valid_prefix("123...", Network::Mainnet));
67+
assert!(!is_valid_prefix("456...", Network::Testnet));
68+
}
69+
70+
#[test]
71+
fn test_parse_mainnet_address() {
72+
let addr_str = "f1alg2sxw32ns3ech2w7r3dmp2gl2fputkl7x7jta";
73+
let addr = parse_address(addr_str, Network::Mainnet).unwrap();
74+
75+
set_current_network(Network::Mainnet); // Required to correctly stringify address
76+
assert_eq!(addr.to_string(), addr_str);
77+
}
78+
79+
#[test]
80+
fn test_parse_testnet_address() {
81+
let addr_str = "t410f2oekwcmo2pueydmaq53eic2i62crtbeyuzx2gmy";
82+
let addr = parse_address(addr_str, Network::Testnet).unwrap();
83+
84+
set_current_network(Network::Testnet); // Required to correctly stringify address
85+
assert_eq!(addr.to_string(), addr_str);
86+
}
87+
88+
#[test]
89+
fn test_parse_wrong_network() {
90+
let m_addr_str = "f1alg2sxw32ns3ech2w7r3dmp2gl2fputkl7x7jta";
91+
let err = parse_address(m_addr_str, Network::Testnet).unwrap_err();
92+
assert_eq!(err.to_string(), "Not a valid Testnet address");
93+
94+
let t_addr_str = "t410f2oekwcmo2pueydmaq53eic2i62crtbeyuzx2gmy";
95+
let err = parse_address(t_addr_str, Network::Mainnet).unwrap_err();
96+
assert_eq!(err.to_string(), "Not a valid Mainnet address");
97+
}
98+
99+
#[test]
100+
fn test_parse_eth_address_testnet() {
101+
let addr_str = "0xd388ab098ed3e84c0d808776440b48f685198498";
102+
let addr = parse_address(addr_str, Network::Testnet).unwrap();
103+
104+
let exp_addr_str = "t410f2oekwcmo2pueydmaq53eic2i62crtbeyuzx2gmy";
105+
let exp_addr = parse_address(exp_addr_str, Network::Testnet).unwrap();
106+
107+
assert_eq!(exp_addr, addr);
108+
}
109+
110+
#[test]
111+
fn test_parse_eth_address_mainnet() {
112+
let addr_str = "0xd388ab098ed3e84c0d808776440b48f685198498";
113+
let addr = parse_address(addr_str, Network::Mainnet).unwrap();
114+
115+
let exp_addr_str = "f410f2oekwcmo2pueydmaq53eic2i62crtbeyuzx2gmy";
116+
let exp_addr = parse_address(exp_addr_str, Network::Mainnet).unwrap();
117+
118+
assert_eq!(exp_addr, addr);
119+
}
120+
121+
#[test]
122+
fn test_parse_eth_address_too_short() {
123+
let addr_str = "0xd3";
124+
let e = parse_address(addr_str, Network::Mainnet).err().unwrap();
125+
126+
assert_eq!(e.to_string(), "Invalid address length");
127+
}
128+
129+
#[test]
130+
fn test_parse_eth_address_too_long() {
131+
let addr_str = "0xd388ab098ed3e84c0d808776440b48f68519849812";
132+
let e = parse_address(addr_str, Network::Mainnet).err().unwrap();
133+
134+
assert_eq!(e.to_string(), "Invalid address length");
135+
}
136+
137+
#[test]
138+
fn test_parse_eth_address_invalid_chars() {
139+
let addr_str = "0xd3!8ab098ed3e84c0d808776440b48f685198498";
140+
let e = parse_address(addr_str, Network::Mainnet).err().unwrap();
141+
142+
assert_eq!(e.to_string(), "Invalid characters in address");
143+
}
144+
}

src/faucet/views.rs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,8 @@ use leptos_use::*;
99
use super::utils::faucet_address;
1010
use crate::faucet::utils::sign_with_secret_key;
1111
use crate::{
12-
lotus_json::LotusJson,
13-
message::message_transfer,
14-
rpc_context::Provider,
15-
utils::{catch_all, parse_address},
12+
address::parse_address, lotus_json::LotusJson, message::message_transfer,
13+
rpc_context::Provider, utils::catch_all,
1614
};
1715

1816
#[component]
@@ -64,7 +62,7 @@ pub fn Faucet(target_network: Network) -> impl IntoView {
6462
let target_balance = create_local_resource_with_initial_value(
6563
move || target_address.get(),
6664
move |address| async move {
67-
if let Ok((address, _network)) = parse_address(&address) {
65+
if let Ok(address) = parse_address(&address, target_network) {
6866
Provider::from_network(target_network)
6967
.wallet_balance(address)
7068
.await
@@ -183,11 +181,8 @@ pub fn Faucet(target_network: Network) -> impl IntoView {
183181
<button
184182
class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded-r"
185183
on:click=move |_| {
186-
match parse_address(&target_address.get()) {
187-
Ok((_addr, network)) if network != target_network => {
188-
error_messages.update(|errors| errors.push("Mainnet/testnet address mismatch".to_string()));
189-
}
190-
Ok((addr, _network)) => {
184+
match parse_address(&target_address.get(), target_network) {
185+
Ok(addr) => {
191186
spawn_local(async move {
192187
catch_all(
193188
error_messages,

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod app;
22
mod rpc_context;
33
#[cfg(feature = "hydrate")]
44
use app::App;
5+
mod address;
56
mod faucet;
67
mod key;
78
mod lotus_json;

src/utils.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,7 @@
11
use std::future::Future;
22

3-
use fvm_shared::address::{Address, Network};
43
use leptos::{RwSignal, SignalUpdate as _};
54

6-
pub fn parse_address(s: &str) -> anyhow::Result<(Address, Network)> {
7-
Ok(Network::Testnet
8-
.parse_address(s)
9-
.map(|addr| (addr, Network::Testnet))
10-
.or_else(|_| {
11-
Network::Mainnet
12-
.parse_address(s)
13-
.map(|addr| (addr, Network::Mainnet))
14-
})?)
15-
}
16-
175
pub async fn catch_all(
186
errors: RwSignal<Vec<String>>,
197
cb: impl Future<Output = Result<(), anyhow::Error>>,

0 commit comments

Comments
 (0)