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

Add support for SignerListSet tx #5

Merged
merged 11 commits into from
Oct 5, 2022
92 changes: 83 additions & 9 deletions codec/src/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,59 @@ use xrpl_codec_utils::Field;

use crate::{
traits::{BinarySerialize, CodecField},
types::{AccountIdType, AmountType, BlobType, UInt16Type, UInt32Type, ACCOUNT_ID_TYPE_CODE},
types::{
AccountIdType, AmountType, BlobType, STArrayType, SignerEntryType, UInt16Type, UInt32Type,
ACCOUNT_ID_TYPE_CODE,
},
Vec,
};

// TODO: auto-generate the structs from definitions.json

#[derive(Field, Debug)]
#[derive(Field, Debug, Clone)]
pub struct Account(pub AccountIdType);

#[derive(Field, Debug)]
#[derive(Field, Debug, Clone)]
pub struct Destination(pub AccountIdType);

#[derive(Field, Debug)]
#[derive(Field, Debug, Clone)]
pub struct TransactionType(pub UInt16Type);
impl From<TransactionTypeCode> for TransactionType {
fn from(v: TransactionTypeCode) -> Self {
TransactionType(UInt16Type(v.code()))
}
}

#[derive(Field, Debug)]
#[derive(Field, Debug, Clone)]
pub struct Fee(pub AmountType);

#[derive(Field, Debug)]
#[derive(Field, Debug, Clone)]
pub struct Flags(pub UInt32Type);

#[derive(Field, Debug)]
#[derive(Field, Debug, Clone)]
pub struct Sequence(pub UInt32Type);

#[derive(Field, Debug, Default)]
pub struct SigningPubKey(pub BlobType);

#[derive(Field, Debug)]
#[derive(Field, Debug, Clone)]
pub struct Amount(pub AmountType);

#[derive(Field, Debug, Default)]
pub struct TxnSignature(pub BlobType);

#[derive(Field, Debug, Clone)]
pub struct SignerQuorum(pub UInt32Type);

#[derive(Field, Debug, Clone)]
pub struct SignerWeight(pub UInt16Type);

#[derive(Field, Debug, Clone)]
pub struct SignerEntry(pub SignerEntryType);

#[derive(Field, Debug, Clone)]
pub struct SignerEntries(pub STArrayType<SignerEntry>);

