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
263// all variants of the internal payload are stack-allocated
264impl GetSize for Address {}
265
266/// A Filecoin address is an identifier that refers to an actor in the Filecoin state. All actors
267/// (miner actors, the storage market actor, account actors) have an address. This address encodes
268/// information about the network to which an actor belongs, the specific type of address encoding,
269/// the address payload itself, and a checksum. The goal of this format is to provide a robust
270/// address format that is both easy to use and resistant to errors.
271///
272/// Addresses are prefixed with either a mainnet tag or a testnet tag. The [`StrictAddress`] type
273/// will fail to parse addresses unless they have the correct tag indicated by [`CurrentNetwork`].
274///
275/// For more information, see: <https://spec.filecoin.io/appendix/address/>
276#[derive(
277    Copy,
278    Clone,
279    Debug,
280    Hash,
281    PartialEq,
282    Eq,
283    PartialOrd,
284    Ord,
285    Serialize,
286    Deserialize,
287    derive_more::Display,
288    derive_more::From,
289    derive_more::Into,
290)]
291#[serde(transparent)]
292pub struct StrictAddress(pub Address);
293
294impl FromStr for StrictAddress {
295    type Err = <Address_latest as FromStr>::Err;
296
297    fn from_str(s: &str) -> Result<Self, Self::Err> {
298        let fvm_addr = CurrentNetwork::get().parse_address(s)?;
299        Ok(StrictAddress(fvm_addr.into()))
300    }
301}
302
303// Conversion implementations.
304// Note for `::from_bytes`. Both FVM2 and FVM3 addresses values as bytes must be
305// identical and able to do a conversion, otherwise it is a logic error and
306// Forest should not continue so there is no point in `TryFrom`.
307
308impl From<StrictAddress> for Address_v3 {
309    fn from(other: StrictAddress) -> Self {
310        other.0.into()
311    }
312}
313
314impl From<StrictAddress> for Address_v4 {
315    fn from(other: StrictAddress) -> Self {
316        other.0.into()
317    }
318}
319
320impl From<&Address_v4> for Address {
321    fn from(other: &Address_v4) -> Self {
322        Address(*other)
323    }
324}
325
326impl From<&Address> for Address_v4 {
327    fn from(other: &Address) -> Self {
328        other.0
329    }
330}
331
332impl<'a> From<&'a Address> for &'a Address_v4 {
333    fn from(addr: &'a Address) -> Self {
334        &addr.0
335    }
336}
337
338impl From<&Address_v3> for Address {
339    fn from(other: &Address_v3) -> Self {
340        Address::from(
341            Address_v4::from_bytes(&other.to_bytes()).unwrap_or_else(|e| {
342                panic!("Couldn't convert from FVM3 address to FVM4 address: {other}, {e}")
343            }),
344        )
345    }
346}
347
348impl From<Address_v3> for Address {
349    fn from(other: Address_v3) -> Self {
350        (&other).into()
351    }
352}
353
354impl From<&Address_v2> for Address {
355    fn from(other: &Address_v2) -> Self {
356        Address::from(
357            Address_v4::from_bytes(&other.to_bytes()).unwrap_or_else(|e| {
358                panic!("Couldn't convert from FVM2 address to FVM4 address: {other}, {e}")
359            }),
360        )
361    }
362}
363
364impl From<Address_v2> for Address {
365    fn from(other: Address_v2) -> Self {
366        (&other).into()
367    }
368}
369
370impl From<&Address> for Address_v3 {
371    fn from(other: &Address) -> Self {
372        Address_v3::from_bytes(&other.to_bytes()).unwrap_or_else(|e| {
373            panic!("Couldn't convert from FVM4 address to FVM3 address: {other}, {e}")
374        })
375    }
376}
377
378impl From<Address> for Address_v3 {
379    fn from(other: Address) -> Self {
380        (&other).into()
381    }
382}
383
384impl From<&Address> for Address_v2 {
385    fn from(other: &Address) -> Self {
386        Address_v2::from_bytes(&other.to_bytes()).unwrap_or_else(|e| {
387            panic!("Couldn't convert from FVM4 address to FVM2 address: {other}, {e}")
388        })
389    }
390}
391
392impl From<Address> for Address_v2 {
393    fn from(other: Address) -> Address_v2 {
394        (&other).into()
395    }
396}
397
398#[cfg(test)]
399fn flip_network(input: Network) -> Network {
400    match input {
401        Network::Mainnet => Network::Testnet,
402        Network::Testnet => Network::Mainnet,
403    }
404}
405
406#[test]
407fn relaxed_address_parsing() {
408    assert!(Address::from_str("t01234").is_ok());
409    assert!(Address::from_str("f01234").is_ok());
410}
411
412#[test]
413fn strict_address_parsing() {
414    CurrentNetwork::with(Network::Mainnet, || {
415        assert!(StrictAddress::from_str("f01234").is_ok());
416        assert!(StrictAddress::from_str("t01234").is_err());
417    });
418    CurrentNetwork::with(Network::Testnet, || {
419        assert!(StrictAddress::from_str("f01234").is_err());
420        assert!(StrictAddress::from_str("t01234").is_ok());
421    });
422}
423
424#[test]
425fn set_with_network() {
426    let outer_network = CurrentNetwork::get();
427    let inner_network = flip_network(outer_network);
428    CurrentNetwork::with(inner_network, || {
429        assert_eq!(CurrentNetwork::get(), inner_network);
430    });
431    assert_eq!(outer_network, CurrentNetwork::get());
432}
433
434#[test]
435fn unwind_current_network_on_panic() {
436    let outer_network = CurrentNetwork::get();
437    let inner_network = flip_network(outer_network);
438    assert!(
439        std::panic::catch_unwind(|| {
440            CurrentNetwork::with(inner_network, || {
441                panic!("unwinding stack");
442            })
443        })
444        .is_err()
445    );
446    let new_outer_network = CurrentNetwork::get();
447    assert_eq!(outer_network, new_outer_network);
448}
449
450#[test]
451fn inherit_global_network() {
452    let outer_network = CurrentNetwork::get_global();
453    let inner_network = flip_network(outer_network);
454    CurrentNetwork::set_global(inner_network);
455    std::thread::spawn(move || {
456        assert_eq!(CurrentNetwork::get(), inner_network);
457    })
458    .join()
459    .unwrap();
460    CurrentNetwork::set_global(outer_network);
461}