ido better error reporting

This commit is contained in:
Hossein Zoda 2026-05-11 23:00:13 +03:00
parent f8ddac6ec3
commit a944db4702

View file

@ -17,6 +17,7 @@ use std::sync::LazyLock;
use std::collections::{HashMap}; use std::collections::{HashMap};
use sqlx::{Row, SqlitePool}; use sqlx::{Row, SqlitePool};
use crate::db::blob::{ToBlob, blob_to_display_hex}; use crate::db::blob::{ToBlob, blob_to_display_hex};
use anyhow::Error;
const BCMR_SIGNATURE: &[u8] = &[ const BCMR_SIGNATURE: &[u8] = &[
0x6a, 0x04, 0x42, 0x43, 0x4d, 0x52, 0x20, 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_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 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_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 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 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) 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() let mut bytes = Builder::new()
.push_slice(&bigint_to_vm_number(&BigInt::from(governingOutpointIndex))) .push_slice(&bigint_to_vm_number(&BigInt::from(governingOutpointIndex)))
.into_script() .into_script()
.to_bytes(); .to_bytes();
bytes.extend_from_slice(&STORAGE_CONTRACT); bytes.extend_from_slice(&STORAGE_CONTRACT);
let suffix = Builder::new() let suffix = Builder::new()
.push_slice(data) .push_slice(&data_and_size)
.push_opcode(opcodes::all::OP_DROP) .push_opcode(opcodes::all::OP_DROP)
.into_script() .into_script()
.to_bytes(); .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], bcmr_data: &[u8],
) -> Result<Option<IpfsBcmrWithPlaceholder>> { ) -> Result<Option<IpfsBcmrWithPlaceholder>> {
if bcmr_data.len() == 0 || 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 { struct IdoContext {
preinit_txid: Vec<u8>, preinit_txid: Vec<u8>,
init_txid: Option<Vec<u8>>, init_txid: Option<Vec<u8>>,
@ -923,22 +957,20 @@ struct IdoContext {
offering_token_id: Option<Vec<u8>>, offering_token_id: Option<Vec<u8>>,
} }
async fn create_ido_context_from_preinit(network: Option<Network>, tx: &Transaction) -> Result<IdoContext> { struct IdoPreinitParseParamsResult {
let platform_fee_nfth = match network { preinit_parameters: IdoPreInitParameters,
Some(Network::Chipnet) => CHIPNET_PLATFORM_FEE_NFTH.clone(), offered_token_is_in_supply: bool,
Some(Network::Bitcoin) => MAINNET_PLATFORM_FEE_NFTH.clone(), is_valid_ido: bool,
_ => MAINNET_PLATFORM_FEE_NFTH.clone(), }
};
let offered_token_is_in_supply; fn parse_ido_preinit_tx_params(tx: &Transaction, platform_fee_nfth: &[u8], errors: &mut Vec<Error>, invalid_ido_reasons: &mut Vec<Error>) -> Option<IdoPreinitParseParamsResult> {
let mut is_valid_ido = true;
// verify the ido is created correctly
let preinit_parameters: IdoPreInitParameters;
// preinit announcement // preinit announcement
let annOut = tx.output.get(0); // preinit params let annOut = tx.output.get(0); // preinit params
if annOut.is_some() { if annOut.is_some() {
let instructions: Vec<_> = annOut.unwrap().script_pubkey.instructions().collect(); let instructions: Vec<_> = annOut.unwrap().script_pubkey.instructions().collect();
if instructions.len() != 3 { 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 // authguard locking bytecode
let authguardLockingBytecode; let authguardLockingBytecode;
@ -947,43 +979,51 @@ async fn create_ido_context_from_preinit(network: Option<Network>, tx: &Transact
authguardLockingBytecode = data.to_vec(); authguardLockingBytecode = data.to_vec();
} }
_ => { _ => {
return Err(anyhow::anyhow!("invalid announcement (3)")); errors.push(anyhow::anyhow!("invalid announcement (3)"));
return None;
} }
} }
// params main // params main
match &instructions[1] { match &instructions[1] {
Ok(Instruction::PushBytes(data)) => { Ok(Instruction::PushBytes(data)) => {
if data.len() < 185 { 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) // 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); let offeringBcmrStorageOut = tx.output.get(8);
if offeringBcmrStorageOut.is_none() { 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( let offeringBcmrResult = deserialize_ipfs_bcmr_with_placeholder_from_p2s_storage(&offeringBcmrStorageOut.unwrap().script_pubkey);
&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; let mut oTokenBcmr: Option<IpfsBcmrWithPlaceholder> = None;
if offered_token_is_in_supply { if offered_token_is_in_supply {
let oTokenBcmrStorageOut = tx.output.get(9); let oTokenBcmrStorageOut = tx.output.get(9);
if oTokenBcmrStorageOut.is_none() { 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 { preInitBcmr: IdoPreInitBcmrParameters {
offering: offeringBcmr, offering: offeringBcmr,
oToken: oTokenBcmr, oToken: oTokenBcmr,
@ -1019,230 +1059,362 @@ async fn create_ido_context_from_preinit(network: Option<Network>, tx: &Transact
// enforce limitation (xToken == NATIVE_BCH) // enforce limitation (xToken == NATIVE_BCH)
if preinit_parameters.offering.offer.xTokenCategory.is_some() { 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 { if !offered_token_is_in_supply {
let permanentLiquidityOTokenReserve: BigInt = &preinit_parameters.offeredTokenAmount * &preinit_parameters.permanentLiquidityShareNumerator / &PERMANENT_LIQUIDITY_SHARE_DENOMINATOR.clone(); let permanentLiquidityOTokenReserve: BigInt = &preinit_parameters.offeredTokenAmount * &preinit_parameters.permanentLiquidityShareNumerator / &PERMANENT_LIQUIDITY_SHARE_DENOMINATOR.clone();
if preinit_parameters.offeredTokenTotalSupply <= &preinit_parameters.offeredTokenAmount + &permanentLiquidityOTokenReserve { 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; is_valid_ido = false;
} }
} }
if preinit_parameters.permanentLiquidityShareNumerator < *MIN_PLP_SHARE || if preinit_parameters.permanentLiquidityShareNumerator < *MIN_PLP_SHARE {
preinit_parameters.permanentLiquidityShareNumerator > *MAX_PLP_SHARE || invalid_ido_reasons.push(anyhow::anyhow!("preinit_parameters.permanentLiquidityShareNumerator < *MIN_PLP_SHARE"));
preinit_parameters.offering.offer.discountAnnualRateNumerator < *MIN_PLP_ANNUAL_DISCOUNT || is_valid_ido = false;
preinit_parameters.offering.offer.discountAnnualRateNumerator > *MAX_PLP_ANNUAL_DISCOUNT || }
preinit_parameters.offering.offer.maxDiscountRateNumerator !=
preinit_parameters.permanentLiquidityShareNumerator || if preinit_parameters.permanentLiquidityShareNumerator > *MAX_PLP_SHARE {
preinit_parameters.offeredTokenAmount <= BigInt::from(0) || invalid_ido_reasons.push(anyhow::anyhow!("preinit_parameters.permanentLiquidityShareNumerator > *MAX_PLP_SHARE"));
preinit_parameters.offering.delphiCategory != DELPHI_TOKEN_ID || is_valid_ido = false;
preinit_parameters.offering.platformFeeNFTH != platform_fee_nfth || }
preinit_parameters.offering.platformFeeNumerator < *PLATFORM_FEE || if preinit_parameters.offering.offer.discountAnnualRateNumerator < *MIN_PLP_ANNUAL_DISCOUNT {
preinit_parameters.offering.executionFee < *ENTRY_EXECUTION_FEE { invalid_ido_reasons.push(anyhow::anyhow!("preinit_parameters.offering.offer.discountAnnualRateNumerator > *MAX_PLP_ANNUAL_DISCOUNT"));
is_valid_ido = false; 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 { } 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); let mut authguardNftOut = None;
if preInitOut.is_none() {
return Err(anyhow::anyhow!("preinit output is not defined!"));
}
// find authguardCategory // find authguardCategory
let mut authguardNftOut = None; if let Some(params) = nullable_preinit_parameters {
if preinit_parameters.authguardNftOutputIndex >= BigInt::from(0) { match params.authguardNftOutputIndex.to_usize() {
authguardNftOut = tx.output.get(preinit_parameters.authguardNftOutputIndex.to_usize().ok_or_else(|| anyhow::anyhow!("authguardNftOutputIndex out of range"))?); Some(index) => {
} authguardNftOut = tx.output.get(index);
if authguardNftOut.is_none() {
let preInit_lock_parameters = IdoPreInitLockParameters { errors.push(anyhow::anyhow!("authguardNftOutputIndex out of range"));
preInitBcmr: preinit_parameters.preInitBcmr.clone(), }
authguardLockingBytecode: preinit_parameters.authguardLockingBytecode.clone(), },
permanentLiquidityShareNumerator: preinit_parameters.permanentLiquidityShareNumerator.clone(), _ => errors.push(anyhow::anyhow!("authguardNftOutputIndex out of range")),
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!"));
} }
let permanentLiquidityOTokenReserve: BigInt = &preinit_parameters.offeredTokenAmount * &preinit_parameters.permanentLiquidityShareNumerator / &PERMANENT_LIQUIDITY_SHARE_DENOMINATOR.clone();
let requiredTokens = &preinit_parameters.offeredTokenAmount + permanentLiquidityOTokenReserve; let state = IdoPreInitState {
if offeredTokenStorageOut.unwrap().token.is_none() { authguardCategory: if authguardNftOut.is_some() && authguardNftOut.unwrap().token.is_some() { authguardNftOut.unwrap().token.as_ref().unwrap().id.to_blob() } else { vec![0; 32] },
return Err(anyhow::anyhow!("offered token storage does not contain a token!")); idoCategory: vec![0; 32],
} nextTxSetterFlag: BigInt::from(0),
if build_p2sh32_script(&build_storage_script(1u32).to_bytes()) != oTokenGenerated: if offered_token_is_in_supply { BigInt::from(1) } else { BigInt::from(0) },
offeredTokenStorageOut.unwrap().script_pubkey { };
return Err(anyhow::anyhow!("offered token locking bytecode does not match!")); 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; let offeringBytecodeStorageOut = tx.output.get(2);
for output in &tx.output { if offeringBytecodeStorageOut.is_none() {
if build_p2sh32_script( errors.push(anyhow::anyhow!("offering bytecode storage not defined!"));
&build_p2nfth_script(&platform_fee_nfth).to_bytes() } else {
) == output.script_pubkey { let bytecode = build_storage_script_with_data_and_size(
preinit_paid_fee += output.value; 1,
&build_offering_bytecode(
&params.offering.offer.maxDiscountRateNumerator,
&params.offering.offer.discountAnnualRateNumerator,
&params.offering.offer.priceNumerator,
&params.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
&params.offering.platformFeeNFTH,
&params.offering.platformFeeNumerator,
&params.offering.delphiCategory,
&params.offering.launchConditions.expiresAt,
&params.offering.launchConditions.deployThreshold,
&params.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
&params.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(
&params.permanentLiquidityShareNumerator,
&params.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(&params.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 = &params.offeredTokenAmount * &params.permanentLiquidityShareNumerator / &PERMANENT_LIQUIDITY_SHARE_DENOMINATOR.clone();
let requiredTokens = &params.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(&params.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( async fn on_create_ido(
@ -1251,7 +1423,22 @@ async fn on_create_ido(
tx: &Transaction, tx: &Transaction,
block_height: i64, block_height: i64,
) -> Result<()> { ) -> 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) => { Ok(context) => {
// create the ido // create the ido
let mut dbtx = pool.begin().await?; let mut dbtx = pool.begin().await?;
@ -1445,8 +1632,8 @@ fn ido_add_tx(
}); });
} }
} else { } else {
return Err(anyhow::anyhow!("expecting PreInitState!")); return Err(anyhow::anyhow!("expecting PreInitState!"));
} }
match parse_preinit_state_from_tx(tx) { match parse_preinit_state_from_tx(tx) {
Ok(new_state) => { Ok(new_state) => {
let ready = is_preinit_state_ready_to_init(&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 // 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 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(); let mut updates: Vec<IdoUpdate> = Vec::new();
for i in 1..new_chain.len() { for i in 1..new_chain.len() {
let item = new_chain.get(i).ok_or_else(|| anyhow::anyhow!("txchain item not found at index"))?; let item = new_chain.get(i).ok_or_else(|| anyhow::anyhow!("txchain item not found at index"))?;