ido better error reporting
This commit is contained in:
parent
f8ddac6ec3
commit
a944db4702
1 changed files with 424 additions and 235 deletions
|
|
@ -17,6 +17,7 @@ use std::sync::LazyLock;
|
|||
use std::collections::{HashMap};
|
||||
use sqlx::{Row, SqlitePool};
|
||||
use crate::db::blob::{ToBlob, blob_to_display_hex};
|
||||
use anyhow::Error;
|
||||
|
||||
const BCMR_SIGNATURE: &[u8] = &[
|
||||
0x6a, 0x04, 0x42, 0x43, 0x4d, 0x52, 0x20,
|
||||
|
|
@ -50,6 +51,7 @@ static MIN_PLP_SHARE: LazyLock<BigInt> = LazyLock::new(|| BigInt::from(20_000_00
|
|||
static MAX_PLP_SHARE: LazyLock<BigInt> = LazyLock::new(|| BigInt::from(80_000_000i64)); // 80%
|
||||
static MAX_PLP_ANNUAL_DISCOUNT: LazyLock<BigInt> = LazyLock::new(|| BigInt::from(10_000_000i64)); // 10%
|
||||
static MIN_PLP_ANNUAL_DISCOUNT: LazyLock<BigInt> = LazyLock::new(|| BigInt::from(0i64)); // 0%
|
||||
static MIN_PLP_AFTER_DISCOUNT: LazyLock<BigInt> = LazyLock::new(|| BigInt::from(5_000_000i64)); // 5%
|
||||
|
||||
static ENTRY_EXECUTION_FEE: LazyLock<BigInt> = LazyLock::new(|| BigInt::from(10000i64)); // 10k sats
|
||||
static IDO_CREATE_EXECUTION_FEE: LazyLock<BigInt> = LazyLock::new(|| BigInt::from(30000i64)); // 30k sats
|
||||
|
|
@ -280,14 +282,17 @@ fn build_storage_script(governingOutpointIndex: u32) -> Script {
|
|||
Script::from(bytes)
|
||||
}
|
||||
|
||||
fn build_storage_script_with_data(governingOutpointIndex: u32, data: &[u8]) -> Script {
|
||||
fn build_storage_script_with_data_and_size(governingOutpointIndex: u32, data: &[u8]) -> Script {
|
||||
let mut data_and_size = Vec::new();
|
||||
data_and_size.extend_from_slice(data);
|
||||
data_and_size.extend_from_slice(&encode_padded_vm_number(&BigInt::from(data.len()), 2).unwrap());
|
||||
let mut bytes = Builder::new()
|
||||
.push_slice(&bigint_to_vm_number(&BigInt::from(governingOutpointIndex)))
|
||||
.into_script()
|
||||
.to_bytes();
|
||||
bytes.extend_from_slice(&STORAGE_CONTRACT);
|
||||
let suffix = Builder::new()
|
||||
.push_slice(data)
|
||||
.push_slice(&data_and_size)
|
||||
.push_opcode(opcodes::all::OP_DROP)
|
||||
.into_script()
|
||||
.to_bytes();
|
||||
|
|
@ -865,7 +870,7 @@ fn is_preinit_broadcast(
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_ipfs_bcmr_with_placeholder(
|
||||
fn deserialize_ipfs_bcmr_with_placeholder(
|
||||
bcmr_data: &[u8],
|
||||
) -> Result<Option<IpfsBcmrWithPlaceholder>> {
|
||||
if bcmr_data.len() == 0 ||
|
||||
|
|
@ -910,6 +915,35 @@ fn parse_ipfs_bcmr_with_placeholder(
|
|||
}))
|
||||
}
|
||||
|
||||
fn serialize_ipfs_bcmr_with_placeholder(
|
||||
input: &Option<IpfsBcmrWithPlaceholder>,
|
||||
) -> Vec<u8> {
|
||||
match input {
|
||||
Some(value) => {
|
||||
let metadata_bytecode = Builder::new()
|
||||
.push_slice(&value.metadata.registryReplaceCalls)
|
||||
.push_slice(&value.metadata.bcmrUrisReplaceCalls)
|
||||
.into_script()
|
||||
.to_bytes();
|
||||
let mut bcmr_bytecode = Vec::new();
|
||||
bcmr_bytecode.extend_from_slice(&BCMR_SIGNATURE);
|
||||
bcmr_bytecode.extend_from_slice(&value.contentHash);
|
||||
bcmr_bytecode.extend_from_slice(&value.urisBytecode);
|
||||
let mut bytes = Vec::new();
|
||||
bytes.extend_from_slice(&metadata_bytecode);
|
||||
bytes.extend_from_slice(&bcmr_bytecode);
|
||||
bytes
|
||||
},
|
||||
None => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_ipfs_bcmr_with_placeholder_from_p2s_storage(script: &Script) -> Result<Option<IpfsBcmrWithPlaceholder>> {
|
||||
deserialize_ipfs_bcmr_with_placeholder(
|
||||
&extract_data_from_storage_script_with_data_and_size(script)?
|
||||
)
|
||||
}
|
||||
|
||||
struct IdoContext {
|
||||
preinit_txid: Vec<u8>,
|
||||
init_txid: Option<Vec<u8>>,
|
||||
|
|
@ -923,22 +957,20 @@ struct IdoContext {
|
|||
offering_token_id: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
async fn create_ido_context_from_preinit(network: Option<Network>, tx: &Transaction) -> Result<IdoContext> {
|
||||
let platform_fee_nfth = match network {
|
||||
Some(Network::Chipnet) => CHIPNET_PLATFORM_FEE_NFTH.clone(),
|
||||
Some(Network::Bitcoin) => MAINNET_PLATFORM_FEE_NFTH.clone(),
|
||||
_ => MAINNET_PLATFORM_FEE_NFTH.clone(),
|
||||
};
|
||||
let offered_token_is_in_supply;
|
||||
let mut is_valid_ido = true;
|
||||
// verify the ido is created correctly
|
||||
let preinit_parameters: IdoPreInitParameters;
|
||||
struct IdoPreinitParseParamsResult {
|
||||
preinit_parameters: IdoPreInitParameters,
|
||||
offered_token_is_in_supply: bool,
|
||||
is_valid_ido: bool,
|
||||
}
|
||||
|
||||
fn parse_ido_preinit_tx_params(tx: &Transaction, platform_fee_nfth: &[u8], errors: &mut Vec<Error>, invalid_ido_reasons: &mut Vec<Error>) -> Option<IdoPreinitParseParamsResult> {
|
||||
// preinit announcement
|
||||
let annOut = tx.output.get(0); // preinit params
|
||||
if annOut.is_some() {
|
||||
let instructions: Vec<_> = annOut.unwrap().script_pubkey.instructions().collect();
|
||||
if instructions.len() != 3 {
|
||||
return Err(anyhow::anyhow!("should have 3 opcodes"));
|
||||
errors.push(anyhow::anyhow!("should have 3 opcodes"));
|
||||
return None;
|
||||
}
|
||||
// authguard locking bytecode
|
||||
let authguardLockingBytecode;
|
||||
|
|
@ -947,43 +979,51 @@ async fn create_ido_context_from_preinit(network: Option<Network>, tx: &Transact
|
|||
authguardLockingBytecode = data.to_vec();
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!("invalid announcement (3)"));
|
||||
errors.push(anyhow::anyhow!("invalid announcement (3)"));
|
||||
return None;
|
||||
}
|
||||
}
|
||||
// params main
|
||||
match &instructions[1] {
|
||||
Ok(Instruction::PushBytes(data)) => {
|
||||
if data.len() < 185 {
|
||||
return Err(anyhow::anyhow!("Incorrect announcement.mainData size"));
|
||||
errors.push(anyhow::anyhow!("Incorrect announcement.mainData size"));
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut is_valid_ido = true;
|
||||
// preinit_parameters.offeredTokenTotalSupply < BigInt::from(0)
|
||||
offered_token_is_in_supply = vm_number_to_bigint(&data[168..176]) < BigInt::from(0);
|
||||
let offered_token_is_in_supply = vm_number_to_bigint(&data[168..176]) < BigInt::from(0);
|
||||
|
||||
let offeringBcmrStorageOut = tx.output.get(8);
|
||||
if offeringBcmrStorageOut.is_none() {
|
||||
return Err(anyhow::anyhow!("offering bcmr storage not defined!"));
|
||||
errors.push(anyhow::anyhow!("offering bcmr storage not defined!"));
|
||||
return None;
|
||||
}
|
||||
let offeringBcmr = parse_ipfs_bcmr_with_placeholder(
|
||||
&extract_data_from_storage_script_with_data_and_size(
|
||||
&offeringBcmrStorageOut.unwrap().script_pubkey
|
||||
)?
|
||||
)?;
|
||||
|
||||
let offeringBcmrResult = deserialize_ipfs_bcmr_with_placeholder_from_p2s_storage(&offeringBcmrStorageOut.unwrap().script_pubkey);
|
||||
if offeringBcmrResult.is_err() {
|
||||
errors.push(anyhow::anyhow!("offering bcmr deserialize failed, {}", offeringBcmrResult.err().unwrap()));
|
||||
return None;
|
||||
}
|
||||
let offeringBcmr = offeringBcmrResult.unwrap();
|
||||
|
||||
let mut oTokenBcmr: Option<IpfsBcmrWithPlaceholder> = None;
|
||||
if offered_token_is_in_supply {
|
||||
let oTokenBcmrStorageOut = tx.output.get(9);
|
||||
if oTokenBcmrStorageOut.is_none() {
|
||||
return Err(anyhow::anyhow!("oToken bcmr storage not defined!"));
|
||||
errors.push(anyhow::anyhow!("oToken bcmr storage not defined!"));
|
||||
return None;
|
||||
}
|
||||
let oTokenBcmrResult = deserialize_ipfs_bcmr_with_placeholder_from_p2s_storage(&oTokenBcmrStorageOut.unwrap().script_pubkey);
|
||||
if oTokenBcmrResult.is_err() {
|
||||
errors.push(anyhow::anyhow!("oToken bcmr deserialize failed, {}", oTokenBcmrResult.err().unwrap()));
|
||||
return None;
|
||||
} else {
|
||||
oTokenBcmr = oTokenBcmrResult.unwrap();
|
||||
}
|
||||
oTokenBcmr = parse_ipfs_bcmr_with_placeholder(
|
||||
&extract_data_from_storage_script_with_data_and_size(
|
||||
&oTokenBcmrStorageOut.unwrap().script_pubkey
|
||||
)?
|
||||
)?;
|
||||
}
|
||||
|
||||
preinit_parameters = IdoPreInitParameters {
|
||||
let preinit_parameters = IdoPreInitParameters {
|
||||
preInitBcmr: IdoPreInitBcmrParameters {
|
||||
offering: offeringBcmr,
|
||||
oToken: oTokenBcmr,
|
||||
|
|
@ -1019,230 +1059,362 @@ async fn create_ido_context_from_preinit(network: Option<Network>, tx: &Transact
|
|||
|
||||
// enforce limitation (xToken == NATIVE_BCH)
|
||||
if preinit_parameters.offering.offer.xTokenCategory.is_some() {
|
||||
return Err(anyhow::anyhow!("non-null xTokenCategory is not supported"));
|
||||
errors.push(anyhow::anyhow!("non-null xTokenCategory is not supported"));
|
||||
return None;
|
||||
}
|
||||
if !offered_token_is_in_supply {
|
||||
let permanentLiquidityOTokenReserve: BigInt = &preinit_parameters.offeredTokenAmount * &preinit_parameters.permanentLiquidityShareNumerator / &PERMANENT_LIQUIDITY_SHARE_DENOMINATOR.clone();
|
||||
if preinit_parameters.offeredTokenTotalSupply <= &preinit_parameters.offeredTokenAmount + &permanentLiquidityOTokenReserve {
|
||||
info!("ido parse, not a valid ido, preinit_parameters.offeredTokenTotalSupply <= preinit_parameters.offeredTokenAmount + permanentLiquidityOTokenReserve");
|
||||
invalid_ido_reasons.push(anyhow::anyhow!("ido parse, not a valid ido, preinit_parameters.offeredTokenTotalSupply <= preinit_parameters.offeredTokenAmount + permanentLiquidityOTokenReserve"));
|
||||
is_valid_ido = false;
|
||||
}
|
||||
}
|
||||
|
||||
if preinit_parameters.permanentLiquidityShareNumerator < *MIN_PLP_SHARE ||
|
||||
preinit_parameters.permanentLiquidityShareNumerator > *MAX_PLP_SHARE ||
|
||||
preinit_parameters.offering.offer.discountAnnualRateNumerator < *MIN_PLP_ANNUAL_DISCOUNT ||
|
||||
preinit_parameters.offering.offer.discountAnnualRateNumerator > *MAX_PLP_ANNUAL_DISCOUNT ||
|
||||
preinit_parameters.offering.offer.maxDiscountRateNumerator !=
|
||||
preinit_parameters.permanentLiquidityShareNumerator ||
|
||||
preinit_parameters.offeredTokenAmount <= BigInt::from(0) ||
|
||||
preinit_parameters.offering.delphiCategory != DELPHI_TOKEN_ID ||
|
||||
preinit_parameters.offering.platformFeeNFTH != platform_fee_nfth ||
|
||||
preinit_parameters.offering.platformFeeNumerator < *PLATFORM_FEE ||
|
||||
preinit_parameters.offering.executionFee < *ENTRY_EXECUTION_FEE {
|
||||
is_valid_ido = false;
|
||||
}
|
||||
if preinit_parameters.permanentLiquidityShareNumerator < *MIN_PLP_SHARE {
|
||||
invalid_ido_reasons.push(anyhow::anyhow!("preinit_parameters.permanentLiquidityShareNumerator < *MIN_PLP_SHARE"));
|
||||
is_valid_ido = false;
|
||||
}
|
||||
|
||||
if preinit_parameters.permanentLiquidityShareNumerator > *MAX_PLP_SHARE {
|
||||
invalid_ido_reasons.push(anyhow::anyhow!("preinit_parameters.permanentLiquidityShareNumerator > *MAX_PLP_SHARE"));
|
||||
is_valid_ido = false;
|
||||
}
|
||||
if preinit_parameters.offering.offer.discountAnnualRateNumerator < *MIN_PLP_ANNUAL_DISCOUNT {
|
||||
invalid_ido_reasons.push(anyhow::anyhow!("preinit_parameters.offering.offer.discountAnnualRateNumerator > *MAX_PLP_ANNUAL_DISCOUNT"));
|
||||
is_valid_ido = false;
|
||||
}
|
||||
if preinit_parameters.offering.offer.discountAnnualRateNumerator > *MAX_PLP_ANNUAL_DISCOUNT {
|
||||
invalid_ido_reasons.push(anyhow::anyhow!("preinit_parameters.offering.offer.discountAnnualRateNumerator > *MAX_PLP_ANNUAL_DISCOUNT"));
|
||||
is_valid_ido = false;
|
||||
}
|
||||
|
||||
if preinit_parameters.offering.offer.maxDiscountRateNumerator < BigInt::from(0) {
|
||||
invalid_ido_reasons.push(anyhow::anyhow!("preinit_parameters.offering.offer.maxDiscountRateNumerator < BigInt::from(0)"));
|
||||
is_valid_ido = false;
|
||||
}
|
||||
if &preinit_parameters.offering.offer.maxDiscountRateNumerator + &MIN_PLP_AFTER_DISCOUNT.clone() >
|
||||
preinit_parameters.permanentLiquidityShareNumerator {
|
||||
invalid_ido_reasons.push(anyhow::anyhow!("preinit_parameters.offering.offer.maxDiscountRateNumerator + MIN_PLP_AFTER_DISCOUNT > preinit_parameters.permanentLiquidityShareNumerator"));
|
||||
is_valid_ido = false;
|
||||
}
|
||||
if preinit_parameters.offeredTokenAmount <= BigInt::from(0) {
|
||||
invalid_ido_reasons.push(anyhow::anyhow!("preinit_parameters.offeredTokenAmount <= BigInt::from(0)"));
|
||||
is_valid_ido = false;
|
||||
}
|
||||
if preinit_parameters.offering.delphiCategory != DELPHI_TOKEN_ID {
|
||||
invalid_ido_reasons.push(anyhow::anyhow!("preinit_parameters.offering.delphiCategory != DELPHI_TOKEN_ID"));
|
||||
is_valid_ido = false;
|
||||
}
|
||||
if &preinit_parameters.offering.platformFeeNFTH != platform_fee_nfth {
|
||||
invalid_ido_reasons.push(anyhow::anyhow!("preinit_parameters.offering.platformFeeNFTH != platform_fee_nfth"));
|
||||
is_valid_ido = false;
|
||||
}
|
||||
if preinit_parameters.offering.platformFeeNumerator < *PLATFORM_FEE {
|
||||
invalid_ido_reasons.push(anyhow::anyhow!("preinit_parameters.offering.platformFeeNumerator < *PLATFORM_FEE"));
|
||||
is_valid_ido = false;
|
||||
}
|
||||
if preinit_parameters.offering.executionFee < *ENTRY_EXECUTION_FEE {
|
||||
invalid_ido_reasons.push(anyhow::anyhow!("preinit_parameters.offering.executionFee < *ENTRY_EXECUTION_FEE"));
|
||||
is_valid_ido = false;
|
||||
}
|
||||
return Some(IdoPreinitParseParamsResult {
|
||||
preinit_parameters,
|
||||
offered_token_is_in_supply,
|
||||
is_valid_ido,
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!("invalid announcement (2)"));
|
||||
errors.push(anyhow::anyhow!("invalid announcement (2)"));
|
||||
return None;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("announcement output is missing"));
|
||||
errors.push(anyhow::anyhow!("announcement output is missing"));
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
struct IdoPreinitParseResult {
|
||||
parameters: Option<IdoPreInitParameters>,
|
||||
state: Option<IdoPreInitState>,
|
||||
is_valid_ido: bool,
|
||||
offered_token_is_in_supply: bool,
|
||||
offered_token_id: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
fn parse_ido_preinit_tx(network: Option<Network>, tx: &Transaction, errors: &mut Vec<Error>, invalid_ido_reasons: &mut Vec<Error>) -> IdoPreinitParseResult {
|
||||
let platform_fee_nfth = match network {
|
||||
Some(Network::Chipnet) => CHIPNET_PLATFORM_FEE_NFTH.clone(),
|
||||
Some(Network::Bitcoin) => MAINNET_PLATFORM_FEE_NFTH.clone(),
|
||||
_ => MAINNET_PLATFORM_FEE_NFTH.clone(),
|
||||
};
|
||||
let offered_token_is_in_supply: bool;
|
||||
let mut is_valid_ido: bool;
|
||||
let nullable_preinit_parameters: Option<IdoPreInitParameters>;
|
||||
// preinit announcement
|
||||
match parse_ido_preinit_tx_params(tx, &platform_fee_nfth, errors, invalid_ido_reasons) {
|
||||
Some(output) => {
|
||||
offered_token_is_in_supply = output.offered_token_is_in_supply;
|
||||
is_valid_ido = output.is_valid_ido;
|
||||
nullable_preinit_parameters = Some(output.preinit_parameters);
|
||||
}
|
||||
None => {
|
||||
offered_token_is_in_supply = false;
|
||||
is_valid_ido = false;
|
||||
nullable_preinit_parameters = None;
|
||||
}
|
||||
}
|
||||
|
||||
let preInitOut = tx.output.get(1);
|
||||
if preInitOut.is_none() {
|
||||
return Err(anyhow::anyhow!("preinit output is not defined!"));
|
||||
}
|
||||
let mut authguardNftOut = None;
|
||||
|
||||
// find authguardCategory
|
||||
let mut authguardNftOut = None;
|
||||
if preinit_parameters.authguardNftOutputIndex >= BigInt::from(0) {
|
||||
authguardNftOut = tx.output.get(preinit_parameters.authguardNftOutputIndex.to_usize().ok_or_else(|| anyhow::anyhow!("authguardNftOutputIndex out of range"))?);
|
||||
}
|
||||
|
||||
let preInit_lock_parameters = IdoPreInitLockParameters {
|
||||
preInitBcmr: preinit_parameters.preInitBcmr.clone(),
|
||||
authguardLockingBytecode: preinit_parameters.authguardLockingBytecode.clone(),
|
||||
permanentLiquidityShareNumerator: preinit_parameters.permanentLiquidityShareNumerator.clone(),
|
||||
offeredTokenAmount: preinit_parameters.offeredTokenAmount.clone(),
|
||||
offeredTokenTotalSupply: preinit_parameters.offeredTokenTotalSupply.clone(),
|
||||
};
|
||||
|
||||
let preinit_state = IdoPreInitState {
|
||||
authguardCategory: if authguardNftOut.is_some() && authguardNftOut.unwrap().token.is_some() { authguardNftOut.unwrap().token.as_ref().unwrap().id.to_blob() } else { vec![0; 32] },
|
||||
idoCategory: vec![0; 32],
|
||||
nextTxSetterFlag: BigInt::from(0),
|
||||
oTokenGenerated: if offered_token_is_in_supply { BigInt::from(1) } else { BigInt::from(0) },
|
||||
};
|
||||
if build_p2sh32_script(
|
||||
&build_preinit_bytecode(&preInit_lock_parameters, &preinit_state)
|
||||
) != preInitOut.unwrap().script_pubkey {
|
||||
return Err(anyhow::anyhow!("preInit output bytecode does not match!"));
|
||||
}
|
||||
|
||||
let offeringBytecodeStorageOut = tx.output.get(2);
|
||||
if offeringBytecodeStorageOut.is_none() {
|
||||
return Err(anyhow::anyhow!("offering bytecode storage not defined!"));
|
||||
}
|
||||
if build_p2sh32_script(
|
||||
&build_storage_script_with_data(
|
||||
1,
|
||||
&build_offering_bytecode(
|
||||
&preinit_parameters.offering.offer.maxDiscountRateNumerator,
|
||||
&preinit_parameters.offering.offer.discountAnnualRateNumerator,
|
||||
&preinit_parameters.offering.offer.priceNumerator,
|
||||
&preinit_parameters.offering.offer.minOffer,
|
||||
preinit_parameters.offering.offer.xTokenCategory.as_deref().unwrap_or(&[]),
|
||||
)
|
||||
).to_bytes()
|
||||
) != offeringBytecodeStorageOut.unwrap().script_pubkey {
|
||||
return Err(anyhow::anyhow!("offering bytecode storage does not match!"));
|
||||
}
|
||||
|
||||
let launcherBytecodeStorageOut = tx.output.get(3);
|
||||
if launcherBytecodeStorageOut.is_none() {
|
||||
return Err(anyhow::anyhow!("launcher bytecode storage not defined!"));
|
||||
}
|
||||
if build_p2sh32_script(
|
||||
&build_storage_script_with_data(
|
||||
1,
|
||||
&build_launcher_bytecode(
|
||||
&vec![0u8; 32], // collectorNFTH
|
||||
&preinit_parameters.offering.platformFeeNFTH,
|
||||
&preinit_parameters.offering.platformFeeNumerator,
|
||||
&preinit_parameters.offering.delphiCategory,
|
||||
&preinit_parameters.offering.launchConditions.expiresAt,
|
||||
&preinit_parameters.offering.launchConditions.deployThreshold,
|
||||
&preinit_parameters.offering.launchConditions.immediateDeployThreshold,
|
||||
)
|
||||
).to_bytes()
|
||||
) != launcherBytecodeStorageOut.unwrap().script_pubkey {
|
||||
return Err(anyhow::anyhow!("launcher bytecode storage does not match!"));
|
||||
}
|
||||
|
||||
|
||||
let distDeployBytecodeStorageOut = tx.output.get(4);
|
||||
if distDeployBytecodeStorageOut.is_none() {
|
||||
return Err(anyhow::anyhow!("dist deploy bytecode storage not defined!"));
|
||||
}
|
||||
if build_p2sh32_script(
|
||||
&build_storage_script_with_data(
|
||||
1,
|
||||
&DISTRIBUTOR_DEPLOY_CONTRACT,
|
||||
).to_bytes()
|
||||
) != distDeployBytecodeStorageOut.unwrap().script_pubkey {
|
||||
return Err(anyhow::anyhow!("dist deploy bytecode storage does not match!"));
|
||||
}
|
||||
|
||||
let distRefundBytecodeStorageOut = tx.output.get(5);
|
||||
if distRefundBytecodeStorageOut.is_none() {
|
||||
return Err(anyhow::anyhow!("dist refund bytecode storage not defined!"));
|
||||
}
|
||||
if build_p2sh32_script(
|
||||
&build_storage_script_with_data(
|
||||
1,
|
||||
&DISTRIBUTOR_REFUND_CONTRACT,
|
||||
).to_bytes()
|
||||
) != distRefundBytecodeStorageOut.unwrap().script_pubkey {
|
||||
return Err(anyhow::anyhow!("dist refund bytecode storage does not match!"));
|
||||
}
|
||||
|
||||
let offeringInitiatorBytecodeStorageOut = tx.output.get(6);
|
||||
if offeringInitiatorBytecodeStorageOut.is_none() {
|
||||
return Err(anyhow::anyhow!("offering initiator bytecode storage not defined!"));
|
||||
}
|
||||
if build_p2sh32_script(
|
||||
&build_storage_script_with_data(
|
||||
1,
|
||||
&build_partial_offering_initiator_bytecode(
|
||||
&BigInt::from(7i64), // extensionOutpointIndex
|
||||
&BigInt::from(6i64), // tokenStorageOutpointIndex
|
||||
&BigInt::from(5i64), // offeringBcmrStorageOutpointIndex
|
||||
&BigInt::from(4i64), // distRefundOutpointIndex
|
||||
&BigInt::from(3i64), // distDeployOutpointIndex
|
||||
&BigInt::from(2i64), // launcherBytecodeOutpointIndex
|
||||
&BigInt::from(1i64), // offeringBytecodeOutpointIndex
|
||||
&preinit_parameters.offering.executionFee,
|
||||
),
|
||||
).to_bytes()
|
||||
) != offeringInitiatorBytecodeStorageOut.unwrap().script_pubkey {
|
||||
return Err(anyhow::anyhow!("offering initiator bytecode storage does not match!"));
|
||||
}
|
||||
|
||||
let idoInitiatorBytecodeStorageOut = tx.output.get(7);
|
||||
if idoInitiatorBytecodeStorageOut.is_none() {
|
||||
return Err(anyhow::anyhow!("ido initiator bytecode storage not defined!"));
|
||||
}
|
||||
if build_p2sh32_script(
|
||||
&build_storage_script_with_data(
|
||||
1,
|
||||
&build_partial_ido_initiator_bytecode(
|
||||
&build_partial_postlaunch_bytecode(
|
||||
&preinit_parameters.permanentLiquidityShareNumerator,
|
||||
&preinit_parameters.offering.offer.priceNumerator,
|
||||
),
|
||||
&BigInt::from(7i64), // permanentPoolOTokenReserveOutpointIndex
|
||||
&BigInt::from(0i64), // offeringInitiatorOutpointIndex
|
||||
),
|
||||
).to_bytes()
|
||||
) != idoInitiatorBytecodeStorageOut.unwrap().script_pubkey {
|
||||
return Err(anyhow::anyhow!("ido initiator bytecode storage does not match!"));
|
||||
}
|
||||
|
||||
// skip validating offering & oToken bcmr
|
||||
|
||||
let mut offered_token_id: Option<Vec<u8>> = None;
|
||||
|
||||
if offered_token_is_in_supply {
|
||||
// verify offered token storage
|
||||
let offeredTokenStorageOut = tx.output.get(9);
|
||||
if offeredTokenStorageOut.is_none() {
|
||||
// valid = false;
|
||||
return Err(anyhow::anyhow!("offered token supply not found!"));
|
||||
if let Some(params) = nullable_preinit_parameters {
|
||||
match params.authguardNftOutputIndex.to_usize() {
|
||||
Some(index) => {
|
||||
authguardNftOut = tx.output.get(index);
|
||||
if authguardNftOut.is_none() {
|
||||
errors.push(anyhow::anyhow!("authguardNftOutputIndex out of range"));
|
||||
}
|
||||
},
|
||||
_ => errors.push(anyhow::anyhow!("authguardNftOutputIndex out of range")),
|
||||
}
|
||||
let permanentLiquidityOTokenReserve: BigInt = &preinit_parameters.offeredTokenAmount * &preinit_parameters.permanentLiquidityShareNumerator / &PERMANENT_LIQUIDITY_SHARE_DENOMINATOR.clone();
|
||||
let requiredTokens = &preinit_parameters.offeredTokenAmount + permanentLiquidityOTokenReserve;
|
||||
if offeredTokenStorageOut.unwrap().token.is_none() {
|
||||
return Err(anyhow::anyhow!("offered token storage does not contain a token!"));
|
||||
}
|
||||
if build_p2sh32_script(&build_storage_script(1u32).to_bytes()) !=
|
||||
offeredTokenStorageOut.unwrap().script_pubkey {
|
||||
return Err(anyhow::anyhow!("offered token locking bytecode does not match!"));
|
||||
|
||||
let state = IdoPreInitState {
|
||||
authguardCategory: if authguardNftOut.is_some() && authguardNftOut.unwrap().token.is_some() { authguardNftOut.unwrap().token.as_ref().unwrap().id.to_blob() } else { vec![0; 32] },
|
||||
idoCategory: vec![0; 32],
|
||||
nextTxSetterFlag: BigInt::from(0),
|
||||
oTokenGenerated: if offered_token_is_in_supply { BigInt::from(1) } else { BigInt::from(0) },
|
||||
};
|
||||
let preInitOut = tx.output.get(1);
|
||||
if preInitOut.is_none() {
|
||||
errors.push(anyhow::anyhow!("preinit output is not defined!"));
|
||||
} else {
|
||||
let preInit_lock_parameters = IdoPreInitLockParameters {
|
||||
preInitBcmr: params.preInitBcmr.clone(),
|
||||
authguardLockingBytecode: params.authguardLockingBytecode.clone(),
|
||||
permanentLiquidityShareNumerator: params.permanentLiquidityShareNumerator.clone(),
|
||||
offeredTokenAmount: params.offeredTokenAmount.clone(),
|
||||
offeredTokenTotalSupply: params.offeredTokenTotalSupply.clone(),
|
||||
};
|
||||
let bytecode = build_preinit_bytecode(&preInit_lock_parameters, &state);
|
||||
if build_p2sh32_script(&bytecode) != preInitOut.unwrap().script_pubkey {
|
||||
errors.push(anyhow::anyhow!("preInit output bytecode does not match, expected bytecode: {}", hex::encode(bytecode)));
|
||||
}
|
||||
let token_amount = BigInt::from(offeredTokenStorageOut.unwrap().token.as_ref().unwrap().amount);
|
||||
if token_amount < requiredTokens {
|
||||
return Err(anyhow::anyhow!("invalid offered token amount!"));
|
||||
}
|
||||
offered_token_id = Some(offeredTokenStorageOut.unwrap().token.as_ref().unwrap().id.to_blob());
|
||||
}
|
||||
|
||||
// collect preinit fee
|
||||
let mut preinit_paid_fee: u64 = 0;
|
||||
for output in &tx.output {
|
||||
if build_p2sh32_script(
|
||||
&build_p2nfth_script(&platform_fee_nfth).to_bytes()
|
||||
) == output.script_pubkey {
|
||||
preinit_paid_fee += output.value;
|
||||
|
||||
let offeringBytecodeStorageOut = tx.output.get(2);
|
||||
if offeringBytecodeStorageOut.is_none() {
|
||||
errors.push(anyhow::anyhow!("offering bytecode storage not defined!"));
|
||||
} else {
|
||||
let bytecode = build_storage_script_with_data_and_size(
|
||||
1,
|
||||
&build_offering_bytecode(
|
||||
¶ms.offering.offer.maxDiscountRateNumerator,
|
||||
¶ms.offering.offer.discountAnnualRateNumerator,
|
||||
¶ms.offering.offer.priceNumerator,
|
||||
¶ms.offering.offer.minOffer,
|
||||
params.offering.offer.xTokenCategory.as_deref().unwrap_or(&[]),
|
||||
)
|
||||
).to_bytes();
|
||||
if build_p2sh32_script(&bytecode) != offeringBytecodeStorageOut.unwrap().script_pubkey {
|
||||
errors.push(anyhow::anyhow!("offering bytecode storage does not match, expected bytecode: {}", hex::encode(bytecode)));
|
||||
}
|
||||
}
|
||||
|
||||
let launcherBytecodeStorageOut = tx.output.get(3);
|
||||
if launcherBytecodeStorageOut.is_none() {
|
||||
errors.push(anyhow::anyhow!("launcher bytecode storage not defined!"));
|
||||
} else {
|
||||
let bytecode = build_storage_script_with_data_and_size(
|
||||
1,
|
||||
&build_launcher_bytecode(
|
||||
&vec![0u8; 32], // collectorNFTH
|
||||
¶ms.offering.platformFeeNFTH,
|
||||
¶ms.offering.platformFeeNumerator,
|
||||
¶ms.offering.delphiCategory,
|
||||
¶ms.offering.launchConditions.expiresAt,
|
||||
¶ms.offering.launchConditions.deployThreshold,
|
||||
¶ms.offering.launchConditions.immediateDeployThreshold,
|
||||
)
|
||||
).to_bytes();
|
||||
if build_p2sh32_script(&bytecode) != launcherBytecodeStorageOut.unwrap().script_pubkey {
|
||||
errors.push(anyhow::anyhow!("launcher bytecode storage does not match, expected bytecode: {}", hex::encode(bytecode)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let distDeployBytecodeStorageOut = tx.output.get(4);
|
||||
if distDeployBytecodeStorageOut.is_none() {
|
||||
errors.push(anyhow::anyhow!("dist deploy bytecode storage not defined!"));
|
||||
} else {
|
||||
let bytecode = build_storage_script_with_data_and_size(
|
||||
1,
|
||||
&DISTRIBUTOR_DEPLOY_CONTRACT,
|
||||
).to_bytes();
|
||||
if build_p2sh32_script(&bytecode) != distDeployBytecodeStorageOut.unwrap().script_pubkey {
|
||||
errors.push(anyhow::anyhow!("dist deploy bytecode storage does not match, expected bytecode: {}", hex::encode(bytecode)));
|
||||
}
|
||||
}
|
||||
|
||||
let distRefundBytecodeStorageOut = tx.output.get(5);
|
||||
if distRefundBytecodeStorageOut.is_none() {
|
||||
errors.push(anyhow::anyhow!("dist refund bytecode storage not defined!"));
|
||||
} else {
|
||||
let bytecode = build_storage_script_with_data_and_size(
|
||||
1,
|
||||
&DISTRIBUTOR_REFUND_CONTRACT,
|
||||
).to_bytes();
|
||||
if build_p2sh32_script(&bytecode) != distRefundBytecodeStorageOut.unwrap().script_pubkey {
|
||||
errors.push(anyhow::anyhow!("dist refund bytecode storage does not match, expected bytecode: {}", hex::encode(bytecode)));
|
||||
}
|
||||
}
|
||||
|
||||
let offeringInitiatorBytecodeStorageOut = tx.output.get(6);
|
||||
if offeringInitiatorBytecodeStorageOut.is_none() {
|
||||
errors.push(anyhow::anyhow!("offering initiator bytecode storage not defined!"));
|
||||
} else {
|
||||
let bytecode = build_storage_script_with_data_and_size(
|
||||
1,
|
||||
&build_partial_offering_initiator_bytecode(
|
||||
&BigInt::from(7i64), // extensionOutpointIndex
|
||||
&BigInt::from(6i64), // tokenStorageOutpointIndex
|
||||
&BigInt::from(5i64), // offeringBcmrStorageOutpointIndex
|
||||
&BigInt::from(4i64), // distRefundOutpointIndex
|
||||
&BigInt::from(3i64), // distDeployOutpointIndex
|
||||
&BigInt::from(2i64), // launcherBytecodeOutpointIndex
|
||||
&BigInt::from(1i64), // offeringBytecodeOutpointIndex
|
||||
¶ms.offering.executionFee,
|
||||
),
|
||||
).to_bytes();
|
||||
if build_p2sh32_script(&bytecode) != offeringInitiatorBytecodeStorageOut.unwrap().script_pubkey {
|
||||
errors.push(anyhow::anyhow!("offering initiator bytecode storage does not match, expected bytecode: {}", hex::encode(bytecode)));
|
||||
}
|
||||
}
|
||||
|
||||
let idoInitiatorBytecodeStorageOut = tx.output.get(7);
|
||||
if idoInitiatorBytecodeStorageOut.is_none() {
|
||||
errors.push(anyhow::anyhow!("ido initiator bytecode storage not defined!"));
|
||||
} else {
|
||||
let bytecode = build_storage_script_with_data_and_size(
|
||||
1,
|
||||
&build_partial_ido_initiator_bytecode(
|
||||
&build_partial_postlaunch_bytecode(
|
||||
¶ms.permanentLiquidityShareNumerator,
|
||||
¶ms.offering.offer.priceNumerator,
|
||||
),
|
||||
&BigInt::from(7i64), // permanentPoolOTokenReserveOutpointIndex
|
||||
&BigInt::from(0i64), // offeringInitiatorOutpointIndex
|
||||
),
|
||||
).to_bytes();
|
||||
if build_p2sh32_script(&bytecode) != idoInitiatorBytecodeStorageOut.unwrap().script_pubkey {
|
||||
errors.push(anyhow::anyhow!("ido initiator bytecode storage does not match, expected bytecode: {}", hex::encode(bytecode)));
|
||||
}
|
||||
}
|
||||
|
||||
let offeringBcmrStorageOut = tx.output.get(8);
|
||||
if offeringBcmrStorageOut.is_none() {
|
||||
errors.push(anyhow::anyhow!("offered token bcmr storage not found!"));
|
||||
} else {
|
||||
let bytecode = build_storage_script_with_data_and_size(
|
||||
1u32,
|
||||
&serialize_ipfs_bcmr_with_placeholder(¶ms.preInitBcmr.offering),
|
||||
);
|
||||
if bytecode != offeringBcmrStorageOut.unwrap().script_pubkey {
|
||||
errors.push(anyhow::anyhow!("offering bcmr storage locking bytecode does not match, expected bytecode: {}", hex::encode(bytecode)));
|
||||
}
|
||||
}
|
||||
|
||||
let mut offered_token_id: Option<Vec<u8>> = None;
|
||||
|
||||
if offered_token_is_in_supply {
|
||||
// verify offered token storage
|
||||
let offeredTokenStorageOut = tx.output.get(9);
|
||||
if offeredTokenStorageOut.is_none() {
|
||||
errors.push(anyhow::anyhow!("offered token supply not found!"));
|
||||
} else {
|
||||
let permanentLiquidityOTokenReserve: BigInt = ¶ms.offeredTokenAmount * ¶ms.permanentLiquidityShareNumerator / &PERMANENT_LIQUIDITY_SHARE_DENOMINATOR.clone();
|
||||
let requiredTokens = ¶ms.offeredTokenAmount + permanentLiquidityOTokenReserve;
|
||||
if offeredTokenStorageOut.unwrap().token.is_none() {
|
||||
errors.push(anyhow::anyhow!("offered token storage does not contain a token!"));
|
||||
} else {
|
||||
let bytecode = build_storage_script(1u32).to_bytes();
|
||||
if build_p2sh32_script(&bytecode) != offeredTokenStorageOut.unwrap().script_pubkey {
|
||||
errors.push(anyhow::anyhow!("offered token locking bytecode does not match, expected bytecode: {}", hex::encode(bytecode)));
|
||||
}
|
||||
let token_amount = BigInt::from(offeredTokenStorageOut.unwrap().token.as_ref().unwrap().amount);
|
||||
if token_amount < requiredTokens {
|
||||
errors.push(anyhow::anyhow!("invalid offered token amount!"));
|
||||
}
|
||||
offered_token_id = Some(offeredTokenStorageOut.unwrap().token.as_ref().unwrap().id.to_blob());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let oTokenBcmrStorageOut = tx.output.get(9);
|
||||
if oTokenBcmrStorageOut.is_none() {
|
||||
errors.push(anyhow::anyhow!("offered token bcmr storage not found!"));
|
||||
} else {
|
||||
let bytecode = build_storage_script_with_data_and_size(
|
||||
1u32,
|
||||
&serialize_ipfs_bcmr_with_placeholder(¶ms.preInitBcmr.oToken),
|
||||
);
|
||||
if bytecode != oTokenBcmrStorageOut.unwrap().script_pubkey {
|
||||
errors.push(anyhow::anyhow!("offered token bcmr storage locking bytecode does not match, expected bytecode: {}", hex::encode(bytecode)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collect preinit fee
|
||||
let mut preinit_paid_fee: u64 = 0;
|
||||
for output in &tx.output {
|
||||
if build_p2sh32_script(
|
||||
&build_p2nfth_script(&platform_fee_nfth).to_bytes()
|
||||
) == output.script_pubkey {
|
||||
preinit_paid_fee += output.value;
|
||||
}
|
||||
}
|
||||
if BigInt::from(preinit_paid_fee) < *IDO_CREATE_EXECUTION_FEE {
|
||||
is_valid_ido = false;
|
||||
invalid_ido_reasons.push(anyhow::anyhow!("ido parse, not enough preinit fee paid!"));
|
||||
}
|
||||
IdoPreinitParseResult {
|
||||
parameters: Some(params),
|
||||
state: Some(state),
|
||||
is_valid_ido,
|
||||
offered_token_is_in_supply,
|
||||
offered_token_id,
|
||||
}
|
||||
} else {
|
||||
IdoPreinitParseResult {
|
||||
parameters: None,
|
||||
state: None,
|
||||
is_valid_ido,
|
||||
offered_token_is_in_supply: false,
|
||||
offered_token_id: None,
|
||||
}
|
||||
}
|
||||
if BigInt::from(preinit_paid_fee) < *IDO_CREATE_EXECUTION_FEE {
|
||||
is_valid_ido = false;
|
||||
info!("ido parse, not enough preinit fee paid!");
|
||||
}
|
||||
|
||||
fn create_ido_context_from_preinit(network: Option<Network>, tx: &Transaction, errors: &mut Vec<Error>, invalid_ido_reasons: &mut Vec<Error>) -> Result<IdoContext> {
|
||||
let result = parse_ido_preinit_tx(network, tx, errors, invalid_ido_reasons);
|
||||
if errors.len() > 0 {
|
||||
Err(anyhow::anyhow!("Failed to parse the preinit tx!"))
|
||||
} else {
|
||||
let params = result.parameters.expect("parameters should be defined!");
|
||||
let state = result.state.expect("state should be defined!");
|
||||
Ok(IdoContext {
|
||||
preinit_txid: tx.txid().to_blob(),
|
||||
init_txid: None,
|
||||
launch_txid: None,
|
||||
status: "PREINIT".to_string(),
|
||||
parameters: IdoParameters::PreInit(params),
|
||||
state: IdoState::PreInit(state),
|
||||
is_valid_ido: result.is_valid_ido,
|
||||
offered_token_id: result.offered_token_id,
|
||||
offering_token_id: None,
|
||||
is_token_created_at_preinit: !result.offered_token_is_in_supply,
|
||||
})
|
||||
}
|
||||
Ok(IdoContext {
|
||||
preinit_txid: tx.txid().to_blob(),
|
||||
init_txid: None,
|
||||
launch_txid: None,
|
||||
status: "PREINIT".to_string(),
|
||||
parameters: IdoParameters::PreInit(preinit_parameters),
|
||||
state: IdoState::PreInit(preinit_state),
|
||||
is_valid_ido: is_valid_ido,
|
||||
offered_token_id: offered_token_id,
|
||||
offering_token_id: None,
|
||||
is_token_created_at_preinit: !offered_token_is_in_supply,
|
||||
})
|
||||
}
|
||||
|
||||
async fn on_create_ido(
|
||||
|
|
@ -1251,7 +1423,22 @@ async fn on_create_ido(
|
|||
tx: &Transaction,
|
||||
block_height: i64,
|
||||
) -> Result<()> {
|
||||
match create_ido_context_from_preinit(network, tx).await {
|
||||
let mut errors: Vec<Error> = Vec::new();
|
||||
let mut invalid_ido_reasons: Vec<Error> = Vec::new();
|
||||
let result = create_ido_context_from_preinit(network, tx, &mut errors, &mut invalid_ido_reasons);
|
||||
if errors.len() > 0 {
|
||||
info!("Parse ido preinit failed, txid: {}\nError(s):", hex::encode(tx.txid().to_blob()));
|
||||
for error in errors {
|
||||
info!(" - {}", error);
|
||||
}
|
||||
}
|
||||
if invalid_ido_reasons.len() > 0 {
|
||||
info!("Invalid ido found, preinit_txid: {}\nReason(s):", hex::encode(tx.txid().to_blob()));
|
||||
for reason in invalid_ido_reasons {
|
||||
info!(" - {}", reason);
|
||||
}
|
||||
}
|
||||
match result {
|
||||
Ok(context) => {
|
||||
// create the ido
|
||||
let mut dbtx = pool.begin().await?;
|
||||
|
|
@ -1445,8 +1632,8 @@ fn ido_add_tx(
|
|||
});
|
||||
}
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("expecting PreInitState!"));
|
||||
}
|
||||
return Err(anyhow::anyhow!("expecting PreInitState!"));
|
||||
}
|
||||
match parse_preinit_state_from_tx(tx) {
|
||||
Ok(new_state) => {
|
||||
let ready = is_preinit_state_ready_to_init(&new_state);
|
||||
|
|
@ -1875,7 +2062,9 @@ async fn on_add_ido_tx(
|
|||
}
|
||||
// start from PREINIT
|
||||
let preinit_tx = items_tx_map.get(&ido.preinit_txid).ok_or_else(|| anyhow::anyhow!("ido's entrypoint does not exist in the txchain"))?;
|
||||
let mut context = create_ido_context_from_preinit(network, preinit_tx).await?;
|
||||
let mut _errors: Vec<Error> = Vec::new();
|
||||
let mut _invalid_ido_reasons: Vec<Error> = Vec::new();
|
||||
let mut context = create_ido_context_from_preinit(network, preinit_tx, &mut _errors, &mut _invalid_ido_reasons)?;
|
||||
let mut updates: Vec<IdoUpdate> = Vec::new();
|
||||
for i in 1..new_chain.len() {
|
||||
let item = new_chain.get(i).ok_or_else(|| anyhow::anyhow!("txchain item not found at index"))?;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue