Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add serde on missing structs #259

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 51 additions & 2 deletions src/bitcoin/fee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ use bitcoin::util::amount::Denomination;
use bitcoin::util::psbt::PartiallySignedTransaction;
use bitcoin::Amount;

use crate::bitcoin::{transaction, Bitcoin, Strategy};
use crate::blockchain::{Fee, FeePriority, FeeStrategy, FeeStrategyError};
use crate::consensus::{self, CanonicalBytes};

use crate::bitcoin::{transaction, Bitcoin, Strategy};

use std::str::FromStr;

use serde::ser::{Serialize, SerializeStruct, Serializer};
use serde::{de, Deserialize, Deserializer};

/// An amount of Bitcoin (internally in satoshis) representing the number of satoshis per virtual
/// byte a transaction must use for its fee. A [`FeeStrategy`] can use one of more of this type
/// depending of its complexity (fixed, range, etc).
Expand Down Expand Up @@ -67,6 +69,27 @@ impl SatPerVByte {
}
}

impl Serialize for SatPerVByte {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(format!("{}", self).as_ref())
}
}

impl<'de> Deserialize<'de> for SatPerVByte {
fn deserialize<D>(deserializer: D) -> Result<SatPerVByte, D::Error>
where
D: Deserializer<'de>,
{
Ok(
SatPerVByte::from_str(&String::deserialize(deserializer)?)
.map_err(de::Error::custom)?,
)
}
}

impl CanonicalBytes for SatPerVByte {
fn as_canonical_bytes(&self) -> Vec<u8> {
bitcoin::consensus::encode::serialize(&self.0.as_sat())
Expand Down Expand Up @@ -195,6 +218,11 @@ impl Fee for PartiallySignedTransaction {
mod tests {
use super::*;

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct SerdeTest {
fee: SatPerVByte,
}

#[test]
fn parse_sats_per_vbyte() {
for s in [
Expand All @@ -221,4 +249,25 @@ mod tests {
let fee_rate = SatPerVByte::from_sat(100);
assert_eq!(format!("{}", fee_rate), "100 satoshi/vByte".to_string());
}

#[test]
fn serialize_fee_rate_in_yaml() {
let fee_rate = SerdeTest {
fee: SatPerVByte::from_sat(10),
};
let s = serde_yaml::to_string(&fee_rate).expect("Encode fee rate in yaml");
assert_eq!("---\nfee: 10 satoshi/vByte\n", s);
}

#[test]
fn deserialize_fee_rate_in_yaml() {
let s = "---\nfee: 10 satoshi/vByte\n";
let fee_rate = serde_yaml::from_str(&s).expect("Decode fee rate from yaml");
assert_eq!(
SerdeTest {
fee: SatPerVByte::from_sat(10)
},
fee_rate
);
}
}
2 changes: 1 addition & 1 deletion src/bitcoin/timelock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ impl FromStr for CSVTimelock {
}

/// An `OP_CSV` value (32-bits integer) to use in transactions and scripts.
#[derive(PartialEq, Eq, PartialOrd, Clone, Debug, Copy, Display)]
#[derive(PartialEq, Eq, PartialOrd, Clone, Debug, Copy, Display, Serialize, Deserialize)]
#[display("{0} blocks")]
pub struct CSVTimelock(u32);

Expand Down
2 changes: 1 addition & 1 deletion src/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::consensus::{self, deserialize, serialize, CanonicalBytes, Decodable,
use crate::crypto::Signatures;
use crate::transaction::{Buyable, Cancelable, Fundable, Lockable, Punishable, Refundable};

#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Display)]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Display, Serialize, Deserialize)]
#[display(Debug)]
pub enum Blockchain {
Bitcoin,
Expand Down
142 changes: 95 additions & 47 deletions src/negotiation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@

use bitcoin::secp256k1::PublicKey;
use inet2_addr::InetSocketAddr;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use serde::ser::{Serialize, SerializeStruct, Serializer};
use serde::{de, Deserialize, Deserializer};
use std::fmt::Display;
use std::str::FromStr;
use thiserror::Error;
use tiny_keccak::{Hasher, Keccak};
Expand Down Expand Up @@ -118,7 +120,12 @@ impl<'de> Deserialize<'de> for OfferId {
/// perspective. The daemon start when the maker is ready to finalize his offer, transforming the
/// offer into a [`PublicOffer`] which contains the data needed to a taker to connect to the
/// maker's daemon.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
///
/// ## Serde implementation
/// Amount types may have multiple serialization representation, e.g. btc and sat for bitcoin or
/// xmr and pico for monero. Using [`Display`] and [`FromStr`] unifies the interface to
/// de/serialize generic amounts.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
TheCharlatan marked this conversation as resolved.
Show resolved Hide resolved
pub struct Offer<Amt, Bmt, Ti, F> {
/// Type of offer and network to use.
pub network: Network,
Expand All @@ -127,8 +134,14 @@ pub struct Offer<Amt, Bmt, Ti, F> {
/// The chosen accordant blockchain.
pub accordant_blockchain: Blockchain,
/// Amount of arbitrating assets to exchanged.
#[serde(with = "string")]
#[serde(bound(serialize = "Amt: Display"))]
#[serde(bound(deserialize = "Amt: FromStr, Amt::Err: Display"))]
pub arbitrating_amount: Amt,
/// Amount of accordant assets to exchanged.
#[serde(with = "string")]
#[serde(bound(serialize = "Bmt: Display"))]
#[serde(bound(deserialize = "Bmt: FromStr, Bmt::Err: Display"))]
pub accordant_amount: Bmt,
/// The cancel timelock parameter of the arbitrating blockchain.
pub cancel_timelock: Ti,
Expand All @@ -140,12 +153,38 @@ pub struct Offer<Amt, Bmt, Ti, F> {
pub maker_role: SwapRole,
}

impl<Amt, Bmt, Ti, F> fmt::Display for Offer<Amt, Bmt, Ti, F>
mod string {
use std::fmt::Display;
use std::str::FromStr;

use serde::{de, Deserialize, Deserializer, Serializer};

pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: Display,
S: Serializer,
{
serializer.collect_str(value)
}

pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: FromStr,
T::Err: Display,
D: Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse()
.map_err(de::Error::custom)
}
}

impl<Amt, Bmt, Ti, F> Display for Offer<Amt, Bmt, Ti, F>
where
Amt: fmt::Display,
Bmt: fmt::Display,
Ti: fmt::Display,
F: fmt::Display,
Amt: Display,
Bmt: Display,
Ti: Display,
F: Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "Network: {}", self.network)?;
Expand Down Expand Up @@ -486,11 +525,15 @@ impl_strict_encoding!(PublicOfferId);
/// A public offer is shared across [`TradeRole::Maker`]'s prefered network to signal is willing of
/// trading some assets at some conditions. The assets and condition are defined in the [`Offer`],
/// maker peer connection information are contained in the public offer.
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct PublicOffer<Amt, Bmt, Ti, F> {
/// The public offer version.
pub version: Version,
/// The content of the offer.
#[serde(bound(serialize = "Amt: Display, Bmt: Display, Ti: Serialize, F: Serialize"))]
#[serde(bound(
deserialize = "Amt: FromStr, Amt::Err: Display, Bmt: FromStr, Bmt::Err: Display, Ti: Deserialize<'de>, F: Deserialize<'de>"
))]
pub offer: Offer<Amt, Bmt, Ti, F>,
/// Node public key, used both as an ID and encryption key for per-session ECDH.
pub node_id: PublicKey,
Expand Down Expand Up @@ -542,7 +585,7 @@ impl<Amt, Bmt, Ti, F> PublicOffer<Amt, Bmt, Ti, F> {
}
}

impl<Amt, Bmt, Ti, F> std::fmt::Display for PublicOffer<Amt, Bmt, Ti, F>
impl<Amt, Bmt, Ti, F> Display for PublicOffer<Amt, Bmt, Ti, F>
where
Self: Encodable,
{
Expand All @@ -553,7 +596,7 @@ where
}
}

impl<Amt, Bmt, Ti, F> std::str::FromStr for PublicOffer<Amt, Bmt, Ti, F>
impl<Amt, Bmt, Ti, F> FromStr for PublicOffer<Amt, Bmt, Ti, F>
where
Amt: CanonicalBytes,
Bmt: CanonicalBytes,
Expand All @@ -572,41 +615,6 @@ where
}
}

// TODO: implement properly without encoding in base58 first
impl<Amt, Bmt, Ti, F> Serialize for PublicOffer<Amt, Bmt, Ti, F>
where
Amt: CanonicalBytes,
Bmt: CanonicalBytes,
Ti: CanonicalBytes,
F: CanonicalBytes,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.to_string().as_ref())
}
}

// TODO: implement properly without decoding from base58
impl<'de, Amt, Bmt, Ti, F> Deserialize<'de> for PublicOffer<Amt, Bmt, Ti, F>
where
Amt: CanonicalBytes,
Bmt: CanonicalBytes,
Ti: CanonicalBytes,
F: CanonicalBytes,
{
fn deserialize<D>(deserializer: D) -> Result<PublicOffer<Amt, Bmt, Ti, F>, D::Error>
where
D: Deserializer<'de>,
{
Ok(
PublicOffer::from_str(&deserializer.deserialize_string(OfferString)?)
.map_err(de::Error::custom)?,
)
}
}

impl<Amt, Bmt, Ti, F> Encodable for PublicOffer<Amt, Bmt, Ti, F>
where
Amt: CanonicalBytes,
Expand Down Expand Up @@ -738,21 +746,61 @@ mod tests {
assert_eq!(&format!("{}", pub_offer), S);
}

#[test]
fn serialize_offer_in_yaml() {
let offer: Offer<bitcoin::Amount, monero::Amount, CSVTimelock, SatPerVByte> = Offer {
network: Network::Testnet,
arbitrating_blockchain: Blockchain::Bitcoin,
accordant_blockchain: Blockchain::Monero,
arbitrating_amount: bitcoin::Amount::from_sat(5),
accordant_amount: monero::Amount::from_pico(6),
cancel_timelock: CSVTimelock::new(7),
punish_timelock: CSVTimelock::new(8),
fee_strategy: FeeStrategy::Fixed(SatPerVByte::from_sat(9)),
maker_role: SwapRole::Bob,
};
let s = serde_yaml::to_string(&offer).expect("Encode public offer in yaml");
assert_eq!(
"---\nnetwork: Testnet\narbitrating_blockchain: Bitcoin\naccordant_blockchain: Monero\narbitrating_amount: 0.00000005 BTC\naccordant_amount: 0.000000000006 XMR\ncancel_timelock: 7\npunish_timelock: 8\nfee_strategy:\n Fixed: 9 satoshi/vByte\nmaker_role: Bob\n",
s
);
}

#[test]
fn deserialize_offer_from_yaml() {
let s = "---\nnetwork: Testnet\narbitrating_blockchain: Bitcoin\naccordant_blockchain: Monero\narbitrating_amount: 0.00000005 BTC\naccordant_amount: 0.000000000006 XMR\ncancel_timelock: 7\npunish_timelock: 8\nfee_strategy:\n Fixed: 9 satoshi/vByte\nmaker_role: Bob\n";
let offer = serde_yaml::from_str(&s).expect("Decode offer from yaml");
assert_eq!(
Offer {
network: Network::Testnet,
arbitrating_blockchain: Blockchain::Bitcoin,
accordant_blockchain: Blockchain::Monero,
arbitrating_amount: bitcoin::Amount::from_sat(5),
accordant_amount: monero::Amount::from_pico(6),
cancel_timelock: CSVTimelock::new(7),
punish_timelock: CSVTimelock::new(8),
fee_strategy: FeeStrategy::Fixed(SatPerVByte::from_sat(9)),
maker_role: SwapRole::Bob,
},
offer
);
}

#[test]
fn serialize_public_offer_in_yaml() {
let public_offer =
PublicOffer::<bitcoin::Amount, monero::Amount, CSVTimelock, SatPerVByte>::from_str("Offer:Cke4ftrP5A71W723UjzEWsNR4gmBqNCsR11111uMFubBevJ2E5fp6ZR11111TBALTh113GTvtvqfD1111114A4TTfifktDH7QZD71vpdfo6EVo2ds7KviHz7vYbLZDkgsMNb11111111111111111111111111111111111111111AfZ113XRBum3er3R")
.expect("Valid public offer");
let s = serde_yaml::to_string(&public_offer).expect("Encode public offer in yaml");
assert_eq!(
"---\n\"Offer:Cke4ftrP5A71W723UjzEWsNR4gmBqNCsR11111uMFubBevJ2E5fp6ZR11111TBALTh113GTvtvqfD1111114A4TTfifktDH7QZD71vpdfo6EVo2ds7KviHz7vYbLZDkgsMNb11111111111111111111111111111111111111111AfZ113XRBum3er3R\"\n",
"---\nversion: 1\noffer:\n network: Local\n arbitrating_blockchain: Bitcoin\n accordant_blockchain: Monero\n arbitrating_amount: 0.00001350 BTC\n accordant_amount: 1000000.001000000000 XMR\n cancel_timelock: 4\n punish_timelock: 6\n fee_strategy:\n Fixed: 1 satoshi/vByte\n maker_role: Bob\nnode_id: 02e77b779cdc2c713823f7a19147a67e4209c74d77e2cb5045bce0584a6be064d4\npeer_address:\n address:\n IPv4: 127.0.0.1\n port: 9735\n",
s
);
}

