Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1abfceb
Add delegated address parsing
ansermino Nov 29, 2024
8747110
Appease the linter gods
ansermino Nov 29, 2024
4345173
Merge branch 'main' into ansermino/address-support
ansermino Dec 2, 2024
84664bb
Refactor parse method and test
ansermino Dec 10, 2024
23a40b0
Merge remote-tracking branch 'origin/ansermino/address-support' into …
ansermino Dec 10, 2024
dce4f5c
Merge remote-tracking branch 'origin/main' into ansermino/address-sup…
ansermino Dec 10, 2024
30b990e
Revert parse_address to not return network
ansermino Dec 10, 2024
99fb965
Add additional test cases, check 0x length
ansermino Dec 10, 2024
8846b85
Pass network to parse_address
ansermino Dec 10, 2024
5b37a29
Fix error handling, lint
ansermino Dec 10, 2024
f85af46
Apply suggestions from code review
ansermino Dec 11, 2024
2761aa7
Fix import, run linters
ansermino Dec 11, 2024
8d0701c
Merge remote-tracking branch 'origin/main' into ansermino/address-sup…
ansermino Dec 11, 2024
2e46d32
Add constants, sanitize addr input, add more test cases
ansermino Dec 11, 2024
c35abde
Refactor code to separate file and integrate with latest changes
ansermino Dec 11, 2024
dd13188
Revert unnecessary change
ansermino Dec 11, 2024
a16513b
Check eth addr first
ansermino Dec 12, 2024
a85d29f
Lint
ansermino Dec 12, 2024
336d8bf
Make it more rusty
ansermino Dec 12, 2024
6609dd8
Apply suggestions from code review
ansermino Dec 12, 2024
a7c1280
Revert "Apply suggestions from code review"
ansermino Dec 12, 2024
2816aa3
Improve eth address validation
ansermino Dec 12, 2024
1da8d43
Flexing my newfound rust knowledge
ansermino Dec 12, 2024
601f803
Lint me baby, one more time!
ansermino Dec 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions src/address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
use anyhow::ensure;
use fvm_shared::address::{Address, Network};
use fvm_shared::ActorID;

// '0x' + 20bytes
const ETH_ADDRESS_LENGTH: usize = 42;
const EAM_NAMESPACE: ActorID = 10;

fn is_valid_prefix(s: &str, n: Network) -> bool {
if s.len() < 2 {
return false;
}

match n {
Network::Mainnet => s.starts_with("f") || s.starts_with("0x"),
Network::Testnet => s.starts_with("t") || s.starts_with("0x"),
}
}

pub fn parse_address(raw: &str, n: Network) -> anyhow::Result<Address> {
let s = raw.trim().to_lowercase();

ensure!(is_valid_prefix(&s, n), "Not a valid {:?} address", n);

if s.len() > 2 && s.starts_with("0x") {
// Expecting an eth address, perform further validation
ensure!(s.len() == ETH_ADDRESS_LENGTH, "Invalid address length");
ensure!(
s.chars().skip(2).all(|c| c.is_ascii_hexdigit()),
"Invalid characters in address"
);

let addr = hex::decode(&s[2..])?;
Ok(Address::new_delegated(EAM_NAMESPACE, &addr)?)
} else {
Ok(n.parse_address(&s)?)
}
}

