From a944db470206a83488018423bd329f66c8378e09 Mon Sep 17 00:00:00 2001 From: Hossein Zoda Date: Mon, 11 May 2026 23:00:13 +0300 Subject: [PATCH] ido better error reporting --- src/db/ido/mod.rs | 659 +++++++++++++++++++++++++++++----------------- 1 file changed, 424 insertions(+), 235 deletions(-) diff --git a/src/db/ido/mod.rs b/src/db/ido/mod.rs index 78f1473..6cb1327 100644 --- a/src/db/ido/mod.rs +++ b/src/db/ido/mod.rs @@ -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 = LazyLock::new(|| BigInt::from(20_000_00 static MAX_PLP_SHARE: LazyLock = LazyLock::new(|| BigInt::from(80_000_000i64)); // 80% static MAX_PLP_ANNUAL_DISCOUNT: LazyLock = LazyLock::new(|| BigInt::from(10_000_000i64)); // 10% static MIN_PLP_ANNUAL_DISCOUNT: LazyLock = LazyLock::new(|| BigInt::from(0i64)); // 0% +static MIN_PLP_AFTER_DISCOUNT: LazyLock = LazyLock::new(|| BigInt::from(5_000_000i64)); // 5% static ENTRY_EXECUTION_FEE: LazyLock = LazyLock::new(|| BigInt::from(10000i64)); // 10k sats static IDO_CREATE_EXECUTION_FEE: LazyLock = 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> { if bcmr_data.len() == 0 || @@ -910,6 +915,35 @@ fn parse_ipfs_bcmr_with_placeholder( })) } +fn serialize_ipfs_bcmr_with_placeholder( + input: &Option, +) -> Vec { + 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> { + deserialize_ipfs_bcmr_with_placeholder( + &extract_data_from_storage_script_with_data_and_size(script)? + ) +} + struct IdoContext { preinit_txid: Vec, init_txid: Option>, @@ -923,22 +957,20 @@ struct IdoContext { offering_token_id: Option>, } -async fn create_ido_context_from_preinit(network: Option, tx: &Transaction) -> Result { - 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, invalid_ido_reasons: &mut Vec) -> Option { // 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, 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 = 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, 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, + state: Option, + is_valid_ido: bool, + offered_token_is_in_supply: bool, + offered_token_id: Option>, +} + +fn parse_ido_preinit_tx(network: Option, tx: &Transaction, errors: &mut Vec, invalid_ido_reasons: &mut Vec) -> 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; + // 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> = 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> = 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, tx: &Transaction, errors: &mut Vec, invalid_ido_reasons: &mut Vec) -> Result { + 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 = Vec::new(); + let mut invalid_ido_reasons: Vec = 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 = Vec::new(); + let mut _invalid_ido_reasons: Vec = Vec::new(); + let mut context = create_ido_context_from_preinit(network, preinit_tx, &mut _errors, &mut _invalid_ido_reasons)?; let mut updates: Vec = 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"))?;