use std::sync::Arc;
use chrono::{DateTime, Utc};
use lazy_static::lazy_static;
use nacl::sign::PUBLIC_KEY_LENGTH;
use tlb_ton::{
BagOfCells, Cell, Context, Error, MsgAddress, UnixTimestamp,
action::{OutAction, SendMsgAction},
r#as::{Data, List, NoArgs, hashmap::HashmapE},
bits::{de::BitReaderExt, ser::BitWriterExt},
de::{CellDeserialize, CellParser, CellParserError},
ser::{CellBuilder, CellBuilderError, CellSerialize},
};
use super::WalletVersion;
lazy_static! {
static ref WALLET_V5R1_CODE_CELL: Arc<Cell> = {
BagOfCells::parse_base64(include_str!("./wallet_v5r1.code"))
.unwrap()
.into_single_root()
.expect("code BoC must be single root")
};
}
pub struct V5R1;
impl WalletVersion for V5R1 {
type Data = WalletV5R1Data;
type SignBody = WalletV5RSignBody;
type ExternalMsgBody = WalletV5R1MsgBody;
const DEFAULT_WALLET_ID: u32 = 0x7FFFFF11;
#[inline]
fn code() -> Arc<Cell> {
WALLET_V5R1_CODE_CELL.clone()
}
#[inline]
fn init_data(wallet_id: u32, pubkey: [u8; nacl::sign::PUBLIC_KEY_LENGTH]) -> Self::Data {
WalletV5R1Data {
is_signature_allowed: true,
seqno: 0,
wallet_id,
pubkey,
extensions: HashmapE::Empty,
}
}
#[inline]
fn create_sign_body(
wallet_id: u32,
valid_until: DateTime<Utc>,
msg_seqno: u32,
msgs: impl IntoIterator<Item = SendMsgAction>,
) -> Self::SignBody {
WalletV5RSignBody {
wallet_id,
valid_until,
msg_seqno,
inner: WalletV5R1InnerRequest {
out_actions: msgs.into_iter().map(OutAction::SendMsg).collect(),
extended: [].into(),
},
}
}
#[inline]
fn wrap_signed_external(body: Self::SignBody, signature: [u8; 64]) -> Self::ExternalMsgBody {
WalletV5R1MsgBody::ExternalSigned(WalletV5R1SignedRequest { body, signature })
}
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WalletV5R1Data {
pub is_signature_allowed: bool,
pub seqno: u32,
pub wallet_id: u32,
pub pubkey: [u8; PUBLIC_KEY_LENGTH],
#[cfg_attr(feature = "arbitrary", arbitrary(default))]
pub extensions: HashmapE<bool>,
}
impl CellSerialize for WalletV5R1Data {
#[inline]
fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> {
builder
.pack(self.is_signature_allowed)?
.pack(self.seqno)?
.pack(self.wallet_id)?
.pack(self.pubkey)?
.store_as_with::<_, &HashmapE<Data<NoArgs<_>>, NoArgs<_>>>(
&self.extensions,
(256, (), ()),
)?;
Ok(())
}
}
impl<'de> CellDeserialize<'de> for WalletV5R1Data {
#[inline]
fn parse(parser: &mut CellParser<'de>) -> Result<Self, CellParserError<'de>> {
Ok(Self {
is_signature_allowed: parser.unpack()?,
seqno: parser.unpack()?,
wallet_id: parser.unpack()?,
pubkey: parser.unpack()?,
extensions: parser.parse_as_with::<_, HashmapE<Data<NoArgs<_>>, NoArgs<_>>>((
256,
(),
(),
))?,
})
}
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WalletV5R1InnerRequest {
pub out_actions: Vec<OutAction>,
pub extended: Vec<ExtendedAction>,
}
impl CellSerialize for WalletV5R1InnerRequest {
fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> {
builder
.store_as::<_, Option<&List>>(
Some(&self.out_actions).filter(|actions| !actions.is_empty()),
)?
.store_as::<_, Option<&List>>(Some(&self.extended).filter(|other| !other.is_empty()))?;
Ok(())
}
}
impl<'de> CellDeserialize<'de> for WalletV5R1InnerRequest {
fn parse(parser: &mut CellParser<'de>) -> Result<Self, CellParserError<'de>> {
Ok(Self {
out_actions: parser
.parse_as::<_, Option<List>>()
.context("out_actions")?
.unwrap_or_default(),
extended: parser
.parse_as::<_, Option<List>>()
.context("extended")?
.unwrap_or_default(),
})
}
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExtendedAction {
AddExtension(MsgAddress),
DeleteExtension(MsgAddress),
SetSignatureAuthAllowed(bool),
}
impl ExtendedAction {
const ADD_EXTENSION_PREFIX: u8 = 0x02;
const DELETE_EXTENSION_PREFIX: u8 = 0x03;
const SET_SIGNATURE_AUTH_ALLOWED_PREFIX: u8 = 0x04;
}
impl CellSerialize for ExtendedAction {
fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> {
match self {
Self::AddExtension(addr) => builder.pack(Self::ADD_EXTENSION_PREFIX)?.pack(addr)?,
Self::DeleteExtension(addr) => {
builder.pack(Self::DELETE_EXTENSION_PREFIX)?.pack(addr)?
}
Self::SetSignatureAuthAllowed(allowed) => builder
.pack(Self::SET_SIGNATURE_AUTH_ALLOWED_PREFIX)?
.pack(allowed)?,
};
Ok(())
}
}
impl<'de> CellDeserialize<'de> for ExtendedAction {
fn parse(parser: &mut CellParser<'de>) -> Result<Self, CellParserError<'de>> {
Ok(match parser.unpack()? {
Self::ADD_EXTENSION_PREFIX => Self::AddExtension(parser.unpack()?),
Self::DELETE_EXTENSION_PREFIX => Self::DeleteExtension(parser.unpack()?),
Self::SET_SIGNATURE_AUTH_ALLOWED_PREFIX => {
Self::SetSignatureAuthAllowed(parser.unpack()?)
}
prefix => return Err(Error::custom(format!("unknown prefix: {prefix:#0x}"))),
})
}
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WalletV5RSignBody {
pub wallet_id: u32,
#[cfg_attr(
feature = "arbitrary",
arbitrary(with = UnixTimestamp::arbitrary)
)]
pub valid_until: DateTime<Utc>,
pub msg_seqno: u32,
pub inner: WalletV5R1InnerRequest,
}
impl CellSerialize for WalletV5RSignBody {
fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> {
builder
.pack(self.wallet_id)?
.pack_as::<_, UnixTimestamp>(self.valid_until)?
.pack(self.msg_seqno)?
.store(&self.inner)?;
Ok(())
}
}
impl<'de> CellDeserialize<'de> for WalletV5RSignBody {
fn parse(parser: &mut CellParser<'de>) -> Result<Self, CellParserError<'de>> {
Ok(Self {
wallet_id: parser.unpack()?,
valid_until: parser.unpack_as::<_, UnixTimestamp>()?,
msg_seqno: parser.unpack()?,
inner: parser.parse()?,
})
}
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WalletV5R1SignedRequest {
pub body: WalletV5RSignBody,
pub signature: [u8; 64],
}
impl CellSerialize for WalletV5R1SignedRequest {
fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> {
builder.store(&self.body)?.pack(self.signature)?;
Ok(())
}
}
impl<'de> CellDeserialize<'de> for WalletV5R1SignedRequest {
fn parse(parser: &mut CellParser<'de>) -> Result<Self, CellParserError<'de>> {
Ok(Self {
body: parser.parse()?,
signature: parser.unpack()?,
})
}
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WalletV5R1MsgBody {
InternalSigned(WalletV5R1SignedRequest),
InternalExtension(InternalExtensionWalletV5R1MsgBody),
ExternalSigned(WalletV5R1SignedRequest),
}
impl WalletV5R1MsgBody {
const INTERNAL_SIGNED_PREFIX: u32 = 0x73696e74;
const INTERNAL_EXTENSION_PREFIX: u32 = 0x6578746e;
const EXTERNAL_SIGNED_PREFIX: u32 = 0x7369676e;
}
impl CellSerialize for WalletV5R1MsgBody {
fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> {
match self {
Self::InternalSigned(msg) => builder.pack(Self::INTERNAL_SIGNED_PREFIX)?.store(msg)?,
Self::InternalExtension(msg) => {
builder.pack(Self::INTERNAL_EXTENSION_PREFIX)?.store(msg)?
}
Self::ExternalSigned(msg) => builder.pack(Self::EXTERNAL_SIGNED_PREFIX)?.store(msg)?,
};
Ok(())
}
}
impl<'de> CellDeserialize<'de> for WalletV5R1MsgBody {
fn parse(parser: &mut CellParser<'de>) -> Result<Self, CellParserError<'de>> {
Ok(match parser.unpack()? {
Self::INTERNAL_SIGNED_PREFIX => {
Self::InternalSigned(parser.parse().context("internal_signed")?)
}
Self::INTERNAL_EXTENSION_PREFIX => {
Self::InternalExtension(parser.parse().context("internal_extension")?)
}
Self::EXTERNAL_SIGNED_PREFIX => {
Self::ExternalSigned(parser.parse().context("external_signed")?)
}
prefix => return Err(Error::custom(format!("unknown prefix: {prefix:#0x}"))),
})
}
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InternalExtensionWalletV5R1MsgBody {
query_id: u64,
inner: WalletV5R1InnerRequest,
}
impl CellSerialize for InternalExtensionWalletV5R1MsgBody {
fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> {
builder.pack(self.query_id)?.store(&self.inner)?;
Ok(())
}
}
impl<'de> CellDeserialize<'de> for InternalExtensionWalletV5R1MsgBody {
fn parse(parser: &mut CellParser<'de>) -> Result<Self, CellParserError<'de>> {
Ok(Self {
query_id: parser.unpack()?,
inner: parser.parse()?,
})
}
}
#[cfg(test)]
mod tests {
use tlb_ton::{
BagOfCellsArgs, BoC,
bits::{de::unpack_fully, ser::pack_with},
};
use super::*;
#[test]
fn check_code() {
let packed = pack_with(
BoC::from_root(WALLET_V5R1_CODE_CELL.clone()),
BagOfCellsArgs {
has_idx: false,
has_crc32c: true,
},
)
.unwrap();
let unpacked: BoC = unpack_fully(&packed).unwrap();
let got: Cell = unpacked.single_root().unwrap().parse_fully().unwrap();
assert_eq!(&got, WALLET_V5R1_CODE_CELL.as_ref());
}
}