1use 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], ipv6[7], ipv6[6], ipv6[5], ipv6[4], ipv6[11], ipv6[10], ipv6[9], ipv6[8], ipv6[15], ipv6[14], ipv6[13], ipv6[12], ];
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}