1use std::sync::LazyLock;
7
8use r402_core::chain::NetworkInfo;
9
10use crate::chain::{CasperChainReference, CasperTokenDeployment, ContractPackageHash};
11use crate::motes::CSPR_DECIMALS;
12
13pub static CASPER_NETWORKS: &[NetworkInfo] = &[
18 NetworkInfo {
19 name: "casper",
20 namespace: "casper",
21 reference: "casper",
22 },
23 NetworkInfo {
24 name: "casper-test",
25 namespace: "casper",
26 reference: "casper-test",
27 },
28];
29
30pub const WCSPR_NAME: &str = "Wrapped CSPR";
32
33pub const WCSPR_VERSION: &str = "1";
35
36const WCSPR_TESTNET_PACKAGE: &str =
40 "3d80df21ba4ee4d66a2a1f60c32570dd5685e4b279f6538162a5fd1314847c1e";
41
42#[allow(
48 clippy::expect_used,
49 reason = "hardcoded package hashes are validated at crate build time by the unit tests"
50)]
51static WCSPR_DEPLOYMENTS: LazyLock<Vec<CasperTokenDeployment>> = LazyLock::new(|| {
52 vec![CasperTokenDeployment::new(
53 CasperChainReference::CASPER_TEST,
54 WCSPR_TESTNET_PACKAGE
55 .parse::<ContractPackageHash>()
56 .expect("built-in wCSPR testnet package hash is valid"),
57 CSPR_DECIMALS,
58 WCSPR_NAME,
59 WCSPR_VERSION,
60 )]
61});
62
63#[must_use]
65pub fn wcspr_casper_deployments() -> &'static [CasperTokenDeployment] {
66 &WCSPR_DEPLOYMENTS
67}
68
69#[must_use]
71pub fn wcspr_casper_deployment(
72 chain: CasperChainReference,
73) -> Option<&'static CasperTokenDeployment> {
74 WCSPR_DEPLOYMENTS
75 .iter()
76 .find(|deployment| deployment.chain_reference == chain)
77}
78
79#[derive(Debug, Clone, Copy)]
90#[allow(
91 clippy::upper_case_acronyms,
92 reason = "WCSPR is a well-known token ticker"
93)]
94pub struct WCSPR;
95
96#[allow(
97 clippy::missing_panics_doc,
98 clippy::expect_used,
99 reason = "static deployment lookups are infallible for built-in data"
100)]
101impl WCSPR {
102 #[must_use]
106 pub fn on(chain: CasperChainReference) -> Option<&'static CasperTokenDeployment> {
107 wcspr_casper_deployment(chain)
108 }
109
110 #[must_use]
112 pub fn all() -> &'static [CasperTokenDeployment] {
113 wcspr_casper_deployments()
114 }
115
116 #[must_use]
118 pub fn casper_test() -> &'static CasperTokenDeployment {
119 wcspr_casper_deployment(CasperChainReference::CASPER_TEST)
120 .expect("built-in wCSPR deployment for Casper testnet missing")
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn network_table_matches_caip2_ids() {
130 let ids: Vec<String> = CASPER_NETWORKS
131 .iter()
132 .map(|net| net.chain_id().to_string())
133 .collect();
134 assert_eq!(ids, vec!["casper:casper", "casper:casper-test"]);
135 }
136
137 #[test]
138 fn every_network_info_maps_to_a_chain_reference() {
139 for network in CASPER_NETWORKS {
140 let chain_id = network.chain_id();
141 assert!(
142 CasperChainReference::try_from(chain_id).is_ok(),
143 "network {} has no chain reference",
144 network.name
145 );
146 }
147 }
148
149 #[test]
150 fn builtin_wcspr_deployment_is_well_formed() {
151 let deployment = WCSPR::casper_test();
152 assert_eq!(
153 deployment.chain_reference,
154 CasperChainReference::CASPER_TEST
155 );
156 assert_eq!(deployment.decimals, CSPR_DECIMALS);
157 assert_eq!(deployment.name, WCSPR_NAME);
158 assert_eq!(deployment.version, WCSPR_VERSION);
159 assert_eq!(deployment.address.to_string(), WCSPR_TESTNET_PACKAGE);
160 }
161
162 #[test]
163 fn lookup_returns_none_for_chains_without_deployments() {
164 assert!(WCSPR::on(CasperChainReference::CASPER).is_none());
165 assert_eq!(WCSPR::all().len(), 1);
166 }
167
168 #[test]
169 fn deployment_amount_is_exact() {
170 let amount = WCSPR::casper_test().amount(1_000_000_000);
171 assert_eq!(amount.amount.inner(), 1_000_000_000);
172 assert_eq!(amount.amount.to_cspr_string(), "1");
173 }
174}