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.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "accounting"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"base64",
|
||||||
|
"cosmwasm-schema",
|
||||||
|
"cosmwasm-std",
|
||||||
|
"cw-storage-plus",
|
||||||
|
"hex",
|
||||||
|
"schemars",
|
||||||
|
"serde",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "addr2line"
|
name = "addr2line"
|
||||||
version = "0.17.0"
|
version = "0.17.0"
|
||||||
|
@ -33,6 +48,9 @@ name = "anyhow"
|
||||||
version = "1.0.66"
|
version = "1.0.66"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
|
checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
|
||||||
|
dependencies = [
|
||||||
|
"backtrace",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
|
|
|
@ -6,6 +6,7 @@ members = [
|
||||||
"contracts/token-bridge",
|
"contracts/token-bridge",
|
||||||
"contracts/shutdown-token-bridge",
|
"contracts/shutdown-token-bridge",
|
||||||
"contracts/mock-bridge-integration",
|
"contracts/mock-bridge-integration",
|
||||||
|
"packages/accounting",
|
||||||
"packages/wormhole-bindings",
|
"packages/wormhole-bindings",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -21,8 +22,8 @@ incremental = false
|
||||||
overflow-checks = true
|
overflow-checks = true
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
|
accounting = { path = "packages/accounting" }
|
||||||
cw20-wrapped-2 = { path = "contracts/cw20-wrapped" }
|
cw20-wrapped-2 = { path = "contracts/cw20-wrapped" }
|
||||||
token-bridge-terra-2 = { path = "contracts/token-bridge" }
|
token-bridge-terra-2 = { path = "contracts/token-bridge" }
|
||||||
wormhole-bindings = { path = "packages/wormhole-bindings" }
|
wormhole-bindings = { path = "packages/wormhole-bindings" }
|
||||||
wormhole-bridge-terra-2 = { path = "contracts/wormhole" }
|
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