use std::str::FromStr;
use bullet_exchange_interface::address::Address;
use bullet_exchange_interface::memo::{Memo, MemoV1, decode, encode};
use bullet_exchange_interface::message::TransferEndpoint;
use bullet_exchange_interface::types::BalanceBucket;
use crate::errors::{SDKError, SDKResult};
use crate::sub_account::MAX_SUB_ACCOUNT_INDEX;
pub fn encode_fast_bridge_memo(
to_address: &str,
sub_account_index: Option<u32>,
balance: BalanceBucket,
) -> SDKResult<Vec<u8>> {
let to_address = Address::from_str(to_address)
.map_err(|e| SDKError::InvalidAddress(format!("{to_address}: {e}")))?;
let sub_account_index = match sub_account_index {
Some(index) if index > MAX_SUB_ACCOUNT_INDEX => {
return Err(SDKError::InvalidSubAccountIndex(index));
}
Some(index) => Some(index as u8), None => None,
};
let bytes = encode(&Memo::V1(MemoV1 {
to_address,
to: TransferEndpoint {
sub_account_index,
balance,
},
}));
decode(&bytes)
.map_err(|_| SDKError::InvalidMemo(format!("{to_address} is not a usable destination")))?;
Ok(bytes)
}
#[cfg(test)]
mod tests {
use super::*;
use bullet_exchange_interface::memo::{MEMO_MAX_LEN, decode};
use bullet_exchange_interface::types::MarketId;
const ADDRESS: &str = "DdKTBsowJD2b8UsevrwF73zNb3C2VjHuAG32VJzFcYc5";
#[test]
fn round_trips_through_the_real_decoder() {
let bytes = encode_fast_bridge_memo(ADDRESS, Some(3), BalanceBucket::Iso(MarketId(7)))
.expect("encodes");
let Memo::V1(decoded) = decode(&bytes).expect("decodes");
assert_eq!(decoded.to_address, Address::from_str(ADDRESS).unwrap());
assert_eq!(decoded.to.sub_account_index, Some(3));
assert_eq!(decoded.to.balance, BalanceBucket::Iso(MarketId(7)));
}
#[test]
fn memo_fits_well_within_the_limit() {
for balance in [
BalanceBucket::Bank,
BalanceBucket::Cross,
BalanceBucket::Spot,
BalanceBucket::Iso(MarketId(u16::MAX)),
] {
for index in [None, Some(MAX_SUB_ACCOUNT_INDEX)] {
let bytes = encode_fast_bridge_memo(ADDRESS, index, balance).expect("encodes");
assert!(
bytes.len() <= 38 && bytes.len() < MEMO_MAX_LEN,
"v1 memo grew to {} bytes",
bytes.len()
);
}
}
}
#[test]
fn rejects_an_unparseable_address() {
assert!(matches!(
encode_fast_bridge_memo("not-an-address", None, BalanceBucket::Bank),
Err(SDKError::InvalidAddress(_))
));
}
#[test]
fn rejects_a_destination_the_shared_decoder_would_refuse() {
const ZERO: &str = "11111111111111111111111111111111";
assert!(
Address::from_str(ZERO).is_ok(),
"the address parses; only the memo rule rejects it"
);
assert!(matches!(
encode_fast_bridge_memo(ZERO, None, BalanceBucket::Bank),
Err(SDKError::InvalidMemo(_))
));
}
#[test]
fn rejects_a_sub_account_index_that_cannot_exist() {
assert!(matches!(
encode_fast_bridge_memo(ADDRESS, Some(32), BalanceBucket::Cross),
Err(SDKError::InvalidSubAccountIndex(32))
));
}
}