From db2870a0d337bba332ebe001abf0124fdcd17c3f Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Wed, 27 Apr 2022 19:54:12 +0000 Subject: [PATCH] Refactor --- terra/Cargo.lock | 23 ++ terra/Cargo.toml | 5 +- terra/Makefile | 2 +- terra/contracts/icco-contributor/Cargo.toml | 1 + .../icco-contributor/src/contract.rs | 8 +- terra/contracts/icco-contributor/src/error.rs | 6 + .../contracts/icco-contributor/src/execute.rs | 161 ++++++++- terra/contracts/icco-contributor/src/lib.rs | 1 - terra/contracts/icco-contributor/src/msg.rs | 13 +- terra/contracts/icco-contributor/src/query.rs | 1 + .../contracts/icco-contributor/src/shared.rs | 78 +++-- terra/contracts/icco-contributor/src/state.rs | 32 +- .../src/testing/mock_querier.rs | 23 +- .../icco-contributor/src/testing/tests.rs | 68 ++-- terra/packages/icco/Cargo.toml | 32 ++ terra/packages/icco/src/byte_utils.rs | 97 ++++++ terra/packages/icco/src/common.rs | 321 ++++++++++++++++++ terra/packages/icco/src/lib.rs | 5 + 18 files changed, 751 insertions(+), 126 deletions(-) create mode 100644 terra/packages/icco/Cargo.toml create mode 100644 terra/packages/icco/src/byte_utils.rs create mode 100644 terra/packages/icco/src/common.rs create mode 100644 terra/packages/icco/src/lib.rs diff --git a/terra/Cargo.lock b/terra/Cargo.lock index 3d0b6711..93ed937b 100644 --- a/terra/Cargo.lock +++ b/terra/Cargo.lock @@ -708,6 +708,28 @@ dependencies = [ "digest", ] +[[package]] +name = "icco" +version = "0.1.0" +dependencies = [ + "bigint", + "cosmwasm-std", + "cosmwasm-vm", + "cw-storage-plus", + "cw20", + "cw20-base", + "generic-array", + "hex", + "k256", + "lazy_static", + "schemars", + "serde", + "serde_json", + "sha3", + "terraswap", + "thiserror", +] + [[package]] name = "icco-contributor" version = "0.1.0" @@ -720,6 +742,7 @@ dependencies = [ "cw20-base", "generic-array", "hex", + "icco", "k256", "lazy_static", "schemars", diff --git a/terra/Cargo.toml b/terra/Cargo.toml index ed68e9a8..75263c32 100644 --- a/terra/Cargo.toml +++ b/terra/Cargo.toml @@ -1,5 +1,8 @@ [workspace] -members = ["contracts/icco-contributor"] +members = [ + "packages/*", + "contracts/icco-contributor" +] [profile.release] opt-level = 3 diff --git a/terra/Makefile b/terra/Makefile index 72f3cb68..fbe2653e 100644 --- a/terra/Makefile +++ b/terra/Makefile @@ -47,7 +47,7 @@ tools/node_modules: tools/package-lock.json ## Run linter and unit tests test: # cargo clippy - cargo test + cargo test -- --nocapture .PHONY: clean clean: diff --git a/terra/contracts/icco-contributor/Cargo.toml b/terra/contracts/icco-contributor/Cargo.toml index eea05ed7..af3a95e1 100644 --- a/terra/contracts/icco-contributor/Cargo.toml +++ b/terra/contracts/icco-contributor/Cargo.toml @@ -30,6 +30,7 @@ bigint = "4" wormhole-bridge-terra = { git = "https://github.com/certusone/wormhole", branch = "feat/token-bridge-proxy", features = ["library"]} token-bridge-terra = { git = "https://github.com/certusone/wormhole", branch="feat/token-bridge-proxy", features = ["library"] } +icco = { path = "../../packages/icco", features = ["library"] } [dev-dependencies] cosmwasm-vm = { version = "0.16.0", default-features = false } diff --git a/terra/contracts/icco-contributor/src/contract.rs b/terra/contracts/icco-contributor/src/contract.rs index e0042799..b48d1bbe 100644 --- a/terra/contracts/icco-contributor/src/contract.rs +++ b/terra/contracts/icco-contributor/src/contract.rs @@ -15,9 +15,6 @@ use crate::{ state::{Config, CONFIG}, }; -// Chain ID of Terra -pub const CHAIN_ID: u16 = 3; - #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult { Ok(Response::new()) @@ -27,14 +24,15 @@ pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult StdResult { let cfg = Config { wormhole_contract: msg.wormhole_contract, token_bridge_contract: msg.token_bridge_contract, conductor_chain: msg.conductor_chain, - conductor_address: msg.conductor_address.as_slice().to_vec(), + conductor_address: msg.conductor_address.into(), + owner: info.sender.to_string(), }; CONFIG.save(deps.storage, &cfg)?; diff --git a/terra/contracts/icco-contributor/src/error.rs b/terra/contracts/icco-contributor/src/error.rs index 056cf4ac..e3f41bf1 100644 --- a/terra/contracts/icco-contributor/src/error.rs +++ b/terra/contracts/icco-contributor/src/error.rs @@ -47,6 +47,12 @@ pub enum ContributorError { #[error("WrongChain")] WrongChain, + + #[error("NonexistentToken")] + NonexistentToken, + + #[error("NonexistentDenom")] + NonexistentDenom, } impl ContributorError { diff --git a/terra/contracts/icco-contributor/src/execute.rs b/terra/contracts/icco-contributor/src/execute.rs index cf08e73a..91f0691c 100644 --- a/terra/contracts/icco-contributor/src/execute.rs +++ b/terra/contracts/icco-contributor/src/execute.rs @@ -1,24 +1,44 @@ use cosmwasm_std::{ to_binary, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Order, QueryRequest, Response, - StdError, StdResult, Storage, Uint256, WasmMsg, WasmQuery, + StdError, StdResult, Storage, Uint128, WasmMsg, WasmQuery, +}; +use terraswap::{ + asset::AssetInfo, + querier::{query_balance, query_token_balance}, }; +use token_bridge_terra::contract::coins_after_tax; use wormhole::{ msg::{ExecuteMsg as WormholeExecuteMsg, QueryMsg as WormholeQueryMsg}, state::ParsedVAA, }; +use icco::common::{ + AcceptedToken, ContributionsSealed, SaleAborted, SaleInit, SaleSealed, SaleStatus, CHAIN_ID, +}; + use crate::{ error::ContributorError, - shared::{AcceptedToken, ContributionsSealed, SaleAborted, SaleInit, SaleSealed, SaleStatus}, state::{ - SaleMessage, TokenIndexKey, ACCEPTED_TOKENS, CHAIN_ID, CONFIG, SALES, SALE_STATUSES, - SALE_TIMES, TOTAL_ALLOCATIONS, TOTAL_CONTRIBUTIONS, ZERO_AMOUNT, + load_accepted_token, + SaleMessage, + TokenIndexKey, + UserAction, + ACCEPTED_ASSETS, + ACCEPTED_TOKENS, + CONFIG, + SALES, + SALE_STATUSES, + SALE_TIMES, + TOTAL_ALLOCATIONS, + TOTAL_CONTRIBUTIONS, //USER_ACTIONS, + ZERO_AMOUNT, }, }; // nonce means nothing? const WORMHOLE_NONCE: u32 = 0; +const NATIVE_DENOM_START_INDEX: usize = 28; pub fn init_sale(deps: DepsMut, env: Env, _info: MessageInfo, vaa: &Binary) -> StdResult { let parsed = parse_vaa(deps.as_ref(), env.block.time.seconds(), vaa)?; @@ -62,8 +82,28 @@ pub fn init_sale(deps: DepsMut, env: Env, _info: MessageInfo, vaa: &Binary) -> S let key: TokenIndexKey = (sale_id, token_index.into()); if token.chain == CHAIN_ID { - // TODO: check if token is actual CW20 + let querier = &deps.querier; + let this = env.contract.address.clone(); + + // attempt to check balances to verify existence + let asset_info = token.make_asset_info(deps.api)?; + match asset_info.clone() { + AssetInfo::NativeToken { denom } => { + if query_balance(querier, this, denom).is_err() { + return ContributorError::NonexistentDenom.std_err(); + } + } + AssetInfo::Token { contract_addr } => { + let validated = deps.api.addr_validate(contract_addr.as_str())?; + if query_token_balance(querier, validated, this).is_err() { + return ContributorError::NonexistentToken.std_err(); + } + } + } + + ACCEPTED_ASSETS.save(storage, key.clone(), &asset_info)?; } + ACCEPTED_TOKENS.save(storage, key.clone(), &token)?; TOTAL_CONTRIBUTIONS.save(storage, key.clone(), &ZERO_AMOUNT)?; TOTAL_ALLOCATIONS.save(storage, key.clone(), &ZERO_AMOUNT)?; @@ -77,22 +117,27 @@ pub fn init_sale(deps: DepsMut, env: Env, _info: MessageInfo, vaa: &Binary) -> S .add_attribute("token_address", hex::encode(&sale.token_address))) } +// TODO : add signature argument pub fn contribute( mut deps: DepsMut, env: Env, info: MessageInfo, sale_id: &[u8], token_index: u8, - amount: Uint256, + amount: Uint128, ) -> StdResult { - // TODO : add signature argument - let storage = deps.storage; - let status = SALE_STATUSES.load(storage, sale_id)?; + //if USER_ACTIONS.load(deps.storage, sale_id).is_ok() { + // return Err(StdError::generic_err("user action exists")); + //} + + //USER_ACTIONS.save(deps.storage, sale_id, &UserAction::CONTRIBUTE); + + let status = SALE_STATUSES.load(deps.storage, sale_id)?; if status.is_aborted { return ContributorError::SaleAborted.std_err(); } - let times = SALE_TIMES.load(storage, sale_id)?; + let times = SALE_TIMES.load(deps.storage, sale_id)?; let now = env.block.time.seconds(); if now < times.start { return ContributorError::SaleNotStarted.std_err(); @@ -100,17 +145,87 @@ pub fn contribute( return ContributorError::SaleEnded.std_err(); } - let token = ACCEPTED_TOKENS.load(storage, (sale_id, token_index.into()))?; - if token.chain != CHAIN_ID { - return ContributorError::WrongChain.std_err(); + let asset_info = ACCEPTED_ASSETS.load(deps.storage, (sale_id, token_index.into()))?; + + //let token = ACCEPTED_TOKENS.load(deps.storage, (sale_id, token_index.into()))?; + //if token.chain != CHAIN_ID { + // return ContributorError::WrongChain.std_err(); + //} + + match asset_info { + AssetInfo::NativeToken { denom } => { + contribute_native(deps, env, info, sale_id, token_index, &denom, amount) + } + AssetInfo::Token { contract_addr } => contribute_token( + deps, + env, + info, + sale_id, + token_index, + &contract_addr, + amount, + ), } + /* + let new_balance: BalanceResponse = + deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: state.token_address.clone(), + msg: to_binary(&TokenQuery::Balance { + address: env.contract.address.to_string(), + })?, + }))?; + + */ //assert!(USER_ACTION.load(deps.storage).is_err()); +} + +pub fn contribute_native( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + sale_id: &[u8], + token_index: u8, + denom: &String, + amount: Uint128, +) -> StdResult { + // TODO: do I handle reply after this? + // USER_ACTIONS.remove(deps.storage, sale_id)? + Ok(Response::new() + .add_attribute("action", "contribute_native") + .add_attribute("sale_id", Binary::from(sale_id).to_base64()) + .add_attribute("denom", denom) + .add_attribute("amount", amount.to_string())) +} + +pub fn contribute_token( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + sale_id: &[u8], + token_index: u8, + contract_addr: &String, + amount: Uint128, +) -> StdResult { + // check balance + /* + let balance: BalanceResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: contract_addr.clone(), + msg: to_binary(&TokenQuery::Balance { + address: env.contract.address.to_string(), + })?, + }))?; + */ + + let querier = &deps.querier; + let validated = deps.api.addr_validate(contract_addr.as_str())?; + let balance = query_token_balance(querier, validated, env.contract.address.clone())?; Ok(Response::new() - .add_attribute("action", "contribute") + .add_attribute("action", "contribute_token") .add_attribute("sale_id", Binary::from(sale_id).to_base64()) - .add_attribute("token_address", Binary::from(token.address).to_base64())) + .add_attribute("contract_addr", contract_addr.clone()) + .add_attribute("amount", amount.to_string())) } pub fn attest_contributions( @@ -354,3 +469,19 @@ fn parse_vaa(deps: Deps, block_time: u64, data: &Binary) -> StdResult }))?; Ok(vaa) } + +/* +fn query_cw20_balance( + deps: Deps, + contract_addr: String, + wallet_addr: String, +) -> StdResult { + let balance: BalanceResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: contract_addr.clone(), + msg: to_binary(&TokenQuery::Balance { + address: wallet_addr.clone(), + })?, + }))?; + Ok(balance) +} +*/ diff --git a/terra/contracts/icco-contributor/src/lib.rs b/terra/contracts/icco-contributor/src/lib.rs index b4e6e99a..a07d1519 100644 --- a/terra/contracts/icco-contributor/src/lib.rs +++ b/terra/contracts/icco-contributor/src/lib.rs @@ -6,7 +6,6 @@ mod error; mod execute; mod msg; mod query; -mod shared; mod state; #[cfg(test)] diff --git a/terra/contracts/icco-contributor/src/msg.rs b/terra/contracts/icco-contributor/src/msg.rs index 086b41b8..4ae271b8 100644 --- a/terra/contracts/icco-contributor/src/msg.rs +++ b/terra/contracts/icco-contributor/src/msg.rs @@ -6,15 +6,13 @@ use std::string::String; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct InstantiateMsg { - // governance contract details - pub gov_chain: u16, - pub gov_address: Binary, - pub wormhole_contract: String, pub token_bridge_contract: String, pub conductor_chain: u16, pub conductor_address: Binary, + + pub owner: String, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -26,7 +24,7 @@ pub enum ExecuteMsg { Contribute { sale_id: Binary, token_index: u8, - amount: Uint256, + amount: Uint128, }, AttestContributions { sale_id: Binary, @@ -78,6 +76,7 @@ pub struct SaleRegistryResponse { pub token_chain: u16, pub token_amount: Uint256, pub min_raise: Uint256, + pub max_raise: Uint256, pub sale_start: u64, pub sale_end: u64, pub recipient: Vec, @@ -115,7 +114,7 @@ pub struct AcceptedTokenResponse { pub struct TotalContributionResponse { pub id: Vec, pub token_index: u8, - pub amount: Uint256, + pub amount: Uint128, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -123,5 +122,5 @@ pub struct TotalContributionResponse { pub struct TotalAllocationResponse { pub id: Vec, pub token_index: u8, - pub amount: Uint256, + pub amount: Uint128, } diff --git a/terra/contracts/icco-contributor/src/query.rs b/terra/contracts/icco-contributor/src/query.rs index 5fac3fc7..f3345694 100644 --- a/terra/contracts/icco-contributor/src/query.rs +++ b/terra/contracts/icco-contributor/src/query.rs @@ -30,6 +30,7 @@ pub fn query_sale_registry(deps: Deps, sale_id: &Binary) -> StdResult, pub refund_recipient: Vec, } -/* -impl Clone for SaleCore { - fn clone(&self) -> SaleCore { - SaleCore { - id: self.id.clone(), - token_address: self.token_address.clone(), - token_chain: self.token_chain, - token_amount: self.token_amount, - min_raise: self.min_raise, - times: self.times.clone(), - recipient: self.recipient.clone(), - refund_recipient: self.refund_recipient.clone(), - } - } -} -*/ - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct AcceptedToken { pub chain: u16, @@ -56,7 +44,7 @@ pub struct SaleStatus { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct Contribution { pub token_index: u8, - pub contributed: Uint256, + pub contributed: Uint256, // actually Uint128, but will be serialized as Uint256 } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -94,6 +82,10 @@ impl AcceptedToken { pub fn deserialize(data: &[u8]) -> StdResult { let address = data.get_bytes32(0).to_vec(); + if address.len() != 32 { + return Err(StdError::generic_err("address.len() != 32")); + } + let chain = data.get_u16(32); let conversion_rate = data.get_u128_be(34); Ok(AcceptedToken { @@ -111,6 +103,43 @@ impl AcceptedToken { ] .concat() } + + pub fn make_asset_info(&self, api: &dyn Api) -> StdResult { + if self.chain != CHAIN_ID { + return Err(StdError::generic_err("chain != terra")); + } + + let addr = self.address.as_slice(); + match addr[0] { + 1u8 => { + // match first "u" (e.g. uusd) + match addr.iter().position(|&x| x == 117u8) { + Some(idx) => { + let denom = &addr[idx..32]; + match String::from_utf8(denom.into()) { + Ok(denom) => Ok(AssetInfo::NativeToken { denom }), + _ => Err(StdError::generic_err("not valid denom")), + } + } + None => Err(StdError::generic_err("not valid denom")), + } + } + _ => { + let token_address = CanonicalAddr::from(&addr[12..32]); + let humanized = api.addr_humanize(&token_address)?; + Ok(AssetInfo::Token { + contract_addr: humanized.to_string(), + }) + } + } + } + + pub fn make_asset(&self, api: &dyn Api, amount: Uint128) -> StdResult { + Ok(Asset { + info: self.make_asset_info(api)?, + amount: amount, + }) + } } pub fn to_const_bytes32(data: &[u8], index: usize) -> [u8; 32] { @@ -123,7 +152,7 @@ pub fn to_u256(data: &[u8], index: usize) -> Uint256 { impl SaleInit { pub const PAYLOAD_ID: u8 = 1; - const INDEX_ACCEPTED_TOKENS_START: usize = 194; + const INDEX_ACCEPTED_TOKENS_START: usize = 226; pub fn deserialize(data: &[u8]) -> StdResult { let sale_id = data.get_bytes32(0).to_vec(); @@ -131,8 +160,9 @@ impl SaleInit { let token_chain = data.get_u16(64); let token_amount = to_u256(data, 66); let min_raise = to_u256(data, 98); - let start = data.get_u64(130 + 24); // encoded as u256, but we only care about u64 time - let end = data.get_u64(162 + 24); // encoded as u256, but we only care about u64 for time + let max_raise = to_u256(data, 130); + let start = data.get_u64(162 + 24); // encoded as u256, but we only care about u64 time + let end = data.get_u64(194 + 24); // encoded as u256, but we only care about u64 for time let accepted_tokens = SaleInit::deserialize_tokens(&data[SaleInit::INDEX_ACCEPTED_TOKENS_START..])?; @@ -151,6 +181,7 @@ impl SaleInit { token_chain, token_amount, min_raise, + max_raise, times: SaleTimes { start, end }, recipient, refund_recipient, @@ -187,12 +218,13 @@ impl ContributionsSealed { } } - pub fn add_contribution(&mut self, token_index: u8, contributed: Uint256) -> StdResult { + pub fn add_contribution(&mut self, token_index: u8, contributed: Uint128) -> StdResult { // limit to len 255 if self.contributions.len() >= 256 { return Err(StdError::generic_err("cannot exceed length 256")); } + let contributed = Uint256::from(contributed.u128()); self.contributions.push(Contribution { token_index, contributed, diff --git a/terra/contracts/icco-contributor/src/state.rs b/terra/contracts/icco-contributor/src/state.rs index 1c2f79b0..61510cd9 100644 --- a/terra/contracts/icco-contributor/src/state.rs +++ b/terra/contracts/icco-contributor/src/state.rs @@ -1,16 +1,17 @@ -use cosmwasm_std::{StdResult, Storage, Uint256}; +use cosmwasm_std::{StdResult, Storage, Uint128}; use cw_storage_plus::{Item, Map, U8Key}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use terraswap::asset::AssetInfo; use wormhole::byte_utils::ByteUtils; -use crate::shared::{AcceptedToken, SaleCore, SaleStatus, SaleTimes}; +use icco::common::{AcceptedToken, SaleCore, SaleStatus, SaleTimes}; // per sale_id and token_index, we need to track a buyer's contribution, as well as whether // he has been refunded or his allocations have been claimed #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct BuyerStatus { - pub contribution: Uint256, + pub contribution: Uint128, pub allocation_is_claimed: bool, pub refund_is_claimed: bool, } @@ -21,6 +22,7 @@ pub struct Config { pub token_bridge_contract: HumanAddr, pub conductor_chain: u16, pub conductor_address: Vec, + pub owner: HumanAddr, } pub struct SaleMessage<'a> { @@ -33,24 +35,30 @@ pub struct UpgradeContract { pub new_contract: u64, } */ +pub struct UserAction; + +impl UserAction { + pub const CONTRIBUTE: u8 = 1; +} pub type HumanAddr = String; pub type SaleId<'a> = &'a [u8]; pub type TokenIndexKey<'a> = (SaleId<'a>, U8Key); pub type BuyerTokenIndexKey<'a> = (SaleId<'a>, U8Key, &'a HumanAddr); -pub const CHAIN_ID: u16 = 3; - pub const CONFIG: Item = Item::new("config"); pub const SALES: Map = Map::new("sales"); pub const SALE_STATUSES: Map = Map::new("sale_statuses"); pub const SALE_TIMES: Map = Map::new("sale_times"); -pub const ACCEPTED_TOKENS: Map<(SaleId, U8Key), AcceptedToken> = Map::new("accepted_tokens"); -pub const TOTAL_CONTRIBUTIONS: Map = Map::new("total_contributions"); -pub const TOTAL_ALLOCATIONS: Map = Map::new("total_allocations"); -//const BUYER_STATUS: Map = Map::new("buyer_statuses"); +pub const ACCEPTED_ASSETS: Map = Map::new("accepted_tokens"); +pub const ACCEPTED_TOKENS: Map = Map::new("accepted_tokens"); +pub const TOTAL_CONTRIBUTIONS: Map = Map::new("total_contributions"); +pub const TOTAL_ALLOCATIONS: Map = Map::new("total_allocations"); +pub const BUYER_STATUSES: Map = Map::new("buyer_statuses"); +//pub const USER_ACTIONS: Map = Map::new("user_actions"); +//pub const TOKEN_CONTRIBUTE_TMP = Item<> = Item::new("token_contribute_tmp"); -pub const ZERO_AMOUNT: Uint256 = Uint256::zero(); +pub const ZERO_AMOUNT: Uint128 = Uint128::zero(); pub fn load_accepted_token( storage: &dyn Storage, @@ -64,7 +72,7 @@ pub fn load_total_contribution( storage: &dyn Storage, sale_id: &[u8], token_index: u8, -) -> StdResult { +) -> StdResult { TOTAL_CONTRIBUTIONS.load(storage, (sale_id, token_index.into())) } @@ -72,7 +80,7 @@ pub fn load_total_allocation( storage: &dyn Storage, sale_id: &[u8], token_index: u8, -) -> StdResult { +) -> StdResult { TOTAL_ALLOCATIONS.load(storage, (sale_id, token_index.into())) } diff --git a/terra/contracts/icco-contributor/src/testing/mock_querier.rs b/terra/contracts/icco-contributor/src/testing/mock_querier.rs index 1fe70296..27dced5f 100644 --- a/terra/contracts/icco-contributor/src/testing/mock_querier.rs +++ b/terra/contracts/icco-contributor/src/testing/mock_querier.rs @@ -26,20 +26,6 @@ pub fn mock_dependencies( pub struct WasmMockQuerier { base: MockQuerier, - minter_querier: MinterQuerier, -} - -#[derive(Clone, Default)] -pub struct MinterQuerier { - minter_addr: String, -} - -impl MinterQuerier { - pub fn new(minter: String) -> Self { - MinterQuerier { - minter_addr: minter, - } - } } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -85,13 +71,6 @@ impl WasmMockQuerier { impl WasmMockQuerier { pub fn new(base: MockQuerier) -> Self { - WasmMockQuerier { - base, - minter_querier: MinterQuerier::default(), - } - } - - pub fn with_anc_minter(&mut self, minter: String) { - self.minter_querier = MinterQuerier::new(minter); + WasmMockQuerier { base } } } diff --git a/terra/contracts/icco-contributor/src/testing/tests.rs b/terra/contracts/icco-contributor/src/testing/tests.rs index 6b7cdd81..dd9a8a06 100644 --- a/terra/contracts/icco-contributor/src/testing/tests.rs +++ b/terra/contracts/icco-contributor/src/testing/tests.rs @@ -1,3 +1,9 @@ +use cosmwasm_std::testing::{mock_env, mock_info}; +use cosmwasm_std::{from_binary, Binary, StdResult, Uint128, Uint256}; +use std::string::String; + +use icco::common::{SaleAborted, SaleSealed}; + use crate::{ contract::{execute, instantiate, query}, msg::{ @@ -5,16 +11,9 @@ use crate::{ SaleRegistryResponse, SaleStatusResponse, SaleTimesResponse, TotalAllocationResponse, TotalContributionResponse, }, - shared::{SaleAborted, SaleSealed}, state::SaleMessage, testing::mock_querier::mock_dependencies, }; -use cosmwasm_std::testing::{mock_env, mock_info}; -use cosmwasm_std::{from_binary, to_binary, Binary, StdResult, Uint256}; - -use std::string::String; - -use wormhole::byte_utils::ByteUtils; /* @@ -142,11 +141,10 @@ use wormhole::byte_utils::ByteUtils; saleAborted vaa 0x010000000001003f2c65c95e0309fec40c863e6f9d318141c7febc648f87f9f65ba43e64c33840574e461593dffc11a8d577bd0c8e9fe9368b5789fd2cc30e6fe0faf1699af53400000004900000000000020000000000000000000000005f8e26facc23fa4cbd87b8d9dbbd33d5047abde100000000000000030f040000000000000000000000000000000000000000000000000000000000000001 */ -const ADDRESS_ZERO: [u8; 32] = [0u8; 32]; - #[test] fn proper_initialization() -> StdResult<()> { let mut deps = mock_dependencies(&[]); + let info = mock_info("creator", &[]); let conductor_chain = 2u16; let conductor_address = "0000000000000000000000005f8e26facc23fa4cbd87b8d9dbbd33d5047abde1"; @@ -154,14 +152,12 @@ fn proper_initialization() -> StdResult<()> { let conductor_address = conductor_address.as_slice(); let msg = InstantiateMsg { - gov_chain: 1, - gov_address: Binary::from(Vec::from(ADDRESS_ZERO)), wormhole_contract: String::new(), token_bridge_contract: String::new(), conductor_chain: conductor_chain, conductor_address: Binary::from(conductor_address), + owner: info.sender.to_string(), }; - let info = mock_info("creator", &[]); let response = instantiate(deps.as_mut(), mock_env(), info, msg.clone())?; assert_eq!( @@ -189,25 +185,22 @@ fn proper_initialization() -> StdResult<()> { #[test] fn init_sale() -> StdResult<()> { let mut deps = mock_dependencies(&[]); - - let env = mock_env(); let info = mock_info("creator", &[]); let conductor_chain = 2u16; - let conductor_address = "0000000000000000000000005f8e26facc23fa4cbd87b8d9dbbd33d5047abde1"; + let conductor_address = "000000000000000000000000f19a2a01b70519f67adb309a994ec8c69a967e8b"; let conductor_address = hex::decode(conductor_address).unwrap(); let conductor_address = conductor_address.as_slice(); let msg = InstantiateMsg { - gov_chain: 1, - gov_address: Binary::from(Vec::from(ADDRESS_ZERO)), wormhole_contract: String::new(), token_bridge_contract: String::new(), conductor_chain: conductor_chain, conductor_address: Binary::from(conductor_address), + owner: info.sender.to_string(), }; - let response = instantiate(deps.as_mut(), env.clone(), info, msg)?; + let response = instantiate(deps.as_mut(), mock_env(), info, msg)?; assert_eq!( response.messages.len(), 0, @@ -215,35 +208,33 @@ fn init_sale() -> StdResult<()> { ); let signed_vaa = "\ - 01000000000100fd75a4cd9fe22519fbe7856267f59d0e0bf9931bf8a7733de4\ - e7b576451ae1b3309c9adee5c96e82e9b06b03fa16e189e3fe9eff132c1f9ce9\ - 22710524a6476300000003880000000000020000000000000000000000005f8e\ - 26facc23fa4cbd87b8d9dbbd33d5047abde100000000000000000f0100000000\ + 01000000000100ae0eda623b8aae9bde03c68922ac218bb0c3aa9c5ea0a70a7a\ + caea0a46d0915a7b3a06758250e44e6c543a37ea1097b85be6f75bd07127b802\ + 38ed6e6b73cbc00000000563000000000002000000000000000000000000f19a\ + 2a01b70519f67adb309a994ec8c69a967e8b00000000000000000f0100000000\ 0000000000000000000000000000000000000000000000000000000000000000\ 000000000000000083752ecafebf4707258dedffbd9c7443148169db00020000\ 000000000000000000000000000000000000000000000de0b6b3a76400000000\ 000000000000000000000000000000000000000000008ac7230489e800000000\ - 00000000000000000000000000000000000000000000000000000000038c0000\ - 0000000000000000000000000000000000000000000000000000000003c80600\ + 00000000000000000000000000000000000000000000c249fdd3277800000000\ + 0000000000000000000000000000000000000000000000000000000005670000\ + 0000000000000000000000000000000000000000000000000000000005a30400\ 0000000000000000000000ddb64fe46a91d46ee29420539fc25fd07c5fea3e00\ 0200000000000000000de0b6b3a7640000000000000000000000000000ddb64f\ e46a91d46ee29420539fc25fd07c5fea3e0004000000000000000002c68af0bb\ 1400000000000000000000000000008a5bbc20ad253e296f61601e868a3206b2\ d4774c0002000000000000000002c68af0bb1400000000000000000000000000\ 003d9e7a12daa29a8b2b1bfaa9dc97ce018853ab31000400000000000000000d\ - e0b6b3a7640000000000000000000000000000ddb64fe46a91d46ee29420539f\ - c25fd07c5fea3e0004000000000000000002c68af0bb14000000000000000000\ - 0000000000ddb64fe46a91d46ee29420539fc25fd07c5fea3e00040000000000\ - 00000002c68af0bb14000000000000000000000000000022d491bde2303f2f43\ - 325b2108d26f1eaba1e32b00000000000000000000000022d491bde2303f2f43\ - 325b2108d26f1eaba1e32b"; + e0b6b3a764000000000000000000000000000022d491bde2303f2f43325b2108\ + d26f1eaba1e32b00000000000000000000000022d491bde2303f2f43325b2108\ + d26f1eaba1e32b"; let signed_vaa = hex::decode(signed_vaa).unwrap(); let info = mock_info("addr0001", &[]); let msg = ExecuteMsg::InitSale { data: Binary::from(signed_vaa.as_slice()), }; - let _response = execute(deps.as_mut(), env.clone(), info, msg)?; + let _response = execute(deps.as_mut(), mock_env(), info, msg)?; let sale_id = "0000000000000000000000000000000000000000000000000000000000000000"; let sale_id = hex::decode(sale_id).unwrap(); @@ -344,10 +335,13 @@ fn init_sale() -> StdResult<()> { let min_raise = Uint256::from(10_000_000_000_000_000_000u128); assert_eq!(sale.min_raise, min_raise, "sale.min_raise != expected"); - let sale_start = 908u64; + let max_raise = Uint256::from(10_000_000_000_000_000_000u128); + assert_eq!(sale.min_raise, max_raise, "sale.max_raise != expected"); + + let sale_start = 1383u64; assert_eq!(sale.sale_start, sale_start, "sale.sale_start != expected"); - let sale_end = 968u64; + let sale_end = 1443u64; assert_eq!(sale.sale_end, sale_end, "sale.sale_end != expected"); let recipient = "00000000000000000000000022d491bde2303f2f43325b2108d26f1eaba1e32b"; @@ -380,8 +374,6 @@ fn init_sale() -> StdResult<()> { "000000000000000000000000ddb64fe46a91d46ee29420539fc25fd07c5fea3e", "0000000000000000000000008a5bbc20ad253e296f61601e868a3206b2d4774c", "0000000000000000000000003d9e7a12daa29a8b2b1bfaa9dc97ce018853ab31", - "000000000000000000000000ddb64fe46a91d46ee29420539fc25fd07c5fea3e", - "000000000000000000000000ddb64fe46a91d46ee29420539fc25fd07c5fea3e", ]); let accepted_token_chains = Vec::from([2u16, 4u16, 2u16, 4u16, 4u16, 4u16]); let accepted_token_conversion_rates = Vec::from([ @@ -389,8 +381,6 @@ fn init_sale() -> StdResult<()> { 200_000_000_000_000_000u128, 200_000_000_000_000_000u128, 1_000_000_000_000_000_000u128, - 200_000_000_000_000_000u128, - 200_000_000_000_000_000u128, ]); let n_accepted_tokens = accepted_token_addresses.len(); @@ -432,7 +422,7 @@ fn init_sale() -> StdResult<()> { let total_contribution: TotalContributionResponse = from_binary(&response)?; assert_eq!( total_contribution.amount, - Uint256::zero(), + Uint128::zero(), "total_contribution.amount != 0" ); @@ -447,7 +437,7 @@ fn init_sale() -> StdResult<()> { let total_allocation: TotalAllocationResponse = from_binary(&response)?; assert_eq!( total_allocation.amount, - Uint256::zero(), + Uint128::zero(), "total_allocation.amount != 0" ); } diff --git a/terra/packages/icco/Cargo.toml b/terra/packages/icco/Cargo.toml new file mode 100644 index 00000000..d7dcc856 --- /dev/null +++ b/terra/packages/icco/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "icco" +version = "0.1.0" +edition = "2018" +description = "Common ICCO Types" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +backtraces = ["cosmwasm-std/backtraces"] +library = [] + +[dependencies] +cosmwasm-std = { version = "0.16.0" } +schemars = "0.8.1" +serde = { version = "1.0.103", default-features = false, features = ["derive"] } +cw20 = "0.8.0" +cw20-base = { version = "0.8.0", features = ["library"] } +cw-storage-plus = { version = "0.8.0" } +terraswap = "2.4.0" +thiserror = { version = "1.0.20" } +k256 = { version = "0.9.4", default-features = false, features = ["ecdsa"] } +sha3 = { version = "0.9.1", default-features = false } +generic-array = { version = "0.14.4" } +hex = "0.4.2" +lazy_static = "1.4.0" +bigint = "4" + +[dev-dependencies] +cosmwasm-vm = { version = "0.16.0", default-features = false } +serde_json = "1.0" diff --git a/terra/packages/icco/src/byte_utils.rs b/terra/packages/icco/src/byte_utils.rs new file mode 100644 index 00000000..b31b6c85 --- /dev/null +++ b/terra/packages/icco/src/byte_utils.rs @@ -0,0 +1,97 @@ +use cosmwasm_std::CanonicalAddr; + +pub trait ByteUtils { + fn get_u8(&self, index: usize) -> u8; + fn get_u16(&self, index: usize) -> u16; + fn get_u32(&self, index: usize) -> u32; + fn get_u64(&self, index: usize) -> u64; + + fn get_u128_be(&self, index: usize) -> u128; + /// High 128 then low 128 + fn get_u256(&self, index: usize) -> (u128, u128); + fn get_address(&self, index: usize) -> CanonicalAddr; + fn get_bytes32(&self, index: usize) -> &[u8]; + fn get_bytes(&self, index: usize, bytes: usize) -> &[u8]; + fn get_const_bytes(&self, index: usize) -> [u8; N]; +} + +impl ByteUtils for &[u8] { + fn get_u8(&self, index: usize) -> u8 { + self[index] + } + fn get_u16(&self, index: usize) -> u16 { + let mut bytes: [u8; 16 / 8] = [0; 16 / 8]; + bytes.copy_from_slice(&self[index..index + 2]); + u16::from_be_bytes(bytes) + } + fn get_u32(&self, index: usize) -> u32 { + let mut bytes: [u8; 32 / 8] = [0; 32 / 8]; + bytes.copy_from_slice(&self[index..index + 4]); + u32::from_be_bytes(bytes) + } + fn get_u64(&self, index: usize) -> u64 { + let mut bytes: [u8; 64 / 8] = [0; 64 / 8]; + bytes.copy_from_slice(&self[index..index + 8]); + u64::from_be_bytes(bytes) + } + fn get_u128_be(&self, index: usize) -> u128 { + let mut bytes: [u8; 128 / 8] = [0; 128 / 8]; + bytes.copy_from_slice(&self[index..index + 128 / 8]); + u128::from_be_bytes(bytes) + } + fn get_u256(&self, index: usize) -> (u128, u128) { + (self.get_u128_be(index), self.get_u128_be(index + 128 / 8)) + } + fn get_address(&self, index: usize) -> CanonicalAddr { + // 32 bytes are reserved for addresses, but only the last 20 bytes are taken by the actual address + CanonicalAddr::from(&self[index + 32 - 20..index + 32]) + } + fn get_bytes32(&self, index: usize) -> &[u8] { + &self[index..index + 32] + } + + fn get_bytes(&self, index: usize, bytes: usize) -> &[u8] { + &self[index..index + bytes] + } + + fn get_const_bytes(&self, index: usize) -> [u8; N] { + let mut bytes: [u8; N] = [0; N]; + bytes.copy_from_slice(&self[index..index + N]); + bytes + } +} + +/// Left-pad a 20 byte address with 0s +pub fn extend_address_to_32(addr: &CanonicalAddr) -> Vec { + extend_address_to_32_array(addr).to_vec() +} + +pub fn extend_address_to_32_array(addr: &CanonicalAddr) -> [u8; 32] { + let mut v: Vec = vec![0; 12]; + v.extend(addr.as_slice()); + let mut result: [u8; 32] = [0; 32]; + result.copy_from_slice(&v); + result +} + +/// Turn a string into a fixed length array. If the string is shorter than the +/// resulting array, it gets padded with \0s on the right. If longer, it gets +/// truncated. +pub fn string_to_array(s: &str) -> [u8; N] { + let bytes = s.as_bytes(); + let len = usize::min(N, bytes.len()); + let zeros = vec![0; N - len]; + let padded = [bytes[..len].to_vec(), zeros].concat(); + let mut result: [u8; N] = [0; N]; + result.copy_from_slice(&padded); + result +} + +pub fn extend_string_to_32(s: &str) -> Vec { + string_to_array::<32>(s).to_vec() +} + +pub fn get_string_from_32(v: &[u8]) -> String { + let s = String::from_utf8_lossy(v); + s.chars().filter(|c| c != &'\0').collect() +} diff --git a/terra/packages/icco/src/common.rs b/terra/packages/icco/src/common.rs new file mode 100644 index 00000000..47fdb29d --- /dev/null +++ b/terra/packages/icco/src/common.rs @@ -0,0 +1,321 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{Api, CanonicalAddr, DepsMut, StdError, StdResult, Uint128, Uint256}; +use terraswap::asset::{Asset, AssetInfo}; + +use crate::byte_utils::ByteUtils; + +// Chain ID of Terra +pub const CHAIN_ID: u16 = 3; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct SaleTimes { + pub start: u64, + pub end: u64, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct SaleCore { + pub id: Vec, + pub token_address: Vec, + pub token_chain: u16, + pub token_amount: Uint256, + pub min_raise: Uint256, + pub max_raise: Uint256, + pub times: SaleTimes, + pub recipient: Vec, + pub refund_recipient: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct AcceptedToken { + pub chain: u16, + pub address: Vec, + pub conversion_rate: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct SaleStatus { + pub is_sealed: bool, + pub is_aborted: bool, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct Contribution { + pub token_index: u8, + pub contributed: Uint256, // actually Uint128, but will be serialized as Uint256 +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct Allocation { + pub token_index: u8, + pub allocated: Uint256, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct SaleInit { + pub core: SaleCore, + pub accepted_tokens: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct ContributionsSealed { + pub sale_id: Vec, + pub chain_id: u16, + pub contributions: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct SaleSealed { + pub sale_id: Vec, + pub allocations: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct SaleAborted { + pub sale_id: Vec, +} + +impl AcceptedToken { + pub const N_BYTES: usize = 50; + + pub fn deserialize(data: &[u8]) -> StdResult { + let address = data.get_bytes32(0).to_vec(); + if address.len() != 32 { + return Err(StdError::generic_err("address.len() != 32")); + } + + let chain = data.get_u16(32); + let conversion_rate = data.get_u128_be(34); + Ok(AcceptedToken { + chain, + address, + conversion_rate: conversion_rate.into(), + }) + } + + pub fn serialize(&self) -> Vec { + [ + self.chain.to_be_bytes().to_vec(), + self.address.to_vec(), + self.conversion_rate.u128().to_be_bytes().to_vec(), + ] + .concat() + } + + pub fn make_asset_info(&self, api: &dyn Api) -> StdResult { + if self.chain != CHAIN_ID { + return Err(StdError::generic_err("chain != terra")); + } + + let addr = self.address.as_slice(); + match addr[0] { + 1u8 => { + // match first "u" (e.g. uusd) + match addr.iter().position(|&x| x == 117u8) { + Some(idx) => { + let denom = &addr[idx..32]; + match String::from_utf8(denom.into()) { + Ok(denom) => Ok(AssetInfo::NativeToken { denom }), + _ => Err(StdError::generic_err("not valid denom")), + } + } + None => Err(StdError::generic_err("not valid denom")), + } + } + _ => { + let token_address = CanonicalAddr::from(&addr[12..32]); + let humanized = api.addr_humanize(&token_address)?; + Ok(AssetInfo::Token { + contract_addr: humanized.to_string(), + }) + } + } + } + + pub fn make_asset(&self, api: &dyn Api, amount: Uint128) -> StdResult { + Ok(Asset { + info: self.make_asset_info(api)?, + amount: amount, + }) + } +} + +pub fn to_const_bytes32(data: &[u8], index: usize) -> [u8; 32] { + data.get_bytes32(index).get_const_bytes(0) +} + +pub fn to_u256(data: &[u8], index: usize) -> Uint256 { + Uint256::new(to_const_bytes32(data, index)) +} + +impl SaleInit { + pub const PAYLOAD_ID: u8 = 1; + const INDEX_ACCEPTED_TOKENS_START: usize = 226; + + pub fn deserialize(data: &[u8]) -> StdResult { + let sale_id = data.get_bytes32(0).to_vec(); + let token_address = data.get_bytes32(32).to_vec(); + let token_chain = data.get_u16(64); + let token_amount = to_u256(data, 66); + let min_raise = to_u256(data, 98); + let max_raise = to_u256(data, 130); + let start = data.get_u64(162 + 24); // encoded as u256, but we only care about u64 time + let end = data.get_u64(194 + 24); // encoded as u256, but we only care about u64 for time + + let accepted_tokens = + SaleInit::deserialize_tokens(&data[SaleInit::INDEX_ACCEPTED_TOKENS_START..])?; + + let index = SaleInit::INDEX_ACCEPTED_TOKENS_START + + 1 + + AcceptedToken::N_BYTES * accepted_tokens.len(); + + let recipient = data.get_bytes32(index).to_vec(); + let refund_recipient = data.get_bytes32(index + 32).to_vec(); + + Ok(SaleInit { + core: SaleCore { + id: sale_id, + token_address, + token_chain, + token_amount, + min_raise, + max_raise, + times: SaleTimes { start, end }, + recipient, + refund_recipient, + }, + accepted_tokens, + }) + } + + fn deserialize_tokens(data: &[u8]) -> StdResult> { + let n_tokens = data.get_u8(0) as usize; + let expected_length = 1 + AcceptedToken::N_BYTES * n_tokens; + if data.len() <= expected_length { + return Err(StdError::generic_err("data.len() < expected_length")); + } + + let mut tokens: Vec = Vec::with_capacity(n_tokens); + for i in 0..n_tokens { + let start = 1 + AcceptedToken::N_BYTES * i; + let end = 1 + AcceptedToken::N_BYTES * (i + 1); + tokens.push(AcceptedToken::deserialize(&data[start..end])?); + } + Ok(tokens) + } +} + +impl ContributionsSealed { + pub const PAYLOAD_ID: u8 = 2; + + pub fn new(sale_id: &[u8], chain_id: u16) -> Self { + ContributionsSealed { + sale_id: sale_id.to_vec(), + chain_id, + contributions: Vec::new(), + } + } + + pub fn add_contribution(&mut self, token_index: u8, contributed: Uint128) -> StdResult { + // limit to len 255 + if self.contributions.len() >= 256 { + return Err(StdError::generic_err("cannot exceed length 256")); + } + + let contributed = Uint256::from(contributed.u128()); + self.contributions.push(Contribution { + token_index, + contributed, + }); + Ok(self.contributions.len()) + } + /* + pub fn deserialize(data: &Vec) -> StdResult { + let data = data.as_slice(); + let payload_id = data.get_u8(0); + + if payload_id != ContributionsSealed::PAYLOAD_ID { + return Err(StdError::generic_err( + "payload_id != ContributionsSealed::PAYLOAD_ID" + )); + } + let sale_id = data.get_bytes32(1).to_vec(); + let chain_id = data.get_u16(33); + + // TODO: deserialize + let contributions: Vec = Vec::new(); + Ok(ContributionsSealed { + sale_id, + chain_id, + contributions, + }) + } + */ + pub fn serialize(&self) -> Vec { + [ + ContributionsSealed::PAYLOAD_ID.to_be_bytes().to_vec(), + self.sale_id.to_vec(), + self.chain_id.to_be_bytes().to_vec(), + self.serialize_contributions(), + ] + .concat() + } + + fn serialize_contributions(&self) -> Vec { + // TODO + Vec::new() + } +} + +impl SaleSealed { + pub const PAYLOAD_ID: u8 = 3; + + pub fn add_allocation(&mut self, token_index: u8, allocated: Uint256) -> StdResult { + self.allocations.push(Allocation { + token_index, + allocated, + }); + Ok(token_index) + } + pub fn deserialize(data: &[u8]) -> StdResult { + let sale_id = data.get_bytes32(0).to_vec(); + + let allocations = SaleSealed::deserialize_allocations(&data[32..]); + Ok(SaleSealed { + sale_id, + allocations, + }) + } + + pub fn deserialize_allocations(data: &[u8]) -> Vec { + // TODO + Vec::new() + } + /* + pub fn serialize(&self) -> Vec { + [ + SaleSealed::PAYLOAD_IDto_be_bytes().to_vec(), + self.sale_id.clone(), + // TODO: serialize allocations + ] + .concat() + } + */ +} + +impl SaleAborted { + pub const PAYLOAD_ID: u8 = 4; + + pub fn deserialize(data: &[u8]) -> StdResult { + Ok(SaleAborted { + sale_id: data.get_bytes32(0).to_vec(), + }) + } + /* + pub fn serialize(&self) -> Vec { + [self.payload_id.to_be_bytes().to_vec(), self.sale_id.clone()].concat() + } + */ +} diff --git a/terra/packages/icco/src/lib.rs b/terra/packages/icco/src/lib.rs new file mode 100644 index 00000000..737e4e04 --- /dev/null +++ b/terra/packages/icco/src/lib.rs @@ -0,0 +1,5 @@ +#[cfg(test)] +extern crate lazy_static; + +pub mod byte_utils; +pub mod common;