#[test]
fn deserialize_public_offer_from_yaml() {
let s = "---\nOffer:Cke4ftrP5A71W723UjzEWsNR4gmBqNCsR11111uMFubBevJ2E5fp6ZR11111TBALTh113GTvtvqfD1111114A4TTfifktDH7QZD71vpdfo6EVo2ds7KviHz7vYbLZDkgsMNb11111111111111111111111111111111111111111AfZ113XRBum3er3R\n";
let s = "---\nversion: 1\noffer:\n network: Local\n arbitrating_blockchain: Bitcoin\n accordant_blockchain: Monero\n arbitrating_amount: 0.00001350 BTC\n accordant_amount: 1000000.001000000000 XMR\n cancel_timelock: 4\n punish_timelock: 6\n fee_strategy:\n Fixed: 1 satoshi/vByte\n maker_role: Bob\nnode_id: 02e77b779cdc2c713823f7a19147a67e4209c74d77e2cb5045bce0584a6be064d4\npeer_address:\n address:\n IPv4: 127.0.0.1\n port: 9735\n";
let public_offer = serde_yaml::from_str(&s).expect("Decode public offer from yaml");
assert_eq!(
PublicOffer::<bitcoin::Amount, monero::Amount, CSVTimelock, SatPerVByte>::from_str("Offer:Cke4ftrP5A71W723UjzEWsNR4gmBqNCsR11111uMFubBevJ2E5fp6ZR11111TBALTh113GTvtvqfD1111114A4TTfifktDH7QZD71vpdfo6EVo2ds7KviHz7vYbLZDkgsMNb11111111111111111111111111111111111111111AfZ113XRBum3er3R")
Expand Down
10 changes: 5 additions & 5 deletions src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ struct ValidatedCoreTransactions<Px, Ti, Pk> {
punish_lock: DataPunishableLock<Ti, Pk>,
}

#[derive(Debug, Clone, Copy, Hash)]
#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)]
pub struct CoreArbitratingTransactions<Px> {
/// Partial transaction raw type representing the lock.
pub lock: Px,
Expand All @@ -57,27 +57,27 @@ impl<Px> CoreArbitratingTransactions<Px> {
}
}

#[derive(Debug, Clone, Copy, Hash)]
#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)]
pub struct ArbitratingParameters<Amt, Ti, F> {
pub arbitrating_amount: Amt,
pub cancel_timelock: Ti,
pub punish_timelock: Ti,
pub fee_strategy: FeeStrategy<F>,
}

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)]
pub struct TxSignatures<Sig> {
pub sig: Sig,
pub adapted_sig: Sig,
}

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)]
pub struct FullySignedPunish<Px, Sig> {
pub punish: Px,
pub punish_sig: Sig,
}

#[derive(Debug, Clone, Hash)]
#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
pub struct Parameters<Pk, Qk, Rk, Sk, Addr, Ti, F, Pr> {
pub buy: Pk,
pub cancel: Pk,
Expand Down
2 changes: 1 addition & 1 deletion src/protocol/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,7 @@ impl<Px, EncSig> Strategy for BuyProcedureSignature<Px, EncSig> {
/// that they have aborted the swap with an `OPTIONAL` message body to provide the reason.
///
/// [`SwapRole`]: crate::role::SwapRole
#[derive(Clone, Debug, Hash, Display)]
#[derive(Clone, Debug, Hash, Display, Serialize, Deserialize)]
#[display(Debug)]
pub struct Abort {
/// The swap identifier related to this message.
Expand Down
2 changes: 1 addition & 1 deletion src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ pub trait Transaction<Px, Out, Amt> {
}

/// Defines the transaction Farcaster IDs for serialization and network communication.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display, Serialize, Deserialize)]
pub enum TxLabel {
/// Represents the first transaction created outside of the system by an external wallet to
/// fund the swap on the arbitrating blockchain.
Expand Down