feat(primitives): Improve ergonomics for Byron primitives (#47)
* feat(primitives): Improve ergonomics for Byron primitives * fix(ci): Fix tests
This commit is contained in:
parent
82c581f3a3
commit
c3662e199d
9 changed files with 293 additions and 45 deletions
|
|
@ -19,3 +19,4 @@ minicbor-derive = "0.8.0"
|
|||
hex = "0.4.3"
|
||||
log = "0.4.14"
|
||||
pallas-crypto = { version = "0.5.0-alpha.0", path = "../pallas-crypto" }
|
||||
base58 = "0.2.0"
|
||||
|
|
|
|||
62
pallas-primitives/src/byron/address.rs
Normal file
62
pallas-primitives/src/byron/address.rs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
use crate::Error;
|
||||
|
||||
use super::Address;
|
||||
use base58::ToBase58;
|
||||
use minicbor::to_vec;
|
||||
|
||||
impl Address {
|
||||
pub fn to_addr_string(&self) -> Result<String, Error> {
|
||||
let cbor = to_vec(self)?;
|
||||
Ok(cbor.to_base58())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ops::Deref;
|
||||
|
||||
use crate::byron::Block;
|
||||
use crate::Fragment;
|
||||
|
||||
const KNOWN_ADDRESSES: &[&str] = &[
|
||||
"DdzFFzCqrht8QHTQXbWy2qoyPaqTN8BjyfKygGmpy9dtot1tvkBfCaVTnR22XCaaDVn3M1U6aiMShoCLzw6VWSwzQKhhJrM3YjYp3wyy",
|
||||
"DdzFFzCqrhsjUjMRukzoFx8sToHzCt4iidB17STXk9adAoVMNus5SvAjS1cXfPKbuNbPUZ5xQG25sMK85n9GdMkqo2ytqBnKWC68s8P3",
|
||||
"Ae2tdPwUPEZFBnsqpm2RkDQfwJseUrBKrTECCDom4bAqNsxTNwbMPCZtbyJ",
|
||||
"DdzFFzCqrhsvNcX48aHrYdcQhKhAwEdLebH7xPskKtQnZucNQmXXmEMJEU2NDqipuDTZKFec5UMCtm4vYoUgjixxULJexAzHGa3Bmctk",
|
||||
"Ae2tdPwUPEZGEC75fV3vktzbwxhkD71JHxSYVgiNCgKB7Yo1rWamWVJDFsV",
|
||||
"DdzFFzCqrht1K1sR9fQWEdcxhTxgqKQqwPHucez1McJy14uqbxu1UKnnq12EdHr9NxYs8RqKnSvswegh8wLvfC4fw6arB3nyqC5Wy4Ky",
|
||||
"DdzFFzCqrhtC8HauYJMa59DzoAeTLnpDcdst1hFWmjkTdg5Xu55ougiBpAmwuo2Coe2DfAj26m52aF4e2yk8v5GQc4umZxsXUT2CuTB2",
|
||||
"DdzFFzCqrht4Zsigv43q9LRHNsgu6TWdASuGe6tCYuv2B9H2wTggkvyuwHMb5WALqWDDiNQEHYq7BvnFJ65UDzKi6ThdZusVYmYLpJg9",
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn known_address_matches() {
|
||||
// TODO: expand this test to include more test blocks
|
||||
let block_idx = 1;
|
||||
let block_str = include_str!("test_data/test2.block");
|
||||
|
||||
let block_bytes = hex::decode(block_str).expect(&format!("bad block file {}", block_idx));
|
||||
let block = Block::decode_fragment(&block_bytes[..])
|
||||
.expect(&format!("error decoding cbor for file {}", block_idx));
|
||||
|
||||
let block = match block {
|
||||
Block::MainBlock(x) => x,
|
||||
Block::EbBlock(_) => panic!(),
|
||||
};
|
||||
|
||||
// don't want to pass if we don't have tx in the block
|
||||
assert!(block.body.tx_payload.len() > 0);
|
||||
|
||||
for tx in block.body.tx_payload.iter() {
|
||||
for output in tx.deref().transaction.outputs.iter() {
|
||||
let addr_str = output.address.to_addr_string().unwrap();
|
||||
|
||||
assert!(
|
||||
KNOWN_ADDRESSES.contains(&addr_str.as_str()),
|
||||
"address {} not in known list",
|
||||
addr_str
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +1,34 @@
|
|||
use super::{Block, BlockHead, EbbHead};
|
||||
use super::{Block, BlockHead, EbbHead, Tx};
|
||||
use pallas_crypto::hash::{Hash, Hasher};
|
||||
|
||||
pub fn hash_boundary_block_header(header: &EbbHead) -> Hash<32> {
|
||||
// hash expects to have a prefix for the type of block
|
||||
Hasher::<256>::hash_cbor(&(0, header))
|
||||
}
|
||||
|
||||
pub fn hash_main_block_header(header: &BlockHead) -> Hash<32> {
|
||||
// hash expects to have a prefix for the type of block
|
||||
Hasher::<256>::hash_cbor(&(1, header))
|
||||
}
|
||||
|
||||
pub fn hash_block_header(block: &Block) -> Hash<32> {
|
||||
match block {
|
||||
Block::EbBlock(x) => hash_boundary_block_header(&x.header),
|
||||
Block::MainBlock(x) => hash_main_block_header(&x.header),
|
||||
impl EbbHead {
|
||||
pub fn to_hash(&self) -> Hash<32> {
|
||||
// hash expects to have a prefix for the type of block
|
||||
Hasher::<256>::hash_cbor(&(0, self))
|
||||
}
|
||||
}
|
||||
|
||||
//pub fn hash_transaction(data: &TransactionBody) -> Hash<32> {
|
||||
// Hasher::<256>::hash_cbor(data)
|
||||
//}
|
||||
impl BlockHead {
|
||||
pub fn to_hash(&self) -> Hash<32> {
|
||||
// hash expects to have a prefix for the type of block
|
||||
Hasher::<256>::hash_cbor(&(1, self))
|
||||
}
|
||||
}
|
||||
|
||||
impl Block {
|
||||
pub fn to_hash(&self) -> Hash<32> {
|
||||
match self {
|
||||
Block::EbBlock(x) => x.header.to_hash(),
|
||||
Block::MainBlock(x) => x.header.to_hash(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Tx {
|
||||
pub fn to_hash(&self) -> Hash<32> {
|
||||
Hasher::<256>::hash_cbor(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
@ -40,7 +48,7 @@ mod tests {
|
|||
let block_model = Block::decode_fragment(&block_bytes[..])
|
||||
.expect(&format!("error decoding cbor for file {}", block_idx));
|
||||
|
||||
let computed_hash = super::hash_block_header(&block_model);
|
||||
let computed_hash = block_model.to_hash();
|
||||
|
||||
assert_eq!(hex::encode(computed_hash), KNOWN_HASH)
|
||||
}
|
||||
|
|
|
|||
76
pallas-primitives/src/byron/fees.rs
Normal file
76
pallas-primitives/src/byron/fees.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
use crate::Error;
|
||||
|
||||
use super::TxPayload;
|
||||
use minicbor::to_vec;
|
||||
|
||||
pub struct PolicyParams {
|
||||
constant: u64,
|
||||
size_coeficient: u64,
|
||||
}
|
||||
|
||||
impl Default for PolicyParams {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
constant: 155_381_000_000_000u64,
|
||||
size_coeficient: 43_946_000_000u64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_linear_fee_policy(tx_size: u64, params: &PolicyParams) -> u64 {
|
||||
println!("tx size: {}", tx_size);
|
||||
let nanos = params.constant + (tx_size * params.size_coeficient);
|
||||
|
||||
let loves = nanos / 1_000_000_000;
|
||||
|
||||
let rem = match nanos % 1_000_000_000 {
|
||||
0 => 0u64,
|
||||
_ => 1u64,
|
||||
};
|
||||
|
||||
loves + rem
|
||||
}
|
||||
|
||||
impl TxPayload {
|
||||
pub fn compute_fee(&self, params: &PolicyParams) -> Result<u64, Error> {
|
||||
let tx_size = to_vec(&self)?.len();
|
||||
let fee = compute_linear_fee_policy(tx_size as u64, params);
|
||||
|
||||
Ok(fee)
|
||||
}
|
||||
|
||||
pub fn compute_fee_with_defaults(&self) -> Result<u64, Error> {
|
||||
self.compute_fee(&PolicyParams::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::byron::Block;
|
||||
use crate::Fragment;
|
||||
|
||||
#[test]
|
||||
fn known_fee_matches() {
|
||||
// TODO: expand this test to include more test blocks
|
||||
let block_idx = 1;
|
||||
let block_str = include_str!("test_data/test4.block");
|
||||
|
||||
let block_bytes = hex::decode(block_str).expect(&format!("bad block file {}", block_idx));
|
||||
let block = Block::decode_fragment(&block_bytes[..])
|
||||
.expect(&format!("error decoding cbor for file {}", block_idx));
|
||||
|
||||
let block = match block {
|
||||
Block::MainBlock(x) => x,
|
||||
Block::EbBlock(_) => panic!(),
|
||||
};
|
||||
|
||||
// don't want to pass if we don't have tx in the block
|
||||
assert!(block.body.tx_payload.len() > 0);
|
||||
|
||||
for tx in block.body.tx_payload.iter().take(1) {
|
||||
println!("{}", tx.transaction.to_hash());
|
||||
let fee = tx.compute_fee_with_defaults().unwrap();
|
||||
assert_eq!(fee, 171070);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
//! Ledger primitives and cbor codec for the Byron era
|
||||
|
||||
mod address;
|
||||
mod crypto;
|
||||
mod fees;
|
||||
mod model;
|
||||
mod time;
|
||||
|
||||
pub use model::*;
|
||||
|
||||
pub mod crypto;
|
||||
|
|
|
|||
|
|
@ -173,13 +173,39 @@ impl minicbor::Encode for AddrAttrProperty {
|
|||
|
||||
pub type AddrAttr = OrderPreservingProperties<AddrAttrProperty>;
|
||||
|
||||
#[derive(Debug, Encode, Decode)]
|
||||
pub struct AddressPayload {
|
||||
#[n(0)]
|
||||
pub root: AddressId,
|
||||
|
||||
#[n(1)]
|
||||
pub attributes: AddrAttr,
|
||||
|
||||
#[n(2)]
|
||||
pub addrtype: AddrType,
|
||||
}
|
||||
|
||||
// address = [ #6.24(bytes .cbor ([addressid, addrattr, addrtype])), u64 ]
|
||||
pub type Address = (CborWrap<(AddressId, AddrAttr, AddrType)>, u64);
|
||||
#[derive(Debug, Encode, Decode)]
|
||||
pub struct Address {
|
||||
#[n(0)]
|
||||
pub payload: CborWrap<AddressPayload>,
|
||||
|
||||
#[n(1)]
|
||||
pub crc: u64,
|
||||
}
|
||||
|
||||
// Transactions
|
||||
|
||||
// txout = [address, u64]
|
||||
pub type TxOut = (Address, u64);
|
||||
#[derive(Debug, Encode, Decode)]
|
||||
pub struct TxOut {
|
||||
#[n(0)]
|
||||
pub address: Address,
|
||||
|
||||
#[n(1)]
|
||||
pub amount: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TxIn {
|
||||
|
|
@ -228,21 +254,34 @@ impl minicbor::Encode for TxIn {
|
|||
}
|
||||
|
||||
// tx = [[+ txin], [+ txout], attributes]
|
||||
pub type Tx = (Vec<TxIn>, Vec<TxOut>, Attributes);
|
||||
#[derive(Debug, Encode, Decode)]
|
||||
pub struct Tx {
|
||||
#[n(0)]
|
||||
pub inputs: MaybeIndefArray<TxIn>,
|
||||
|
||||
#[n(1)]
|
||||
pub outputs: MaybeIndefArray<TxOut>,
|
||||
|
||||
#[n(2)]
|
||||
pub attributes: Attributes,
|
||||
}
|
||||
|
||||
// txproof = [u32, hash, hash]
|
||||
pub type TxProof = (u32, ByronHash, ByronHash);
|
||||
|
||||
pub type ValidatorScript = (u16, ByteVec);
|
||||
pub type RedeemerScript = (u16, ByteVec);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Twit {
|
||||
// [0, #6.24(bytes .cbor ([pubkey, signature]))]
|
||||
Variant0(CborWrap<(PubKey, Signature)>),
|
||||
PkWitness(CborWrap<(PubKey, Signature)>),
|
||||
|
||||
// [1, #6.24(bytes .cbor ([[u16, bytes], [u16, bytes]]))]
|
||||
Variant1(CborWrap<((u16, ByteVec), (u16, ByteVec))>),
|
||||
ScriptWitness(CborWrap<(ValidatorScript, RedeemerScript)>),
|
||||
|
||||
// [2, #6.24(bytes .cbor ([pubkey, signature]))]
|
||||
Variant2(CborWrap<(PubKey, Signature)>),
|
||||
RedeemWitness(CborWrap<(PubKey, Signature)>),
|
||||
|
||||
// [u8 .gt 2, encoded-cbor]
|
||||
Other(u8, ByteVec),
|
||||
|
|
@ -255,9 +294,9 @@ impl<'b> minicbor::Decode<'b> for Twit {
|
|||
let variant = d.u8()?;
|
||||
|
||||
match variant {
|
||||
0 => Ok(Twit::Variant0(d.decode()?)),
|
||||
1 => Ok(Twit::Variant1(d.decode()?)),
|
||||
2 => Ok(Twit::Variant2(d.decode()?)),
|
||||
0 => Ok(Twit::PkWitness(d.decode()?)),
|
||||
1 => Ok(Twit::ScriptWitness(d.decode()?)),
|
||||
2 => Ok(Twit::RedeemWitness(d.decode()?)),
|
||||
x => Ok(Twit::Other(x, d.decode()?)),
|
||||
}
|
||||
}
|
||||
|
|
@ -269,21 +308,21 @@ impl minicbor::Encode for Twit {
|
|||
e: &mut minicbor::Encoder<W>,
|
||||
) -> Result<(), minicbor::encode::Error<W::Error>> {
|
||||
match self {
|
||||
Twit::Variant0(x) => {
|
||||
Twit::PkWitness(x) => {
|
||||
e.array(2)?;
|
||||
e.u8(0)?;
|
||||
e.encode(x)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Twit::Variant1(x) => {
|
||||
Twit::ScriptWitness(x) => {
|
||||
e.array(2)?;
|
||||
e.u8(1)?;
|
||||
e.encode(x)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Twit::Variant2(x) => {
|
||||
Twit::RedeemWitness(x) => {
|
||||
e.array(2)?;
|
||||
e.u8(2)?;
|
||||
e.encode(x)?;
|
||||
|
|
@ -327,7 +366,7 @@ pub type VssDec = ByteVec;
|
|||
// cddl note:
|
||||
// This is encoded using the
|
||||
// 'Binary' instance for Scrape.Proof
|
||||
pub type VssProof = (ByteVec, ByteVec, ByteVec, Vec<ByteVec>);
|
||||
pub type VssProof = (ByteVec, ByteVec, ByteVec, MaybeIndefArray<ByteVec>);
|
||||
|
||||
//ssccomm = [pubkey, [{vsspubkey => vssenc},vssproof], signature]
|
||||
pub type SscComm = (
|
||||
|
|
@ -781,7 +820,14 @@ pub struct BlockHead {
|
|||
}
|
||||
|
||||
// [tx, [* twit]]
|
||||
pub type TxPayload = (Tx, Vec<Twit>);
|
||||
#[derive(Debug, Encode, Decode)]
|
||||
pub struct TxPayload {
|
||||
#[n(0)]
|
||||
pub transaction: Tx,
|
||||
|
||||
#[n(1)]
|
||||
pub witness: MaybeIndefArray<Twit>,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Debug)]
|
||||
pub struct BlockBody {
|
||||
|
|
@ -920,12 +966,10 @@ mod tests {
|
|||
let block = Block::decode_fragment(&bytes[..])
|
||||
.expect(&format!("error decoding cbor for file {}", idx));
|
||||
|
||||
let _bytes2 =
|
||||
let bytes2 =
|
||||
to_vec(block).expect(&format!("error encoding block cbor for file {}", idx));
|
||||
|
||||
// HACK: we ommit the ismorphic requirement until we find the
|
||||
// offending difference
|
||||
// assert_eq!(bytes, bytes2);
|
||||
assert_eq!(hex::encode(bytes), hex::encode(bytes2));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -940,8 +984,6 @@ mod tests {
|
|||
let block = BlockHead::decode_fragment(&bytes[..])
|
||||
.expect(&format!("error decoding cbor for file {}", idx));
|
||||
|
||||
println!("{:?}", block);
|
||||
|
||||
let bytes2 =
|
||||
to_vec(block).expect(&format!("error encoding header cbor for file {}", idx));
|
||||
|
||||
|
|
|
|||
1
pallas-primitives/src/byron/test_data/test4.block
Normal file
1
pallas-primitives/src/byron/test_data/test4.block
Normal file
|
|
@ -0,0 +1 @@
|
|||
820183851a2d964a095820b5bdd15fbfbe7f618d2b7db0b20632658466b1b17654e9a8b865ec0e9fdccd4e8483015820c2c44f1f28599c08c6c1da0e375dc995d2b686c445b19110fa96ca68dfb2880e5820314b3c77650d6eba459864966dbcda18c438ee21bdd7e6b3272bf1e6b241d75d83025820d36a2619a672494604e11bb447cbcf5231e9f2ba25c2169177edc941bd50ad6c5820d36a2619a672494604e11bb447cbcf5231e9f2ba25c2169177edc941bd50ad6c5820afc0da64183bf2664f3d4eec7238d524ba607faeeab24fc100eb861dba69971b58204e66280cd94d591072349bec0a3090a53aa945562efb6d08d56e53654b0e4098848201195457584026566e86fc6b9b177c8480e275b2b112b573f6d073f9deea53b8d99c4ed976b335b2b3842f0e380001f090bc923caa9691ed9115e286da9421e2745c7acc87f18119a89f8202828400584026566e86fc6b9b177c8480e275b2b112b573f6d073f9deea53b8d99c4ed976b335b2b3842f0e380001f090bc923caa9691ed9115e286da9421e2745c7acc87f15840f14f712dc600d793052d4842d50cefa4e65884ea6cf83707079eb8ce302efc85dae922d5eb3838d2b91784f04824d26767bfb65bd36a36e74fec46d09d98858d58408ab43e904b06e799c1817c5ced4f3a7bbe15cdbf422dea9d2d5dc2c6105ce2f4d4c71e5d4779f6c44b770a133636109949e1f7786acb5a732bcdea0470fea4065840cfd641e91f908471af31762e7124147e8d3b27036d436f24d785de7330ebe33c03dae5ec27eb2944a82545fbcc30016737ba7696fb5e3fcaf8963ea12dbb87098483000000826a63617264616e6f2d736c01a058204ba92aa320c60acc9ad7b9a64f2eda55c4d2ec28e604faf186708b4f0c4e8edf849f82839f8200d81858248258200ca95f3bb516e3fa36b3c5ce18316a3d197b4faf2e36635baecae47e8a714b8d00ff9f8282d818584283581caca526063940ef762c899b92f20134264a43c5ab36d46cc6d36540d1a101581e581c2729cbfd641133bd0633ff422b246fa0d95cc2bef293d39adf3fd22b001aa25f5bd41b0000021a9f3ab0c28282d818584283581ce0751a974c40abdac7e71ee5b09d12b0591b1f4ecab73062ac8f96caa101581e581cca3e553c9c63c531002ff143535ea35088673bf86d25026baf12db3e001afdf29bac1a02625a00ffa0818200d81858858258408b9397dce473d3f296ad24e24e48795a495ad2f1602896e913fdc7e55e55e21f6fa53491a197e86428c86fadc0253ddec8d88bee623d474632603633643b26eb5840555e2f86fd803d1ed5bb3a3bc7f08dd82896744c4a0d99ce7b61f696ef632b12f6d8bd4787fcfe27de2bb7b1127fb1646d8d2f26755b4186f605210709016709ff8302a0d90102809fff82809fff81a0
|
||||
47
pallas-primitives/src/byron/time.rs
Normal file
47
pallas-primitives/src/byron/time.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
use super::{EbbHead, SlotId};
|
||||
|
||||
// TODO: is it safe to hardcode these values?
|
||||
const WELLKNOWN_SLOT_LENGTH: u64 = 20; // 20 secs
|
||||
const WELLKNOWN_EPOCH_LENGTH: u64 = 5 * 24 * 60 * 60; // 5 days
|
||||
|
||||
fn epoch_slot_to_absolute(epoch: u64, sub_epoch_slot: u64) -> u64 {
|
||||
((epoch * WELLKNOWN_EPOCH_LENGTH) / WELLKNOWN_SLOT_LENGTH) + sub_epoch_slot
|
||||
}
|
||||
|
||||
impl SlotId {
|
||||
pub fn to_abs_slot(&self) -> u64 {
|
||||
epoch_slot_to_absolute(self.epoch, self.slot)
|
||||
}
|
||||
}
|
||||
|
||||
impl EbbHead {
|
||||
pub fn to_abs_slot(&self) -> u64 {
|
||||
epoch_slot_to_absolute(self.consensus_data.epoch_id, 0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::byron::Block;
|
||||
use crate::Fragment;
|
||||
|
||||
#[test]
|
||||
fn knwon_slot_matches() {
|
||||
// TODO: expand this test to include more test blocks
|
||||
let block_idx = 1;
|
||||
let block_str = include_str!("test_data/test1.block");
|
||||
|
||||
let block_bytes = hex::decode(block_str).expect(&format!("bad block file {}", block_idx));
|
||||
let block = Block::decode_fragment(&block_bytes[..])
|
||||
.expect(&format!("error decoding cbor for file {}", block_idx));
|
||||
|
||||
let block = match block {
|
||||
Block::MainBlock(x) => x,
|
||||
Block::EbBlock(_) => panic!(),
|
||||
};
|
||||
|
||||
let computed_slot = block.header.consensus_data.0.to_abs_slot();
|
||||
|
||||
assert_eq!(computed_slot, 4492794);
|
||||
}
|
||||
}
|
||||
|
|
@ -153,9 +153,10 @@ where
|
|||
MaybeIndefArray::Def(x) => {
|
||||
e.encode(x)?;
|
||||
}
|
||||
MaybeIndefArray::Indef(x) if x.is_empty() => {
|
||||
e.encode(x)?;
|
||||
}
|
||||
// TODO: this seemed necesary on alonzo, but breaks on byron. We need to double check.
|
||||
//MaybeIndefArray::Indef(x) if x.is_empty() => {
|
||||
// e.encode(x)?;
|
||||
//}
|
||||
MaybeIndefArray::Indef(x) => {
|
||||
e.begin_array()?;
|
||||
|
||||
|
|
@ -223,7 +224,7 @@ where
|
|||
|
||||
/// Wraps a struct so that it is encoded/decoded as a cbor bytes
|
||||
#[derive(Debug)]
|
||||
pub struct CborWrap<T>(T);
|
||||
pub struct CborWrap<T>(pub T);
|
||||
|
||||
impl<'b, T> minicbor::Decode<'b> for CborWrap<T>
|
||||
where
|
||||
|
|
@ -257,6 +258,14 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for CborWrap<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TagWrap<I, const T: u64>(I);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue