Skip to main content

amaru_kernel/cardano/
pool_params.rs

1// Copyright 2025 PRAGMA
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::{
16    Hash, Lovelace, Nullable, PoolId, PoolMetadata, RationalNumber, Relay, RewardAccount, Set, cbor,
17    size::{KEY, VRF_KEY},
18};
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct PoolParams {
22    pub id: PoolId,
23    pub vrf: Hash<VRF_KEY>,
24    pub pledge: Lovelace,
25    pub cost: Lovelace,
26    pub margin: RationalNumber,
27    pub reward_account: RewardAccount,
28    pub owners: Set<Hash<KEY>>,
29    pub relays: Vec<Relay>,
30    pub metadata: Nullable<PoolMetadata>,
31}
32
33impl<C> cbor::encode::Encode<C> for PoolParams {
34    fn encode<W: cbor::encode::Write>(
35        &self,
36        e: &mut cbor::Encoder<W>,
37        ctx: &mut C,
38    ) -> Result<(), cbor::encode::Error<W::Error>> {
39        e.array(9)?;
40        e.encode_with(self.id, ctx)?;
41        e.encode_with(self.vrf, ctx)?;
42        e.encode_with(self.pledge, ctx)?;
43        e.encode_with(self.cost, ctx)?;
44        e.encode_with(&self.margin, ctx)?;
45        e.encode_with(&self.reward_account, ctx)?;
46        e.encode_with(&self.owners, ctx)?;
47        e.encode_with(&self.relays, ctx)?;
48        e.encode_with(&self.metadata, ctx)?;
49        Ok(())
50    }
51}
52
53impl<'b, C> cbor::decode::Decode<'b, C> for PoolParams {
54    fn decode(d: &mut cbor::Decoder<'b>, ctx: &mut C) -> Result<Self, cbor::decode::Error> {
55        let _len = d.array()?;
56        Ok(PoolParams {
57            id: d.decode_with(ctx)?,
58            vrf: d.decode_with(ctx)?,
59            pledge: d.decode_with(ctx)?,
60            cost: d.decode_with(ctx)?,
61            margin: d.decode_with(ctx)?,
62            reward_account: d.decode_with(ctx)?,
63            owners: d.decode_with(ctx)?,
64            relays: d.decode_with(ctx)?,
65            metadata: d.decode_with(ctx)?,
66        })
67    }
68}
69
70impl serde::Serialize for PoolParams {
71    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
72        use std::collections::BTreeMap;
73
74        use pallas_addresses::Address;
75        use serde::ser::SerializeStruct;
76
77        fn as_lovelace_map(n: u64) -> BTreeMap<String, BTreeMap<String, u64>> {
78            let mut lovelace = BTreeMap::new();
79            lovelace.insert("lovelace".to_string(), n);
80            let mut ada = BTreeMap::new();
81            ada.insert("ada".to_string(), lovelace);
82            ada
83        }
84
85        fn as_string_ratio(r: &RationalNumber) -> String {
86            format!("{}/{}", r.numerator, r.denominator)
87        }
88
89        fn as_bech32_addr(bytes: &[u8]) -> Result<String, pallas_addresses::Error> {
90            Address::from_bytes(bytes).and_then(|addr| addr.to_bech32())
91        }
92
93        struct WrapRelay<'a>(&'a Relay);
94
95        impl serde::Serialize for WrapRelay<'_> {
96            fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
97                match self.0 {
98                    Relay::SingleHostAddr(port, ipv4, ipv6) => {
99                        let mut s = serializer.serialize_struct("Relay::SingleHostAddr", 4)?;
100                        s.serialize_field("type", "ipAddress")?;
101                        if let Nullable::Some(ipv4) = ipv4 {
102                            s.serialize_field("ipv4", &format!("{}.{}.{}.{}", ipv4[0], ipv4[1], ipv4[2], ipv4[3]))?;
103                        }
104                        if let Nullable::Some(ipv6) = ipv6 {
105                            let bytes: [u8; 16] = [
106                                ipv6[3], ipv6[2], ipv6[1], ipv6[0], // 1st fragment
107                                ipv6[7], ipv6[6], ipv6[5], ipv6[4], // 2nd fragment
108                                ipv6[11], ipv6[10], ipv6[9], ipv6[8], // 3rd fragment
109                                ipv6[15], ipv6[14], ipv6[13], ipv6[12], // 4th fragment
110                            ];
111                            s.serialize_field("ipv6", &format!("{}", std::net::Ipv6Addr::from(bytes)))?;
112                        }
113                        if let Nullable::Some(port) = port {
114                            s.serialize_field("port", port)?;
115                        }
116                        s.end()
117                    }
118                    Relay::SingleHostName(port, hostname) => {
119                        let mut s = serializer.serialize_struct("Relay::SingleHostName", 3)?;
120                        s.serialize_field("type", "hostname")?;
121                        s.serialize_field("hostname", hostname)?;
122                        if let Nullable::Some(port) = port {
123                            s.serialize_field("port", port)?;
124                        }
125                        s.end()
126                    }
127                    Relay::MultiHostName(hostname) => {
128                        let mut s = serializer.serialize_struct("Relay::MultiHostName", 2)?;
129                        s.serialize_field("type", "hostname")?;
130                        s.serialize_field("hostname", hostname)?;
131                        s.end()
132                    }
133                }
134            }
135        }
136
137        let mut s = serializer.serialize_struct("PoolParams", 9)?;
138        s.serialize_field("id", &hex::encode(self.id))?;
139        s.serialize_field("vrfVerificationKeyHash", &hex::encode(self.vrf))?;
140        s.serialize_field("pledge", &as_lovelace_map(self.pledge))?;
141        s.serialize_field("cost", &as_lovelace_map(self.cost))?;
142        s.serialize_field("margin", &as_string_ratio(&self.margin))?;
143        s.serialize_field("rewardAccount", &as_bech32_addr(&self.reward_account).map_err(serde::ser::Error::custom)?)?;
144        s.serialize_field("owners", &self.owners.iter().map(hex::encode).collect::<Vec<String>>())?;
145        s.serialize_field("relays", &self.relays.iter().map(WrapRelay).collect::<Vec<WrapRelay<'_>>>())?;
146        if let Nullable::Some(metadata) = &self.metadata {
147            s.serialize_field("metadata", metadata)?;
148        }
149        s.end()
150    }
151}
152
153#[cfg(any(test, feature = "test-utils"))]
154pub use tests::*;
155
156#[cfg(any(test, feature = "test-utils"))]
157mod tests {
158    use proptest::{prelude::*, prop_compose};
159
160    use super::*;
161    use crate::{
162        Bytes, Hash, Nullable, RationalNumber, Relay, any_hash28, any_hash32, prop_cbor_roundtrip,
163        size::{CREDENTIAL, KEY},
164    };
165
166    prop_cbor_roundtrip!(PoolParams, any_pool_params());
167
168    fn any_nullable_port() -> impl Strategy<Value = Nullable<u32>> {
169        prop_oneof![Just(Nullable::Undefined), Just(Nullable::Null), any::<u32>().prop_map(Nullable::Some),]
170    }
171
172    fn any_nullable_ipv4() -> impl Strategy<Value = Nullable<Bytes>> {
173        prop_oneof![
174            Just(Nullable::Undefined),
175            Just(Nullable::Null),
176            any::<[u8; 4]>().prop_map(|a| Nullable::Some(Vec::from(a).into())),
177        ]
178    }
179
180    fn any_nullable_ipv6() -> impl Strategy<Value = Nullable<Bytes>> {
181        prop_oneof![
182            Just(Nullable::Undefined),
183            Just(Nullable::Null),
184            any::<[u8; 16]>().prop_map(|a| Nullable::Some(Vec::from(a).into())),
185        ]
186    }
187
188    prop_compose! {
189        fn single_host_addr()(
190            port in any_nullable_port(),
191            ipv4 in any_nullable_ipv4(),
192            ipv6 in any_nullable_ipv6()
193        ) -> Relay {
194            Relay::SingleHostAddr(port, ipv4, ipv6)
195        }
196    }
197
198    prop_compose! {
199        fn single_host_name()(
200            port in any_nullable_port(),
201            dnsname in any::<String>(),
202        ) -> Relay {
203            Relay::SingleHostName(port, dnsname)
204        }
205    }
206
207    prop_compose! {
208        fn multi_host_name()(
209            dnsname in any::<String>(),
210        ) -> Relay {
211            Relay::MultiHostName(dnsname)
212        }
213    }
214
215    fn any_relay() -> BoxedStrategy<Relay> {
216        prop_oneof![single_host_addr(), single_host_name(), multi_host_name(),].boxed()
217    }
218
219    prop_compose! {
220        pub fn any_pool_params()(
221            id in any_hash28(),
222            vrf in any_hash32(),
223            pledge in any::<u64>(),
224            cost in any::<u64>(),
225            margin in 0..100u64,
226            reward_account in any::<[u8; CREDENTIAL]>(),
227            owners in any::<Vec<[u8; KEY]>>(),
228            relays in proptest::collection::vec(any_relay(), 0..10),
229        ) -> PoolParams {
230            PoolParams {
231                id,
232                vrf,
233                pledge,
234                cost,
235                margin: RationalNumber { numerator: margin, denominator: 100 },
236                reward_account: [&[0xF0], &reward_account[..]].concat().into(),
237                owners: owners.into_iter().map(|h| h.into()).collect::<Vec<Hash<KEY>>>().into(),
238                relays,
239                metadata: Nullable::Null,
240            }
241        }
242    }
243}