Skip to main content

forest/shim/
address.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use std::{fmt::Display, str::FromStr};
5
6use data_encoding::Encoding;
7use data_encoding_macro::new_encoding;
8use fvm_shared2::address::Address as Address_v2;
9use fvm_shared3::address::Address as Address_v3;
10use fvm_shared4::address::Address as Address_v4;
11use fvm_shared4::address::Address as Address_latest;
12pub use fvm_shared4::address::{Error, Network, PAYLOAD_HASH_LEN, Payload, Protocol};
13use get_size2::GetSize;
14use integer_encoding::VarInt;
15use num_traits::FromPrimitive;
16use serde::{Deserialize, Serialize};
17use std::sync::{
18    LazyLock,
19    atomic::{AtomicU8, Ordering},
20};
21
22pub type AddressId = u64;
23
24/// Zero address used to avoid allowing it to be used for verification.
25/// This is intentionally disallowed because it is an edge case with Filecoin's BLS
26/// signature verification.
27// Copied from ref-fvm due to a bug in their definition.
28pub static ZERO_ADDRESS: LazyLock<Address> = LazyLock::new(|| {
29    Network::Mainnet
30        .parse_address(
31            "f3yaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaby2smx7a",
32        )
33        .unwrap()
34        .into()
35});
36
37static GLOBAL_NETWORK: AtomicU8 = AtomicU8::new(Network::Mainnet as u8);
38
39thread_local! {
40    // Thread local network identifier. Defaults to value in GLOBAL_NETWORK.
41    static LOCAL_NETWORK: AtomicU8 = AtomicU8::new(GLOBAL_NETWORK.load(Ordering::Acquire));
42}
43
44/// For user safety, Filecoin has different addresses for its mainnet and test networks: Mainnet
45/// and testnet addresses are prefixed with `f` and `t`, respectively.
46///
47/// We use a thread-local variable to determine which format to use when parsing and pretty-printing
48/// addresses. Note that the [`Address`] structure will parse both forms, while [`StrictAddress`]
49/// will only succeed if the address has the correct network prefix.
50///
51/// The thread-local network variable is initialized to the value of the global network. This global
52/// network variable is set once when Forest has figured out which network it is using.
53pub struct CurrentNetwork;
54impl CurrentNetwork {
55    pub fn get() -> Network {
56        FromPrimitive::from_u8(LOCAL_NETWORK.with(|ident| ident.load(Ordering::Acquire)))
57            .unwrap_or(Network::Mainnet)
58    }
59
60    pub fn set(network: Network) {
61        LOCAL_NETWORK.with(|ident| ident.store(network as u8, Ordering::Release));
62    }
63
64    pub fn set_global(network: Network) {
65        GLOBAL_NETWORK.store(network as u8, Ordering::Release);
66        CurrentNetwork::set(network);
67    }
68
69    #[cfg(test)]
70    pub fn with<X>(network: Network, cb: impl FnOnce() -> X) -> X {
71        let guard = NetworkGuard::new(network);
72        let result = cb();
73        drop(guard);
74        result
75    }
76
77    #[cfg(test)]
78    fn get_global() -> Network {
79        FromPrimitive::from_u8(GLOBAL_NETWORK.load(Ordering::Acquire)).unwrap_or(Network::Mainnet)
80    }
81}
82
83#[cfg(test)]
84struct NetworkGuard(Network);
85#[cfg(test)]
86mod network_guard_impl {
87    use super::*;
88
89    impl NetworkGuard {
90        pub fn new(new_network: Network) -> Self {
91            let previous_network = CurrentNetwork::get();
92            CurrentNetwork::set(new_network);
93            NetworkGuard(previous_network)
94        }
95    }
96
97    impl Drop for NetworkGuard {
98        fn drop(&mut self) {
99            CurrentNetwork::set(self.0);
100        }
101    }
102}
103
104/// A Filecoin address is an identifier that refers to an actor in the Filecoin state. All actors
105/// (miner actors, the storage market actor, account actors) have an address. This address encodes
106/// information about the network to which an actor belongs, the specific type of address encoding,
107/// the address payload itself, and a checksum. The goal of this format is to provide a robust
108/// address format that is both easy to use and resistant to errors.
109///
110/// Addresses are prefixed with either a mainnet tag or a testnet tag. The [`Address`] type will
111/// parse both versions and discard the prefix. See also [`StrictAddress`].
112///
113/// For more information, see: <https://spec.filecoin.io/appendix/address/>
114#[derive(
115    Copy,
116    Clone,
117    Debug,
118    Hash,
119    PartialEq,
120    Eq,
121    PartialOrd,
122    Ord,
123    Serialize,
124    Deserialize,
125    derive_more::Deref,
126    derive_more::DerefMut,
127    derive_more::From,
128    derive_more::Into,
129)]
130#[serde(transparent)]
131#[cfg_attr(test, derive(derive_quickcheck_arbitrary::Arbitrary))]
132pub struct Address(Address_latest);
133
134impl Default for Address {
135    fn default() -> Self {
136        Address(Address_latest::new_id(0))
137    }
138}
139
140impl Address {
141    pub const SYSTEM_ACTOR: Address = Address::new_id(0);
142    pub const INIT_ACTOR: Address = Address::new_id(1);
143    pub const REWARD_ACTOR: Address = Address::new_id(2);
144    pub const CRON_ACTOR: Address = Address::new_id(3);
145    pub const POWER_ACTOR: Address = Address::new_id(4);
146    pub const MARKET_ACTOR: Address = Address::new_id(5);
147    pub const VERIFIED_REGISTRY_ACTOR: Address = Address::new_id(6);
148    pub const DATACAP_TOKEN_ACTOR: Address = Address::new_id(7);
149    pub const ETHEREUM_ACCOUNT_MANAGER_ACTOR: Address = Address::new_id(10);
150    pub const SAFT_ACTOR: Address = Address::new_id(122);
151    pub const RESERVE_ACTOR: Address = Address::new_id(90);
152    pub const CHAOS_ACTOR: Address = Address::new_id(98);
153    pub const BURNT_FUNDS_ACTOR: Address = Address::new_id(99);
154
155    pub const fn new_id(id: u64) -> Self {
156        Address(Address_latest::new_id(id))
157    }
158
159    pub fn new_actor(data: &[u8]) -> Self {
160        Address(Address_latest::new_actor(data))
161    }
162
163    pub fn new_bls(pubkey: &[u8]) -> Result<Self, Error> {
164        Address_latest::new_bls(pubkey).map(Address::from)
165    }
166
167    pub fn new_secp256k1(pubkey: &[u8]) -> Result<Self, Error> {
168        Address_latest::new_secp256k1(pubkey).map(Address::from)
169    }
170
171    pub fn new_delegated(ns: u64, subaddress: &[u8]) -> Result<Self, Error> {
172        Ok(Self(Address_latest::new_delegated(ns, subaddress)?))
173    }
174
175    pub fn protocol(&self) -> Protocol {
176        self.0.protocol()
177    }
178
179    pub fn into_payload(self) -> Payload {
180        self.0.into_payload()
181    }
182
183    pub fn from_bytes(bz: &[u8]) -> Result<Self, Error> {
184        Address_latest::from_bytes(bz).map(Address)
185    }
186}
187
188impl FromStr for Address {
189    type Err = <Address_latest as FromStr>::Err;
190
191    fn from_str(s: &str) -> Result<Self, Self::Err> {
192        Network::Testnet
193            .parse_address(s)
194            .or_else(|_| Network::Mainnet.parse_address(s))
195            .map(Address::from)
196    }
197}
198
199/// defines the encoder for `base32` encoding with the provided string with no padding
200const ADDRESS_ENCODER: Encoding = new_encoding! {
201    symbols: "abcdefghijklmnopqrstuvwxyz234567",
202    padding: None,
203};
204
205impl Display for Address {
206    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207        use fvm_shared4::address::CHECKSUM_HASH_LEN;
208        const MAINNET_PREFIX: &str = "f";
209        const TESTNET_PREFIX: &str = "t";
210
211        let protocol = self.protocol();
212
213        let prefix = if matches!(CurrentNetwork::get(), Network::Mainnet) {
214            MAINNET_PREFIX
215        } else {
216            TESTNET_PREFIX
217        };
218
219        // write `fP` where P is the protocol number.
220        write!(f, "{prefix}{protocol}")?;
221
222        fn write_payload(
223            f: &mut std::fmt::Formatter<'_>,
224            protocol: Protocol,
225            prefix: Option<&[u8]>,
226            data: &[u8],
227        ) -> std::fmt::Result {
228            let mut hasher = blake2b_simd::Params::new()
229                .hash_length(CHECKSUM_HASH_LEN)
230                .to_state();
231            hasher.update(&[protocol as u8]);
232            if let Some(prefix) = prefix {
233                hasher.update(prefix);
234            }
235            hasher.update(data);
236
237            let mut buf = Vec::with_capacity(data.len() + CHECKSUM_HASH_LEN);
238            buf.extend(data);
239            buf.extend(hasher.finalize().as_bytes());
240
241            f.write_str(&ADDRESS_ENCODER.encode(&buf))
242        }
243
244        match self.payload() {
245            Payload::ID(id) => write!(f, "{id}"),
246            Payload::Secp256k1(data) | Payload::Actor(data) => {
247                write_payload(f, protocol, None, data)
248            }
249            Payload::BLS(data) => write_payload(f, protocol, None, data),
250            Payload::Delegated(addr) => {
251                write!(f, "{}f", addr.namespace())?;
252                write_payload(
253                    f,
254                    protocol,
255                    Some(&addr.namespace().encode_var_vec()),
256                    addr.subaddress(),
257                )
258            }
259        }
260    }
261}
262
263impl GetSize for Address {
264    fn get_heap_size(&self) -> usize {
265        0 // all variants of the internal payload are stack-allocated
266    }
267}
268
269/// A Filecoin address is an identifier that refers to an actor in the Filecoin state. All actors
270/// (miner actors, the storage market actor, account actors) have an address. This address encodes
271/// information about the network to which an actor belongs, the specific type of address encoding,
272/// the address payload itself, and a checksum. The goal of this format is to provide a robust
273/// address format that is both easy to use and resistant to errors.
274///
275/// Addresses are prefixed with either a mainnet tag or a testnet tag. The [`StrictAddress`] type
276/// will fail to parse addresses unless they have the correct tag indicated by [`CurrentNetwork`].
277///
278/// For more information, see: <https://spec.filecoin.io/appendix/address/>
279#[derive(
280    Copy,
281    Clone,
282    Debug,
283    Hash,
284    PartialEq,
285    Eq,
286    PartialOrd,
287    Ord,
288    Serialize,
289    Deserialize,
290    derive_more::Display,
291    derive_more::From,
292    derive_more::Into,
293)]
294#[serde(transparent)]
295pub struct StrictAddress(pub Address);
296
297impl FromStr for StrictAddress {
298    type Err = <Address_latest as FromStr>::Err;
299
300    fn from_str(s: &str) -> Result<Self, Self::Err> {
301        let fvm_addr = CurrentNetwork::get().parse_address(s)?;
302        Ok(StrictAddress(fvm_addr.into()))
303    }
304}
305
306// Conversion implementations.
307// Note for `::from_bytes`. Both FVM2 and FVM3 addresses values as bytes must be
308// identical and able to do a conversion, otherwise it is a logic error and
309// Forest should not continue so there is no point in `TryFrom`.
310
311impl From<StrictAddress> for Address_v3 {
312    fn from(other: StrictAddress) -> Self {
313        other.0.into()
314    }
315}
316
317impl From<StrictAddress> for Address_v4 {
318    fn from(other: StrictAddress) -> Self {
319        other.0.into()
320    }
321}
322
323impl From<&Address_v4> for Address {
324    fn from(other: &Address_v4) -> Self {
325        Address(*other)
326    }
327}
328
329impl From<&Address> for Address_v4 {
330    fn from(other: &Address) -> Self {
331        other.0
332    }
333}
334
335impl<'a> From<&'a Address> for &'a Address_v4 {
336    fn from(addr: &'a Address) -> Self {
337        &addr.0
338    }
339}
340
341impl From<&Address_v3> for Address {
342    fn from(other: &Address_v3) -> Self {
343        Address::from(
344            Address_v4::from_bytes(&other.to_bytes()).unwrap_or_else(|e| {
345                panic!("Couldn't convert from FVM3 address to FVM4 address: {other}, {e}")
346            }),
347        )
348    }
349}
350
351impl From<Address_v3> for Address {
352    fn from(other: Address_v3) -> Self {
353        (&other).into()
354    }
355}
356
357impl From<&Address_v2> for Address {
358    fn from(other: &Address_v2) -> Self {
359        Address::from(
360            Address_v4::from_bytes(&other.to_bytes()).unwrap_or_else(|e| {
361                panic!("Couldn't convert from FVM2 address to FVM4 address: {other}, {e}")
362            }),
363        )
364    }
365}
366
367impl From<Address_v2> for Address {
368    fn from(other: Address_v2) -> Self {
369        (&other).into()
370    }
371}
372
373impl From<&Address> for Address_v3 {
374    fn from(other: &Address) -> Self {
375        Address_v3::from_bytes(&other.to_bytes()).unwrap_or_else(|e| {
376            panic!("Couldn't convert from FVM4 address to FVM3 address: {other}, {e}")
377        })
378    }
379}
380
381impl From<Address> for Address_v3 {
382    fn from(other: Address) -> Self {
383        (&other).into()
384    }
385}
386
387impl From<&Address> for Address_v2 {
388    fn from(other: &Address) -> Self {
389        Address_v2::from_bytes(&other.to_bytes()).unwrap_or_else(|e| {
390            panic!("Couldn't convert from FVM4 address to FVM2 address: {other}, {e}")
391        })
392    }
393}
394
395impl From<Address> for Address_v2 {
396    fn from(other: Address) -> Address_v2 {
397        (&other).into()
398    }
399}
400
401#[cfg(test)]
402fn flip_network(input: Network) -> Network {
403    match input {
404        Network::Mainnet => Network::Testnet,
405        Network::Testnet => Network::Mainnet,
406    }
407}
408
409#[test]
410fn relaxed_address_parsing() {
411    assert!(Address::from_str("t01234").is_ok());
412    assert!(Address::from_str("f01234").is_ok());
413}
414
415#[test]
416fn strict_address_parsing() {
417    CurrentNetwork::with(Network::Mainnet, || {
418        assert!(StrictAddress::from_str("f01234").is_ok());
419        assert!(StrictAddress::from_str("t01234").is_err());
420    });
421    CurrentNetwork::with(Network::Testnet, || {
422        assert!(StrictAddress::from_str("f01234").is_err());
423        assert!(StrictAddress::from_str("t01234").is_ok());
424    });
425}
426
427#[test]
428fn set_with_network() {
429    let outer_network = CurrentNetwork::get();
430    let inner_network = flip_network(outer_network);
431    CurrentNetwork::with(inner_network, || {
432        assert_eq!(CurrentNetwork::get(), inner_network);
433    });
434    assert_eq!(outer_network, CurrentNetwork::get());
435}
436
437#[test]
438fn unwind_current_network_on_panic() {
439    let outer_network = CurrentNetwork::get();
440    let inner_network = flip_network(outer_network);
441    assert!(
442        std::panic::catch_unwind(|| {
443            CurrentNetwork::with(inner_network, || {
444                panic!("unwinding stack");
445            })
446        })
447        .is_err()
448    );
449    let new_outer_network = CurrentNetwork::get();
450    assert_eq!(outer_network, new_outer_network);
451}
452
453#[test]
454fn inherit_global_network() {
455    let outer_network = CurrentNetwork::get_global();
456    let inner_network = flip_network(outer_network);
457    CurrentNetwork::set_global(inner_network);
458    std::thread::spawn(move || {
459        assert_eq!(CurrentNetwork::get(), inner_network);
460    })
461    .join()
462    .unwrap();
463    CurrentNetwork::set_global(outer_network);
464}