cosmwasm: Add core accounting package
The accounting package implements the reusable, chain agnostic part of tokenbridge accounting. Part of #1880.
This commit is contained in:
parent
4188373a69
commit
c5925f1467
|
@ -2,6 +2,21 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "accounting"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"hex",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.17.0"
|
||||
|
@ -33,6 +48,9 @@ name = "anyhow"
|
|||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
|
|
|
@ -6,6 +6,7 @@ members = [
|
|||
"contracts/token-bridge",
|
||||
"contracts/shutdown-token-bridge",
|
||||
"contracts/mock-bridge-integration",
|
||||
"packages/accounting",
|
||||
"packages/wormhole-bindings",
|
||||
]
|
||||
|
||||
|
@ -21,8 +22,8 @@ incremental = false
|
|||
overflow-checks = true
|
||||
|
||||
[patch.crates-io]
|
||||
accounting = { path = "packages/accounting" }
|
||||
cw20-wrapped-2 = { path = "contracts/cw20-wrapped" }
|
||||
token-bridge-terra-2 = { path = "contracts/token-bridge" }
|
||||
wormhole-bindings = { path = "packages/wormhole-bindings" }
|
||||
wormhole-bridge-terra-2 = { path = "contracts/wormhole" }
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
[package]
|
||||
name = "accounting"
|
||||
version = "0.1.0"
|
||||
authors = ["Wormhole Project Contributors"]
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
# for more explicit tests, cargo test --features=backtraces
|
||||
backtraces = ["cosmwasm-std/backtraces"]
|
||||
# use library feature to disable all instantiate/execute/query exports
|
||||
library = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
base64 = "0.13"
|
||||
cosmwasm-schema = "1"
|
||||
cosmwasm-std = "1"
|
||||
cw-storage-plus = "0.13.2"
|
||||
hex = "0.4.3"
|
||||
schemars = "0.8.8"
|
||||
serde = { version = "1.0.137", default-features = false }
|
||||
thiserror = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { version = "1", features = ["backtrace"] }
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,5 @@
|
|||
mod contract;
|
||||
pub mod msg;
|
||||
pub mod state;
|
||||
|
||||
pub use contract::*;
|
|
@ -0,0 +1,10 @@
|
|||
use cosmwasm_schema::cw_serde;
|
||||
|
||||
use crate::state::{Account, Modification, Transfer};
|
||||
|
||||
#[cw_serde]
|
||||
pub struct Instantiate {
|
||||
pub accounts: Vec<Account>,
|
||||
pub transfers: Vec<Transfer>,
|
||||
pub modifications: Vec<Modification>,
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
use std::fmt;
|
||||
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::Uint256;
|
||||
use cw_storage_plus::Map;
|
||||
|
||||
pub mod account;
|
||||
mod addr;
|
||||
pub mod transfer;
|
||||
|
||||
pub use account::Account;
|
||||
pub use addr::TokenAddress;
|
||||
pub use transfer::Transfer;
|
||||
|
||||
pub const ACCOUNTS: Map<account::Key, account::Balance> = Map::new("accounting/accounts");
|
||||
|
||||
pub const TRANSFERS: Map<transfer::Key, transfer::Data> = Map::new("accounting/transfers");
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(Eq, PartialOrd, Ord)]
|
||||
pub enum Kind {
|
||||
Add,
|
||||
Sub,
|
||||
}
|
||||
|
||||
impl fmt::Display for Kind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Kind::Add => f.write_str("ADD"),
|
||||
Kind::Sub => f.write_str("SUB"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(Eq, PartialOrd, Ord)]
|
||||
pub struct Modification {
|
||||
// The sequence number of this modification. Each modification must be
|
||||
// uniquely identifiable just by its sequnce number.
|
||||
pub sequence: u64,
|
||||
// The chain id of the account to be modified.
|
||||
pub chain_id: u16,
|
||||
// The chain id of the native chain for the token.
|
||||
pub token_chain: u16,
|
||||
// The address of the token on its native chain.
|
||||
pub token_address: TokenAddress,
|
||||
// The kind of modification to be made.
|
||||
pub kind: Kind,
|
||||
// The amount to be modified.
|
||||
pub amount: Uint256,
|
||||
// A human-readable reason for the modification.
|
||||
pub reason: String,
|
||||
}
|
||||
|
||||
pub const MODIFICATIONS: Map<u64, Modification> = Map::new("accounting/modifications");
|
|
@ -0,0 +1,324 @@
|
|||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::{StdResult, Uint256};
|
||||
use cw_storage_plus::{Key as CwKey, KeyDeserialize, PrimaryKey};
|
||||
|
||||
use crate::state::TokenAddress;
|
||||
|
||||
#[cw_serde]
|
||||
pub struct Account {
|
||||
pub key: Key,
|
||||
|
||||
// The current balance of the account.
|
||||
pub balance: Balance,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub fn lock_or_burn(&mut self, amt: Uint256) -> StdResult<()> {
|
||||
if self.key.chain_id == self.key.token_chain {
|
||||
self.balance.0 = self.balance.checked_add(amt)?;
|
||||
} else {
|
||||
self.balance.0 = self.balance.checked_sub(amt)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unlock_or_mint(&mut self, amt: Uint256) -> StdResult<()> {
|
||||
if self.key.chain_id == self.key.token_chain {
|
||||
self.balance.0 = self.balance.checked_sub(amt)?;
|
||||
} else {
|
||||
self.balance.0 = self.balance.checked_add(amt)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(Eq, PartialOrd, Ord)]
|
||||
pub struct Key {
|
||||
// The chain id of the chain to which this account belongs.
|
||||
chain_id: u16,
|
||||
// The chain id of the native chain for the token associated with this account.
|
||||
token_chain: u16,
|
||||
// The address of the token associated with this account on its native chain.
|
||||
token_address: TokenAddress,
|
||||
}
|
||||
|
||||
impl Key {
|
||||
pub fn new(chain_id: u16, token_chain: u16, token_address: TokenAddress) -> Self {
|
||||
Self {
|
||||
chain_id,
|
||||
token_chain,
|
||||
token_address,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chain_id(&self) -> u16 {
|
||||
self.chain_id
|
||||
}
|
||||
|
||||
pub fn token_chain(&self) -> u16 {
|
||||
self.token_chain
|
||||
}
|
||||
|
||||
pub fn token_address(&self) -> &TokenAddress {
|
||||
&self.token_address
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyDeserialize for Key {
|
||||
type Output = Self;
|
||||
|
||||
fn from_vec(v: Vec<u8>) -> StdResult<Self::Output> {
|
||||
<(u16, u16, TokenAddress)>::from_vec(v).map(|(chain_id, token_chain, token_address)| Key {
|
||||
chain_id,
|
||||
token_chain,
|
||||
token_address,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PrimaryKey<'a> for Key {
|
||||
type Prefix = (u16, u16);
|
||||
type SubPrefix = u16;
|
||||
type Suffix = TokenAddress;
|
||||
type SuperSuffix = (u16, TokenAddress);
|
||||
|
||||
fn key(&self) -> Vec<CwKey> {
|
||||
self.chain_id
|
||||
.key()
|
||||
.into_iter()
|
||||
.chain(self.token_chain.key())
|
||||
.chain(self.token_address.key())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct Balance(Uint256);
|
||||
|
||||
impl Balance {
|
||||
pub const fn new(v: Uint256) -> Balance {
|
||||
Balance(v)
|
||||
}
|
||||
|
||||
pub const fn zero() -> Balance {
|
||||
Balance(Uint256::zero())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Balance {
|
||||
type Target = Uint256;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Balance {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Uint256> for Balance {
|
||||
fn as_ref(&self) -> &Uint256 {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<Uint256> for Balance {
|
||||
fn as_mut(&mut self) -> &mut Uint256 {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Uint256> for Balance {
|
||||
fn from(v: Uint256) -> Self {
|
||||
Balance(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Balance> for Uint256 {
|
||||
fn from(b: Balance) -> Self {
|
||||
b.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use cosmwasm_std::StdError;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn native_lock() {
|
||||
let mut acc = Account {
|
||||
key: Key {
|
||||
chain_id: 0xbae2,
|
||||
token_chain: 0xbae2,
|
||||
token_address: TokenAddress::new([
|
||||
0x62, 0x4e, 0x8d, 0xc6, 0xe0, 0xfe, 0x16, 0xe2, 0x59, 0x6e, 0xcf, 0x9f, 0x90,
|
||||
0x0e, 0xd9, 0x5f, 0x4e, 0x6d, 0x26, 0xea, 0xf1, 0x9e, 0xe3, 0xe2, 0x88, 0x63,
|
||||
0x60, 0xff, 0xc4, 0x1b, 0xfb, 0x61,
|
||||
]),
|
||||
},
|
||||
|
||||
balance: Balance(500u128.into()),
|
||||
};
|
||||
|
||||
acc.lock_or_burn(200u128.into()).unwrap();
|
||||
|
||||
assert_eq!(acc.balance.0, Uint256::from(700u128));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_lock_overflow() {
|
||||
let mut acc = Account {
|
||||
key: Key {
|
||||
chain_id: 0xbae2,
|
||||
token_chain: 0xbae2,
|
||||
token_address: TokenAddress::new([
|
||||
0x62, 0x4e, 0x8d, 0xc6, 0xe0, 0xfe, 0x16, 0xe2, 0x59, 0x6e, 0xcf, 0x9f, 0x90,
|
||||
0x0e, 0xd9, 0x5f, 0x4e, 0x6d, 0x26, 0xea, 0xf1, 0x9e, 0xe3, 0xe2, 0x88, 0x63,
|
||||
0x60, 0xff, 0xc4, 0x1b, 0xfb, 0x61,
|
||||
]),
|
||||
},
|
||||
|
||||
balance: Balance(Uint256::MAX),
|
||||
};
|
||||
|
||||
let e = acc.lock_or_burn(200u128.into()).unwrap_err();
|
||||
|
||||
assert!(matches!(e, StdError::Overflow { .. }))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_unlock() {
|
||||
let mut acc = Account {
|
||||
key: Key {
|
||||
chain_id: 0xbae2,
|
||||
token_chain: 0xbae2,
|
||||
token_address: TokenAddress::new([
|
||||
0x62, 0x4e, 0x8d, 0xc6, 0xe0, 0xfe, 0x16, 0xe2, 0x59, 0x6e, 0xcf, 0x9f, 0x90,
|
||||
0x0e, 0xd9, 0x5f, 0x4e, 0x6d, 0x26, 0xea, 0xf1, 0x9e, 0xe3, 0xe2, 0x88, 0x63,
|
||||
0x60, 0xff, 0xc4, 0x1b, 0xfb, 0x61,
|
||||
]),
|
||||
},
|
||||
|
||||
balance: Balance(500u128.into()),
|
||||
};
|
||||
|
||||
acc.unlock_or_mint(200u128.into()).unwrap();
|
||||
|
||||
assert_eq!(acc.balance.0, Uint256::from(300u128));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_unlock_underflow() {
|
||||
let mut acc = Account {
|
||||
key: Key {
|
||||
chain_id: 0xbae2,
|
||||
token_chain: 0xbae2,
|
||||
token_address: TokenAddress::new([
|
||||
0x62, 0x4e, 0x8d, 0xc6, 0xe0, 0xfe, 0x16, 0xe2, 0x59, 0x6e, 0xcf, 0x9f, 0x90,
|
||||
0x0e, 0xd9, 0x5f, 0x4e, 0x6d, 0x26, 0xea, 0xf1, 0x9e, 0xe3, 0xe2, 0x88, 0x63,
|
||||
0x60, 0xff, 0xc4, 0x1b, 0xfb, 0x61,
|
||||
]),
|
||||
},
|
||||
|
||||
balance: Balance(Uint256::zero()),
|
||||
};
|
||||
|
||||
let e = acc.unlock_or_mint(200u128.into()).unwrap_err();
|
||||
|
||||
assert!(matches!(e, StdError::Overflow { .. }))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrapped_burn() {
|
||||
let mut acc = Account {
|
||||
key: Key {
|
||||
chain_id: 0xcae8,
|
||||
token_chain: 0xbae2,
|
||||
token_address: TokenAddress::new([
|
||||
0x62, 0x4e, 0x8d, 0xc6, 0xe0, 0xfe, 0x16, 0xe2, 0x59, 0x6e, 0xcf, 0x9f, 0x90,
|
||||
0x0e, 0xd9, 0x5f, 0x4e, 0x6d, 0x26, 0xea, 0xf1, 0x9e, 0xe3, 0xe2, 0x88, 0x63,
|
||||
0x60, 0xff, 0xc4, 0x1b, 0xfb, 0x61,
|
||||
]),
|
||||
},
|
||||
|
||||
balance: Balance(500u128.into()),
|
||||
};
|
||||
|
||||
acc.lock_or_burn(200u128.into()).unwrap();
|
||||
|
||||
assert_eq!(acc.balance.0, Uint256::from(300u128));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrapped_burn_underflow() {
|
||||
let mut acc = Account {
|
||||
key: Key {
|
||||
chain_id: 0xcae8,
|
||||
token_chain: 0xbae2,
|
||||
token_address: TokenAddress::new([
|
||||
0x62, 0x4e, 0x8d, 0xc6, 0xe0, 0xfe, 0x16, 0xe2, 0x59, 0x6e, 0xcf, 0x9f, 0x90,
|
||||
0x0e, 0xd9, 0x5f, 0x4e, 0x6d, 0x26, 0xea, 0xf1, 0x9e, 0xe3, 0xe2, 0x88, 0x63,
|
||||
0x60, 0xff, 0xc4, 0x1b, 0xfb, 0x61,
|
||||
]),
|
||||
},
|
||||
|
||||
balance: Balance(Uint256::zero()),
|
||||
};
|
||||
|
||||
let e = acc.lock_or_burn(200u128.into()).unwrap_err();
|
||||
|
||||
assert!(matches!(e, StdError::Overflow { .. }))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrapped_mint() {
|
||||
let mut acc = Account {
|
||||
key: Key {
|
||||
chain_id: 0xcae8,
|
||||
token_chain: 0xbae2,
|
||||
token_address: TokenAddress::new([
|
||||
0x62, 0x4e, 0x8d, 0xc6, 0xe0, 0xfe, 0x16, 0xe2, 0x59, 0x6e, 0xcf, 0x9f, 0x90,
|
||||
0x0e, 0xd9, 0x5f, 0x4e, 0x6d, 0x26, 0xea, 0xf1, 0x9e, 0xe3, 0xe2, 0x88, 0x63,
|
||||
0x60, 0xff, 0xc4, 0x1b, 0xfb, 0x61,
|
||||
]),
|
||||
},
|
||||
|
||||
balance: Balance(500u128.into()),
|
||||
};
|
||||
|
||||
acc.unlock_or_mint(200u128.into()).unwrap();
|
||||
|
||||
assert_eq!(acc.balance.0, Uint256::from(700u128));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrapped_mint_overflow() {
|
||||
let mut acc = Account {
|
||||
key: Key {
|
||||
chain_id: 0xcae8,
|
||||
token_chain: 0xbae2,
|
||||
token_address: TokenAddress::new([
|
||||
0x62, 0x4e, 0x8d, 0xc6, 0xe0, 0xfe, 0x16, 0xe2, 0x59, 0x6e, 0xcf, 0x9f, 0x90,
|
||||
0x0e, 0xd9, 0x5f, 0x4e, 0x6d, 0x26, 0xea, 0xf1, 0x9e, 0xe3, 0xe2, 0x88, 0x63,
|
||||
0x60, 0xff, 0xc4, 0x1b, 0xfb, 0x61,
|
||||
]),
|
||||
},
|
||||
|
||||
balance: Balance(Uint256::MAX),
|
||||
};
|
||||
|
||||
let e = acc.unlock_or_mint(200u128.into()).unwrap_err();
|
||||
|
||||
assert!(matches!(e, StdError::Overflow { .. }))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
use std::{
|
||||
fmt,
|
||||
ops::{Deref, DerefMut},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use cosmwasm_std::{StdError, StdResult};
|
||||
use cw_storage_plus::{Key, KeyDeserialize, Prefixer, PrimaryKey};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{de, Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, JsonSchema)]
|
||||
#[repr(transparent)]
|
||||
pub struct TokenAddress(#[schemars(with = "String")] [u8; 32]);
|
||||
|
||||
impl TokenAddress {
|
||||
pub const fn new(addr: [u8; 32]) -> TokenAddress {
|
||||
TokenAddress(addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 32]> for TokenAddress {
|
||||
fn from(addr: [u8; 32]) -> Self {
|
||||
TokenAddress(addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TokenAddress> for [u8; 32] {
|
||||
fn from(addr: TokenAddress) -> Self {
|
||||
addr.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for TokenAddress {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
<[u8; 32]>::try_from(value)
|
||||
.map(Self)
|
||||
.map_err(|v: Vec<u8>| anyhow!("invalid length; want 32, got {}", v.len()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for TokenAddress {
|
||||
type Target = [u8; 32];
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for TokenAddress {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8; 32]> for TokenAddress {
|
||||
fn as_ref(&self) -> &[u8; 32] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<[u8; 32]> for TokenAddress {
|
||||
fn as_mut(&mut self) -> &mut [u8; 32] {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for TokenAddress {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<[u8]> for TokenAddress {
|
||||
fn as_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TokenAddress {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(&hex::encode(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for TokenAddress {
|
||||
type Err = anyhow::Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
hex::decode(s)
|
||||
.context("failed to decode hex")
|
||||
.and_then(Self::try_from)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for TokenAddress {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&base64::encode(self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for TokenAddress {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_str(Base64Visitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct Base64Visitor;
|
||||
|
||||
impl<'de> de::Visitor<'de> for Base64Visitor {
|
||||
type Value = TokenAddress;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str("a valid base64 encoded string of a 32-byte array")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
base64::decode(v)
|
||||
.map_err(|_| E::invalid_value(de::Unexpected::Str(v), &self))
|
||||
.and_then(|b| {
|
||||
b.try_into()
|
||||
.map_err(|b: Vec<u8>| E::invalid_length(b.len(), &self))
|
||||
})
|
||||
.map(TokenAddress)
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyDeserialize for TokenAddress {
|
||||
type Output = Self;
|
||||
|
||||
fn from_vec(v: Vec<u8>) -> StdResult<Self::Output> {
|
||||
v.try_into()
|
||||
.map(TokenAddress)
|
||||
.map_err(|v| StdError::InvalidDataSize {
|
||||
expected: 32,
|
||||
actual: v.len() as u64,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PrimaryKey<'a> for TokenAddress {
|
||||
type Prefix = ();
|
||||
type SubPrefix = ();
|
||||
type Suffix = Self;
|
||||
type SuperSuffix = Self;
|
||||
|
||||
fn key(&self) -> Vec<Key> {
|
||||
vec![Key::Ref(&**self)]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Prefixer<'a> for TokenAddress {
|
||||
fn prefix(&self) -> Vec<Key> {
|
||||
vec![Key::Ref(&**self)]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
use std::{fmt, str::FromStr};
|
||||
|
||||
use anyhow::{ensure, Context};
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::{StdResult, Uint256};
|
||||
use cw_storage_plus::{Key as CwKey, KeyDeserialize, PrimaryKey};
|
||||
|
||||
use crate::state::TokenAddress;
|
||||
|
||||
#[cw_serde]
|
||||
pub struct Transfer {
|
||||
pub key: Key,
|
||||
pub data: Data,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(Eq, PartialOrd, Ord, Default, Hash)]
|
||||
pub struct Key {
|
||||
// The chain id of the chain on which this transfer originated.
|
||||
emitter_chain: u16,
|
||||
|
||||
// The address on the emitter chain that created this transfer.
|
||||
emitter_address: TokenAddress,
|
||||
|
||||
// The sequence number of the transfer.
|
||||
sequence: u64,
|
||||
}
|
||||
|
||||
impl Key {
|
||||
pub fn new(emitter_chain: u16, emitter_address: TokenAddress, sequence: u64) -> Self {
|
||||
Self {
|
||||
emitter_chain,
|
||||
emitter_address,
|
||||
sequence,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emitter_chain(&self) -> u16 {
|
||||
self.emitter_chain
|
||||
}
|
||||
|
||||
pub fn emitter_address(&self) -> &TokenAddress {
|
||||
&self.emitter_address
|
||||
}
|
||||
|
||||
pub fn sequence(&self) -> u64 {
|
||||
self.sequence
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Key {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{:05}/{}/{:016}",
|
||||
self.emitter_chain, self.emitter_address, self.sequence
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Key {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut components = s.split('/');
|
||||
let emitter_chain = components
|
||||
.next()
|
||||
.map(str::parse)
|
||||
.transpose()
|
||||
.context("failed to parse emitter chain")?
|
||||
.context("emitter chain missing")?;
|
||||
let emitter_address = components
|
||||
.next()
|
||||
.map(str::parse)
|
||||
.transpose()
|
||||
.context("failed to parse emitter address")?
|
||||
.context("emitter address missing")?;
|
||||
let sequence = components
|
||||
.next()
|
||||
.map(str::parse)
|
||||
.transpose()
|
||||
.context("failed to parse sequence")?
|
||||
.context("sequence missing")?;
|
||||
|
||||
ensure!(
|
||||
components.next().is_none(),
|
||||
"unexpected trailing input data"
|
||||
);
|
||||
|
||||
Ok(Key {
|
||||
emitter_chain,
|
||||
emitter_address,
|
||||
sequence,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyDeserialize for Key {
|
||||
type Output = Self;
|
||||
|
||||
fn from_vec(v: Vec<u8>) -> StdResult<Self::Output> {
|
||||
<(u16, TokenAddress, u64)>::from_vec(v).map(|(emitter_chain, emitter_address, sequence)| {
|
||||
Key {
|
||||
emitter_chain,
|
||||
emitter_address,
|
||||
sequence,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PrimaryKey<'a> for Key {
|
||||
type Prefix = (u16, TokenAddress);
|
||||
type SubPrefix = u16;
|
||||
type Suffix = u64;
|
||||
type SuperSuffix = (TokenAddress, u64);
|
||||
|
||||
fn key(&self) -> Vec<CwKey> {
|
||||
self.emitter_chain
|
||||
.key()
|
||||
.into_iter()
|
||||
.chain(self.emitter_address.key())
|
||||
.chain(self.sequence.key())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct Data {
|
||||
// The amount to be transferred.
|
||||
pub amount: Uint256,
|
||||
|
||||
// The id of the native chain of the token.
|
||||
pub token_chain: u16,
|
||||
|
||||
// The address of the token on its native chain.
|
||||
pub token_address: TokenAddress,
|
||||
|
||||
// The chain id where the tokens are being sent.
|
||||
pub recipient_chain: u16,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn key_display() {
|
||||
let addr = TokenAddress::new([
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 225, 139, 34, 20, 175, 249, 112, 0, 217, 116,
|
||||
207, 100, 126, 124, 52, 126, 143, 165, 133,
|
||||
]);
|
||||
let k = Key::new(2, addr, 254278);
|
||||
let expected = "00002/0000000000000000000000003ee18b2214aff97000d974cf647e7c347e8fa585/0000000000254278";
|
||||
assert_eq!(expected, k.to_string());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue