Refactor
This commit is contained in:
parent
67b134b933
commit
db2870a0d3
|
@ -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",
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
[workspace]
|
||||
members = ["contracts/icco-contributor"]
|
||||
members = [
|
||||
"packages/*",
|
||||
"contracts/icco-contributor"
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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<Response> {
|
||||
Ok(Response::new())
|
||||
|
@ -27,14 +24,15 @@ pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Respons
|
|||
pub fn instantiate(
|
||||
deps: DepsMut,
|
||||
_env: Env,
|
||||
_info: MessageInfo,
|
||||
info: MessageInfo,
|
||||
msg: InstantiateMsg,
|
||||
) -> StdResult<Response> {
|
||||
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)?;
|
||||
|
||||
|
|
|
@ -47,6 +47,12 @@ pub enum ContributorError {
|
|||
|
||||
#[error("WrongChain")]
|
||||
WrongChain,
|
||||
|
||||
#[error("NonexistentToken")]
|
||||
NonexistentToken,
|
||||
|
||||
#[error("NonexistentDenom")]
|
||||
NonexistentDenom,
|
||||
}
|
||||
|
||||
impl ContributorError {
|
||||
|
|
|
@ -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<Response> {
|
||||
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<Response> {
|
||||
// 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<Response> {
|
||||
// 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<Response> {
|
||||
// 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<ParsedVAA>
|
|||
}))?;
|
||||
Ok(vaa)
|
||||
}
|
||||
|
||||
/*
|
||||
fn query_cw20_balance(
|
||||
deps: Deps,
|
||||
contract_addr: String,
|
||||
wallet_addr: String,
|
||||
) -> StdResult<BalanceResponse> {
|
||||
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)
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -6,7 +6,6 @@ mod error;
|
|||
mod execute;
|
||||
mod msg;
|
||||
mod query;
|
||||
mod shared;
|
||||
mod state;
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -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<u8>,
|
||||
|
@ -115,7 +114,7 @@ pub struct AcceptedTokenResponse {
|
|||
pub struct TotalContributionResponse {
|
||||
pub id: Vec<u8>,
|
||||
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<u8>,
|
||||
pub token_index: u8,
|
||||
pub amount: Uint256,
|
||||
pub amount: Uint128,
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ pub fn query_sale_registry(deps: Deps, sale_id: &Binary) -> StdResult<SaleRegist
|
|||
token_chain: sale.token_chain,
|
||||
token_amount: sale.token_amount,
|
||||
min_raise: sale.min_raise,
|
||||
max_raise: sale.max_raise,
|
||||
sale_start: sale.times.start,
|
||||
sale_end: sale.times.end,
|
||||
recipient: sale.recipient,
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use cosmwasm_std::{StdError, StdResult, Uint128, Uint256};
|
||||
use cosmwasm_std::{Api, CanonicalAddr, DepsMut, StdError, StdResult, Uint128, Uint256};
|
||||
use terraswap::asset::{Asset, AssetInfo};
|
||||
|
||||
use wormhole::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,
|
||||
|
@ -18,28 +22,12 @@ pub struct SaleCore {
|
|||
pub token_chain: u16,
|
||||
pub token_amount: Uint256,
|
||||
pub min_raise: Uint256,
|
||||
pub max_raise: Uint256,
|
||||
pub times: SaleTimes,
|
||||
pub recipient: Vec<u8>,
|
||||
pub refund_recipient: Vec<u8>,
|
||||
}
|
||||
|
||||
/*
|
||||
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<Self> {
|
||||
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<AssetInfo> {
|
||||
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<Asset> {
|
||||
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<Self> {
|
||||
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<usize> {
|
||||
pub fn add_contribution(&mut self, token_index: u8, contributed: Uint128) -> StdResult<usize> {
|
||||
// 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,
|
||||
|
|
|
@ -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<u8>,
|
||||
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<Config> = Item::new("config");
|
||||
pub const SALES: Map<SaleId, SaleCore> = Map::new("sales");
|
||||
pub const SALE_STATUSES: Map<SaleId, SaleStatus> = Map::new("sale_statuses");
|
||||
pub const SALE_TIMES: Map<SaleId, SaleTimes> = Map::new("sale_times");
|
||||
pub const ACCEPTED_TOKENS: Map<(SaleId, U8Key), AcceptedToken> = Map::new("accepted_tokens");
|
||||
pub const TOTAL_CONTRIBUTIONS: Map<TokenIndexKey, Uint256> = Map::new("total_contributions");
|
||||
pub const TOTAL_ALLOCATIONS: Map<TokenIndexKey, Uint256> = Map::new("total_allocations");
|
||||
//const BUYER_STATUS: Map<BuyerTokenIndexKey, BuyerStatus> = Map::new("buyer_statuses");
|
||||
pub const ACCEPTED_ASSETS: Map<TokenIndexKey, AssetInfo> = Map::new("accepted_tokens");
|
||||
pub const ACCEPTED_TOKENS: Map<TokenIndexKey, AcceptedToken> = Map::new("accepted_tokens");
|
||||
pub const TOTAL_CONTRIBUTIONS: Map<TokenIndexKey, Uint128> = Map::new("total_contributions");
|
||||
pub const TOTAL_ALLOCATIONS: Map<TokenIndexKey, Uint128> = Map::new("total_allocations");
|
||||
pub const BUYER_STATUSES: Map<BuyerTokenIndexKey, BuyerStatus> = Map::new("buyer_statuses");
|
||||
//pub const USER_ACTIONS: Map<SaleId, u8> = 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<Uint256> {
|
||||
) -> StdResult<Uint128> {
|
||||
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<Uint256> {
|
||||
) -> StdResult<Uint128> {
|
||||
TOTAL_ALLOCATIONS.load(storage, (sale_id, token_index.into()))
|
||||
}
|
||||
|
||||
|
|
|
@ -26,20 +26,6 @@ pub fn mock_dependencies(
|
|||
|
||||
pub struct WasmMockQuerier {
|
||||
base: MockQuerier<Empty>,
|
||||
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<Empty>) -> Self {
|
||||
WasmMockQuerier {
|
||||
base,
|
||||
minter_querier: MinterQuerier::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_anc_minter(&mut self, minter: String) {
|
||||
self.minter_querier = MinterQuerier::new(minter);
|
||||
WasmMockQuerier { base }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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"
|
|
@ -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<const N: usize>(&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<const N: usize>(&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<u8> {
|
||||
extend_address_to_32_array(addr).to_vec()
|
||||
}
|
||||
|
||||
pub fn extend_address_to_32_array(addr: &CanonicalAddr) -> [u8; 32] {
|
||||
let mut v: Vec<u8> = 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<const N: usize>(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<u8> {
|
||||
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()
|
||||
}
|
|
@ -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<u8>,
|
||||
pub token_address: Vec<u8>,
|
||||
pub token_chain: u16,
|
||||
pub token_amount: Uint256,
|
||||
pub min_raise: Uint256,
|
||||
pub max_raise: Uint256,
|
||||
pub times: SaleTimes,
|
||||
pub recipient: Vec<u8>,
|
||||
pub refund_recipient: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct AcceptedToken {
|
||||
pub chain: u16,
|
||||
pub address: Vec<u8>,
|
||||
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<AcceptedToken>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct ContributionsSealed {
|
||||
pub sale_id: Vec<u8>,
|
||||
pub chain_id: u16,
|
||||
pub contributions: Vec<Contribution>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct SaleSealed {
|
||||
pub sale_id: Vec<u8>,
|
||||
pub allocations: Vec<Allocation>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct SaleAborted {
|
||||
pub sale_id: Vec<u8>,
|
||||
}
|
||||
|
||||
impl AcceptedToken {
|
||||
pub const N_BYTES: usize = 50;
|
||||
|
||||
pub fn deserialize(data: &[u8]) -> StdResult<Self> {
|
||||
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<u8> {
|
||||
[
|
||||
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<AssetInfo> {
|
||||
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<Asset> {
|
||||
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<Self> {
|
||||
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<Vec<AcceptedToken>> {
|
||||
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<AcceptedToken> = 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<usize> {
|
||||
// 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<u8>) -> StdResult<Self> {
|
||||
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<Contribution> = Vec::new();
|
||||
Ok(ContributionsSealed {
|
||||
sale_id,
|
||||
chain_id,
|
||||
contributions,
|
||||
})
|
||||
}
|
||||
*/
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
[
|
||||
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<u8> {
|
||||
// TODO
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl SaleSealed {
|
||||
pub const PAYLOAD_ID: u8 = 3;
|
||||
|
||||
pub fn add_allocation(&mut self, token_index: u8, allocated: Uint256) -> StdResult<u8> {
|
||||
self.allocations.push(Allocation {
|
||||
token_index,
|
||||
allocated,
|
||||
});
|
||||
Ok(token_index)
|
||||
}
|
||||
pub fn deserialize(data: &[u8]) -> StdResult<Self> {
|
||||
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<Allocation> {
|
||||
// TODO
|
||||
Vec::new()
|
||||
}
|
||||
/*
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
[
|
||||
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<Self> {
|
||||
Ok(SaleAborted {
|
||||
sale_id: data.get_bytes32(0).to_vec(),
|
||||
})
|
||||
}
|
||||
/*
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
[self.payload_id.to_be_bytes().to_vec(), self.sale_id.clone()].concat()
|
||||
}
|
||||
*/
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
#[cfg(test)]
|
||||
extern crate lazy_static;
|
||||
|
||||
pub mod byte_utils;
|
||||
pub mod common;
|
Loading…
Reference in New Issue