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