#[cfg(test)]
mod tests {
use super::*;
use fvm_shared::address::set_current_network;

#[test]
fn test_check_address_prefix() {
// Valid cases
assert!(is_valid_prefix("f123...", Network::Mainnet));
assert!(is_valid_prefix("0x123...", Network::Mainnet));
assert!(is_valid_prefix("t456...", Network::Testnet));
assert!(is_valid_prefix("0x789...", Network::Testnet));

// Wrong network
assert!(!is_valid_prefix("f123...", Network::Testnet));
assert!(!is_valid_prefix("t456...", Network::Mainnet));

// Bad length
assert!(!is_valid_prefix("f", Network::Mainnet));
assert!(!is_valid_prefix("t", Network::Testnet));
assert!(!is_valid_prefix("", Network::Mainnet)); // Empty string
assert!(!is_valid_prefix("abc", Network::Mainnet)); // Short address

// Invalid prefixes
assert!(!is_valid_prefix("g123...", Network::Mainnet));
assert!(!is_valid_prefix("h456...", Network::Testnet));
assert!(!is_valid_prefix("123...", Network::Mainnet));
assert!(!is_valid_prefix("456...", Network::Testnet));
}

#[test]
fn test_parse_mainnet_address() {
let addr_str = "f1alg2sxw32ns3ech2w7r3dmp2gl2fputkl7x7jta";
let addr = parse_address(addr_str, Network::Mainnet).unwrap();

set_current_network(Network::Mainnet); // Required to correctly stringify address
assert_eq!(addr.to_string(), addr_str);
}

#[test]
fn test_parse_testnet_address() {
let addr_str = "t410f2oekwcmo2pueydmaq53eic2i62crtbeyuzx2gmy";
let addr = parse_address(addr_str, Network::Testnet).unwrap();

set_current_network(Network::Testnet); // Required to correctly stringify address
assert_eq!(addr.to_string(), addr_str);
}

#[test]
fn test_parse_wrong_network() {
let m_addr_str = "f1alg2sxw32ns3ech2w7r3dmp2gl2fputkl7x7jta";
let err = parse_address(m_addr_str, Network::Testnet).unwrap_err();
assert_eq!(err.to_string(), "Not a valid Testnet address");

let t_addr_str = "t410f2oekwcmo2pueydmaq53eic2i62crtbeyuzx2gmy";
let err = parse_address(t_addr_str, Network::Mainnet).unwrap_err();
assert_eq!(err.to_string(), "Not a valid Mainnet address");
}

#[test]
fn test_parse_eth_address_testnet() {
let addr_str = "0xd388ab098ed3e84c0d808776440b48f685198498";
let addr = parse_address(addr_str, Network::Testnet).unwrap();

let exp_addr_str = "t410f2oekwcmo2pueydmaq53eic2i62crtbeyuzx2gmy";
let exp_addr = parse_address(exp_addr_str, Network::Testnet).unwrap();

assert_eq!(exp_addr, addr);
}

#[test]
fn test_parse_eth_address_mainnet() {
let addr_str = "0xd388ab098ed3e84c0d808776440b48f685198498";
let addr = parse_address(addr_str, Network::Mainnet).unwrap();

let exp_addr_str = "f410f2oekwcmo2pueydmaq53eic2i62crtbeyuzx2gmy";
let exp_addr = parse_address(exp_addr_str, Network::Mainnet).unwrap();

assert_eq!(exp_addr, addr);
}

#[test]
fn test_parse_eth_address_too_short() {
let addr_str = "0xd3";
let e = parse_address(addr_str, Network::Mainnet).err().unwrap();

assert_eq!(e.to_string(), "Invalid address length");
}

#[test]
fn test_parse_eth_address_too_long() {
let addr_str = "0xd388ab098ed3e84c0d808776440b48f68519849812";
let e = parse_address(addr_str, Network::Mainnet).err().unwrap();

assert_eq!(e.to_string(), "Invalid address length");
}

#[test]
fn test_parse_eth_address_invalid_chars() {
let addr_str = "0xd3!8ab098ed3e84c0d808776440b48f685198498";
let e = parse_address(addr_str, Network::Mainnet).err().unwrap();

assert_eq!(e.to_string(), "Invalid characters in address");
}
}
15 changes: 5 additions & 10 deletions src/faucet/views.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@ use leptos_use::*;
use super::utils::faucet_address;
use crate::faucet::utils::sign_with_secret_key;
use crate::{
lotus_json::LotusJson,
message::message_transfer,
rpc_context::Provider,
utils::{catch_all, parse_address},
address::parse_address, lotus_json::LotusJson, message::message_transfer,
rpc_context::Provider, utils::catch_all,
};

#[component]
Expand Down Expand Up @@ -64,7 +62,7 @@ pub fn Faucet(target_network: Network) -> impl IntoView {
let target_balance = create_local_resource_with_initial_value(
move || target_address.get(),
move |address| async move {
if let Ok((address, _network)) = parse_address(&address) {
if let Ok(address) = parse_address(&address, target_network) {
Provider::from_network(target_network)
.wallet_balance(address)
.await
Expand Down Expand Up @@ -183,11 +181,8 @@ pub fn Faucet(target_network: Network) -> impl IntoView {
<button
class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded-r"
on:click=move |_| {
match parse_address(&target_address.get()) {
Ok((_addr, network)) if network != target_network => {
error_messages.update(|errors| errors.push("Mainnet/testnet address mismatch".to_string()));
}
Ok((addr, _network)) => {
match parse_address(&target_address.get(), target_network) {
Ok(addr) => {
spawn_local(async move {
catch_all(
error_messages,
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod app;
mod rpc_context;
#[cfg(feature = "hydrate")]
use app::App;
mod address;
mod faucet;
mod key;
mod lotus_json;
Expand Down
12 changes: 0 additions & 12 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,7 @@
use std::future::Future;

use fvm_shared::address::{Address, Network};
use leptos::{RwSignal, SignalUpdate as _};

pub fn parse_address(s: &str) -> anyhow::Result<(Address, Network)> {
Ok(Network::Testnet
.parse_address(s)
.map(|addr| (addr, Network::Testnet))
.or_else(|_| {
Network::Mainnet
.parse_address(s)
.map(|addr| (addr, Network::Mainnet))
})?)
}

pub async fn catch_all(
errors: RwSignal<Vec<String>>,
cb: impl Future<Output = Result<(), anyhow::Error>>,
Expand Down
Loading