impl<T: CodecField> BinarySerialize for T {
fn binary_serialize_to(&self, buf: &mut Vec<u8>, for_signing: bool) {
if !self.is_serialized() {
Expand Down Expand Up @@ -155,7 +170,6 @@ impl TransactionTypeCode {
mod tests {
use super::*;
use crate::types::BlobType;
use std::prelude::*;

#[test]
fn serialize_signing_pub_key() {
Expand All @@ -180,4 +194,64 @@ mod tests {
let buf = Destination(AccountIdType(dest)).binary_serialize(true);
println!("{:?}", hex::encode(&buf));
}
#[test]
fn serialize_signer_entry() {
let signer_entry = SignerEntry(SignerEntryType(
Account(AccountIdType([1_u8; 20])),
SignerWeight(UInt16Type(1_u16)),
));
let buf = signer_entry.binary_serialize(true);
// construct the expected buffer manually
let signer_entry_field_id: u8 = 0xEB; // Typecode(14) | FieldCode(11) = 0xEB
let account_field_id: u8 = 0x81; // Typecode(8) | Fieldcode(1) = 0x81(129)
let signer_weight_field_id: u8 = 0x13; // Typecode(1) | Fieldcode(3) = 0x13(19)
let account_field_vl: u8 = 0x14; // https://xrpl.org/serialization.html#accountid-fields
let st_object_end: u8 = 0xe1; // https://xrpl.org/serialization.html#object-fields

let mut expected_buf = Vec::<u8>::default();
expected_buf.extend_from_slice(&[signer_entry_field_id]);
expected_buf.extend_from_slice(&[signer_weight_field_id]); // SignerWeight comes first in the canonical order
expected_buf.extend_from_slice(&1_u16.to_be_bytes());
expected_buf.extend_from_slice(&[account_field_id]);
expected_buf.extend_from_slice(&[account_field_vl]);
expected_buf.extend_from_slice(&[1_u8; 20]);
expected_buf.extend_from_slice(&[st_object_end]);

assert_eq!(buf, expected_buf);
}
#[test]
fn serialize_signer_entries() {
let mut signer_entries_vec = Vec::<SignerEntry>::default();
for i in 1..=2 {
signer_entries_vec.push(SignerEntry(SignerEntryType(
Account(AccountIdType([i as u8; 20])),
SignerWeight(UInt16Type(i as u16)),
)));
}
let signer_entries = SignerEntries(STArrayType(signer_entries_vec));

let buf = signer_entries.binary_serialize(true);
let signer_entries_field_id: u8 = 0xF4; // Typecode(15) | FieldCode(4) = 0xF4
let signer_entry_field_id: u8 = 0xEB; // Typecode(14) | FieldCode(11) = 0xEB
let account_field_id: u8 = 0x81; // Typecode(8) | Fieldcode(1) = 0x81(129)
let signer_weight_field_id: u8 = 0x13; // Typecode(1) | Fieldcode(3) = 0x13(19)
let account_field_vl: u8 = 0x14; // https://xrpl.org/serialization.html#accountid-fields
let st_object_end: u8 = 0xe1; // https://xrpl.org/serialization.html#object-fields

// let's construct the expected buffer -> https://xrpl.org/serialization.html#array-fields
let mut expected_buf = Vec::<u8>::default();
expected_buf.extend_from_slice(&[signer_entries_field_id]);
for i in 1..=2 {
expected_buf.extend_from_slice(&[signer_entry_field_id]); // SignerEntry field ID
expected_buf.extend_from_slice(&[signer_weight_field_id]); // SignerWeight comes first in the canonical order
expected_buf.extend_from_slice(&(i as u16).to_be_bytes());
expected_buf.extend_from_slice(&[account_field_id]);
expected_buf.extend_from_slice(&[account_field_vl]);
expected_buf.extend_from_slice(&[i as u8; 20]);
expected_buf.extend_from_slice(&[st_object_end]);
}
expected_buf.extend_from_slice(&[0xf1]); // STArray end 0xf1

assert_eq!(buf, expected_buf);
}
}
158 changes: 155 additions & 3 deletions codec/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use xrpl_codec_utils::Transaction;
use crate::{
field::*,
traits::{BinarySerialize, CodecField, CodecToFields},
types::{AccountIdType, AmountType, BlobType, UInt32Type},
types::{
AccountIdType, AmountType, BlobType, STArrayType, SignerEntryType, UInt16Type, UInt32Type,
},
Vec,
};

Expand Down Expand Up @@ -120,12 +122,81 @@ impl Payment {
}
}

/// An XRP SignerListSet tx
#[derive(Transaction, Debug)]
pub struct SignerListSet {
/// common tx fields
account: Account,
transaction_type: TransactionType,
fee: Fee,
flags: Flags,
/// SignerListSet
signer_quorum: SignerQuorum,
signer_entries: SignerEntries,
/// set when signing
signing_pub_key: SigningPubKey,
txn_signature: TxnSignature,
}

impl SignerListSet {
/// Create a new XRP SignerListSet transaction
///
/// Applies the global signing flags (see https://xrpl.org/transaction-common-fields.html#global-flags)
///
/// - `account` the sender's address
/// - `fee` the max XRP fee in drops
/// - `signer_quorum` signer quorum required
/// - `signer_entries` signer entries which can participate in multi signing
/// - `signing_pub_key` public key of `account`
pub fn new(
account: [u8; 20],
fee: u64,
signer_quorum: u32,
signer_entries: Vec<([u8; 20], u16)>,
signing_pub_key: Option<[u8; 33]>,
) -> Self {
Self {
account: Account(AccountIdType(account)),
transaction_type: TransactionTypeCode::SignerListSet.into(),
fee: Fee(AmountType(fee)),
// https://xrpl.org/transaction-common-fields.html#global-flags
flags: Flags(UInt32Type(0x8000_0000_u32)),
signer_quorum: SignerQuorum(UInt32Type(signer_quorum)),
signer_entries: SignerEntries(STArrayType(
signer_entries
.into_iter()
.map(|(account, weight)| {
SignerEntry(SignerEntryType(
Account(AccountIdType(account)),
SignerWeight(UInt16Type(weight)),
))
})
.collect(),
)),
signing_pub_key: signing_pub_key
.map(|pk| SigningPubKey(BlobType(pk.to_vec())))
.unwrap_or_default(),
txn_signature: Default::default(),
}
}
/// Attach a signature to the transaction
pub fn attach_signature(&mut self, signature: [u8; 65]) {
self.txn_signature = TxnSignature(BlobType(signature.to_vec()));
}
}

#[cfg(test)]
mod tests {
use super::{CodecToFields, Payment};
use super::*;
use crate::{
field::{Account, SignerEntry, SignerWeight},
types::{AccountIdType, SignerEntryType, UInt16Type},
};
use alloc::vec::Vec;

#[test]
fn canonical_field_order() {
#[allow(non_snake_case)]
fn test_Payment_canonical_field_order() {
let account = [1_u8; 20];
let destination = [2_u8; 20];
let amount = 5_000_000_u64; // 5 XRP
Expand Down Expand Up @@ -154,4 +225,85 @@ mod tests {
}
}
}
#[test]
#[allow(non_snake_case)]
fn test_SignerListSet_canonical_field_order() {
let account = [1_u8; 20];
let fee = 1_000; // 1000 drops
let signing_pub_key = [1_u8; 33];
let signer_quorum = 3_u32;
let mut signer_entries = Vec::<([u8; 20], u16)>::default();
signer_entries.push(([1_u8; 20], 1_u16));
signer_entries.push(([2_u8; 20], 2_u16));

let signer_list_set = SignerListSet::new(
account,
fee,
signer_quorum,
signer_entries,
Some(signing_pub_key),
);

for chunk in signer_list_set.to_canonical_fields().chunks(2) {
match chunk {
&[f1, f2] => {
assert!(
f1.type_code() < f2.type_code()
|| f1.type_code() == f2.type_code()
&& f1.field_code() <= f2.field_code()
);
}
_ => continue,
}
}
}
#[test]
#[allow(non_snake_case)]
fn test_SignerListSet_serialize() {
let account = [1_u8; 20];
let fee = 1_000; // 1000 drops
let signing_pub_key = [1_u8; 33];
let signer_quorum = 3_u32;
let mut signer_entries = Vec::<([u8; 20], u16)>::default();
signer_entries.push(([1_u8; 20], 1_u16));
signer_entries.push(([2_u8; 20], 2_u16));

let signer_list_set = SignerListSet::new(
account,
fee,
signer_quorum,
signer_entries.clone(),
Some(signing_pub_key),
);

let buf = signer_list_set.binary_serialize(true);
// Construct the expected buf manually
let mut expected_buf = Vec::<u8>::default();
expected_buf.extend_from_slice(
&TransactionType(UInt16Type(TransactionTypeCode::SignerListSet.code()))
.binary_serialize(true),
); // TransactionType
expected_buf.extend_from_slice(&Flags(UInt32Type(0x8000_0000_u32)).binary_serialize(true)); // Flags
expected_buf
.extend_from_slice(&SignerQuorum(UInt32Type(signer_quorum)).binary_serialize(true)); // SignerQuorum
expected_buf.extend_from_slice(&Fee(AmountType(fee)).binary_serialize(true)); // Fee
expected_buf.extend_from_slice(
&SigningPubKey(BlobType(signing_pub_key.to_vec())).binary_serialize(true),
); // SigningPubKey
expected_buf.extend_from_slice(&TxnSignature::default().binary_serialize(true)); // TxnSignature
expected_buf.extend_from_slice(&Account(AccountIdType(account)).binary_serialize(true)); // Account
let signer_entries = signer_entries
.into_iter()
.map(|(account, weight)| {
SignerEntry(SignerEntryType(
Account(AccountIdType(account)),
SignerWeight(UInt16Type(weight)),
))
})
.collect();
expected_buf
.extend_from_slice(&SignerEntries(STArrayType(signer_entries)).binary_serialize(true)); // SignerEntries

assert_eq!(buf, expected_buf);
}
}
Loading