Skip to content

Commit

Permalink
Add support for SignerListSet tx (#5)
Browse files Browse the repository at this point in the history
* Add SignerListSet struct

* Add SignerEntryType

* Add STArrayType

* Add SignerQuorum, SignerWeight, SignerEntry, SignerEntries fields

* Update SignerListSet transaction struct

* implement SignerListSet tx, add tests.
  • Loading branch information
surangap authored Oct 5, 2022
1 parent 68037da commit d340830
Show file tree
Hide file tree
Showing 4 changed files with 487 additions and 25 deletions.
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

0 comments on commit d340830

Please sign in to comment.