ido more fixes +two unittests

This commit is contained in:
Hossein Zoda 2026-05-19 22:52:07 +03:00
parent a944db4702
commit c4eaad4cec
2 changed files with 177 additions and 71 deletions

View file

@ -10,7 +10,7 @@ use bitcoin_hashes::sha256d;
//use bitcoin_hashes::hash160;
use bitcoin_hashes::Hash;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use serde_with::{serde_as, DisplayFromStr};
use bitcoincash::blockdata::transaction::Transaction;
use bitcoincash::{Txid, BlockHash, TokenID, Network};
use std::sync::LazyLock;
@ -56,12 +56,22 @@ static MIN_PLP_AFTER_DISCOUNT: LazyLock<BigInt> = LazyLock::new(|| BigInt::from(
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 PLATFORM_FEE: LazyLock<BigInt> = LazyLock::new(|| BigInt::from(10_000_000i64)); // 10%
const DELPHI_TOKEN_ID: &[u8; 32] = &[
0xd0, 0xd4, 0x6f, 0x5c, 0xbd, 0x82, 0x18, 0x8a, 0xce,
0xde, 0x0d, 0x3e, 0x49, 0xc7, 0x57, 0x00, 0xc1, 0x9c,
0xb8, 0x33, 0x1a, 0x30, 0x10, 0x1f, 0x0b, 0xb6, 0xa2,
0x60, 0x06, 0x6a, 0xc9, 0x72
const CHIPNET_DELPHI_TOKEN_ID: &[u8; 32] = &[
0x1b, 0x94, 0x82, 0x40, 0xc9, 0xcf, 0xaa, 0x82, 0x24,
0x38, 0x3d, 0xb1, 0x6e, 0x47, 0x35, 0xef, 0xa0, 0x9c,
0x94, 0x26, 0xd1, 0x71, 0x3d, 0x26, 0x5e, 0x84, 0x9a,
0x8e, 0x91, 0xf2, 0x66, 0x9a
];
const MAINNET_DELPHI_TOKEN_ID: &[u8; 32] = &[
0xbe, 0x0d, 0x0d, 0x83, 0x24, 0xe8, 0xcd, 0xa4, 0x1d,
0x34, 0xb8, 0x5b, 0xd2, 0x03, 0xce, 0x24, 0x82, 0x25,
0x6e, 0xb3, 0x37, 0xa0, 0xad, 0x0f, 0xea, 0x82, 0xc2,
0xdd, 0xd7, 0x30, 0x6c, 0x88
];
// TODO:: set the platform fee nfth
const CHIPNET_PLATFORM_FEE_NFTH: &[u8; 32] = &[
0x07, 0x2d, 0x5f, 0xbe, 0xcf, 0xa1, 0xbc, 0x37,
@ -88,7 +98,7 @@ static DISTRIBUTOR_DEPLOY_CONTRACT: LazyLock<Vec<u8>> = LazyLock::new(|| hex::de
static DISTRIBUTOR_REFUND_CONTRACT: LazyLock<Vec<u8>> = LazyLock::new(|| hex::decode("c0009d00ce01207f7551ce01207f75788851cf517f755f84538851ca51ca82770136940120947f7701207f7551cf517f75760120840100876451cc51c6a26951d100886751d352d09d51d152ce886802aa200120537a7e55797eaa7e01877e51cd8800cf577f77547f75817651a06300cf517f7500cf517f77567f757e788c54807e00cf5b7f77587f757e00cf01137f77587f757e00cf011b7f77587f757e00d28800ce00d18800c700cd88c4529e6352d10088c4539d686755608500cf517f77567f757e00cf5b7f77587f757e00cf01137f77587f757e00cf011b7f77587f757e00d288527900d18800d3009d02aa20012055797e56797eaa7e01877e00cd8852d100885352790120840100876475526876ce54798876cf517f755f845488c4539e6353d10088c4549d6875686d6d7551").unwrap());
static EXECUTION_FEE_PAYOUT_CONTRACT: LazyLock<Vec<u8>> = LazyLock::new(|| hex::decode("c0ce00ce01207f758800cf517f755f84528800cf577f77547f758151a169c0cf517f7701207f7502aa2001207b7e7b7eaa7e01877e52cd8852ccc0c6a2").unwrap());
static OFFERING_INITIATOR_CONTRACT: LazyLock<Vec<u8>> = LazyLock::new(|| hex::decode("c0c9009dc0c85b79c8885a79c9577a9d5979ce827701209d567900a263c0c85b79c8885a79c957799d68c0c800d1788800d2578800d3009d00cd5a7a88c08bc0c878c88876c9547a9d76ca827778ca7853947f77527f75817bca7b53945279947f777c7f75c05293c0c878c88876c9557a9d76ca827778ca7853947f77527f75817bca7b53945279947f777c7f75c05393c0c878c88876c9567a9d76ca827778ca7853947f77527f75815178529302ff00a063755367785293014ba063755268685379ca537a5394537a947b947f7702aa20525352807e5b797e7b7eaa7e01877e54cd8854cc7cc6a26954d10088c05493c0c878c88876c9567a9d76ca827778ca7853947f77527f75815178529302ff00a063755367785293014ba063755268685379ca537a5394537a947b947f7702aa20525352807e5a797e7b7eaa7e01877e55cd8855cc7cc6a26955d100885779d07600a0690100557a7e04000000007e51d28851d15479527e8851d3789d51cd02aa20547aaa7e01877e885153d28853d153798853d3009d53cd02aa20537aaa7e01877e8802aa20525352807e567a7eaa7e01877e52cd8852cc5579c6a26952d1557ace8852d39d52d20088c05593c0c878c88876c9537a9d76c7827778c77853947f77527f75817bc77b53945279947f777c7f7576827700a06376517f75768176014b9f788b547982779f9a635279788b7f77537a757c6b7c6c5279517f757b757c6878016a8764016a537a757c6b7c6c686d67016a776856cd8856d1008857d100876457d101207f75788791696857cd567f75066a0442434d52879169c4589e6358d100876458d101207f75788791696858cd567f75066a0442434d52879169c4599e6359d100876459d101207f75788791696859cd567f75066a0442434d52879169c45a9e635ad10087645ad101207f7578879169685acd567f75066a0442434d52879169c45b9e635bd10087645bd101207f7578879169685bcd567f75066a0442434d52879169c45c9d686868686d7551").unwrap());
static OFFERING_INTEGRATED_TIMELOCKED_P2NFTH_CONTRACT: LazyLock<Vec<u8>> = LazyLock::new(|| hex::decode("c0cf527f7701207f75c0cf01227f77815479ce01207f757b88537acf567f75817600a0699f6978ce7bcf7eaa88c0cf517f77517f7581c0c85279c8887cc99cs").unwrap());
static OFFERING_INTEGRATED_TIMELOCKED_P2NFTH_CONTRACT: LazyLock<Vec<u8>> = LazyLock::new(|| hex::decode("c0cf527f7701207f75c0cf01227f77815479ce01207f757b88537acf567f75817600a0699f6978ce7bcf7eaa88c0cf517f77517f7581c0c85279c8887cc99c").unwrap());
static IDO_INITIATOR_CONTRACT: LazyLock<Vec<u8>> = LazyLock::new(|| hex::decode("c0ce827701209dc0cf827700a0695579827701209dc0c85779c8885679c99dc0c85779c8885679c99d5479529376ca827778ca7853947f77527f75817bca7b53945279947f777c7f7501157f7701207f75c0cec0cf7eaa88547ac852d1827701209d52d300a06902aa20c101147f755479827751807e54797e5379827751807e537a7e01207e7b7e01207e52d17e01207e547a7e587e52d358807e537a7eaa7e01877e57cd8857ccc0c6a26957d1c0ce8857d2c0cf8857d3c0d09d02aa20525752807e7b7eaa7e01877e7658cd8858cc02e803a26958d1008859cd8859cc78c6a26959d178ce8859d278cf8859d37cd09c").unwrap());
static IDO_POSTLAUNCH_CONTRACT: LazyLock<Vec<u8>> = LazyLock::new(|| hex::decode("5879009c63c0009dc0c9009e69c0cdc0c788c0ccc0c6a269c0d1c0ce88c0d3c0d09dc0d2c0cf8802aa20520052807e597a7eaa7e01877ec0c851c88851c9589d7651cd8851cc51c6a26951d151ce8851d351d09dc0c852c88852c9599d52cd8852cc52c6a26952d152ce8852d352d09dc353a06353ce0088c3549d6853d10088c4549d6d6d6d6d51675879519c63c0009dc0c9009dc0cdc0c788c0d1c0ce88c0d3c0d09dc0d2c0cf88c0c852c88852c9529d52cd52c78852cc52c6a26952d152ce8852d352d09d02aa200120c0cec0cf7eaa7e587a7eaa7e01877e00005376c75479876376ce59798763527978d093537a757c6b7c6c6776ce0088686ec6937b757c6776ce008868768bc378a06376c75579876376ce5a798763537978d093547a757c6b7c6b7c6c6c6776ce008868527978c693537a757c6b7c6c6776ce008868768b77c378a06376c75579876376ce5a798763537978d093547a757c6b7c6b7c6c6c6776ce008868527978c693537a757c6b7c6c6776ce008868768b77c378a06376c75579876376ce5a798763537978d093547a757c6b7c6b7c6c6c6776ce008868527978c693537a757c6b7c6c6776ce008868768b77c378a06376c75579876376ce5a798763537978d093547a757c6b7c6b7c6c6c6776ce008868527978c693537a757c6b7c6c6776ce008868768b77c378a06376c75579876376ce5a798763537978d093547a757c6b7c6b7c6c6c6776ce008868527978c693537a757c6b7c6c6776ce008868768b77c378a06376c75579876376ce5a798763537978d093547a757c6b7c6b7c6c6c6776ce008868527978c693537a757c6b7c6c6776ce008868768b77c378a06376c75579876376ce5a798763537978d093547a757c6b7c6b7c6c6c6776ce008868527978c693537a757c6b7c6c6776ce008868768b77c378a06376c75579876376ce5a798763537978d093547a757c6b7c6b7c6c6c6776ce008868527978c693537a757c6b7c6c6776ce008868768b77c3789d6868686868686868c0ccc0c6547a93a269c0c851c88851c9519d51cd51c78851cc51c6a26951d0537a937600a06351d159798851d378a26951d200886751d100886853d10088c4549d6d6d6d6d6d6d5167587a529dc0009dc0c9009d5379827701209d53ce567a8853cf517f755f845588c0c653cf577f77587f7581a269c0c851c88851c9519d53cf5f7f77587f75817600a06351d078a2696851d000a06351ce5679886851cf0088c0c852c88852c9529d52ce567988000053cf517f75608401008763557981537994765679950400e1f505967652d0a06352d07768765679950500e87648179653cf01177f77587f758194760500e8764817955779967802e803a27800a09a6378567a757c6b7c6b7c6b7c6b7c6c6c6c6c765379a16376557a757c6b7c6b7c6b7c6c6c6c675279557a757c6b7c6b7c6b7c6c6c6c68686d6d68c0c651c69352c69352799451d052d093527994537900a0537900a09a6302aa2005746376a914000114807e2b88ac67c0d1c0ce88c25288c0cdc0c788c0c6c0d095c0c6c0cc9490539502e80396c0cc7c94c0d3957ca2687eaa7e01877e00cd8800cc54799d00d15a798800d353799d00d20088096a0653554d4d4f4e14000114807e51cd8851cc009d51d100886700d1008800cd016a8851d1008851cd016a886802aa2001205a7a7e5b7a7eaa7e01877e52cd8852cc7ba2697600a06352d378a26952d158798852d200886752d100886853d10088c4549d6d6d6d6d75516868").unwrap());
@ -99,6 +109,7 @@ static P2NFTH_CONTRACT: LazyLock<Vec<u8>> = LazyLock::new(|| hex::decode("78ce7b
static CASHTOKENS_STUDIO_PAY2CATEGORY_CONTRACT: LazyLock<Vec<u8>> = LazyLock::new(|| hex::decode("51ce8851d0009d6300cdc0c7886851").unwrap());
static REBUILD_IPFS_PLACEHOLDER_BCMR_FOR_GENESIS_WITH_AUTHGUARD_CONTRACT: LazyLock<Vec<u8>> = LazyLock::new(|| hex::decode("0902a9147ca97e01877e57897c6b00c08851d100887c635ab2756d51cd016a88674d0301404142434445464748494a4b4c4d4e4f505152535455565758595a6162636465666768696a6b6c6d6e6f707172737475767778797a303132333435363738392d5f6b007c8253a26365537f7c76011299527f77816c766b7c7f77517f75785c99527f77013f84816c766b7c7f77517f757e785699527f77013f84816c766b7c7f77517f757e7c527f77013f84816c766b7c7f77517f757e7b7c7e7c82539f666882760087636d677d537c94007c807e76011299527f77816c766b7c7f77517f75785c99527f77013f84816c766b7c7f77517f757e785699527f77013f84816c766b7c7f77517f757e7c527f77013f84816c766b7c7f77517f757e7c51937f757e686c7554893a10303132333435363738396162636465667c006b65517f7c5279785f84817f77517f756c7e6b52797c5499817f77517f756c7e6b82009c666d6c5689097f7b827b7c7f777e7e538978a8886b518ac0ce568a65766c537a538a6b74519c66756c766ba804015512207c7e548a01757c7e6b528a6c7c6b65766c537a538a6b74519c66756c6ca87c7e076a0442434d52207c7e51cd886801206c0f51ce8851d0009d6300cdc0c78868517e7e578a00cd8800ce00d18800d2008800d000d38800c600cca26952d10088c45387").unwrap());
#[serde_as]
#[derive(Serialize, Deserialize, Clone)]
pub struct IpfsBcmrWithPlaceholderMetadata {
@ -132,19 +143,27 @@ pub struct IdoPreInitLockParameters {
offeredTokenTotalSupply: BigInt,
}
#[serde_as]
#[derive(Serialize, Deserialize, Clone)]
pub struct IdoParametersOfferingLaunchConditions {
#[serde_as(as = "DisplayFromStr")]
expiresAt: BigInt,
#[serde_as(as = "DisplayFromStr")]
deployThreshold: BigInt,
#[serde_as(as = "DisplayFromStr")]
immediateDeployThreshold: BigInt,
}
#[serde_as]
#[derive(Serialize, Deserialize, Clone)]
pub struct IdoParametersOfferingOffer {
#[serde_as(as = "DisplayFromStr")]
maxDiscountRateNumerator: BigInt,
#[serde_as(as = "DisplayFromStr")]
discountAnnualRateNumerator: BigInt,
#[serde_as(as = "DisplayFromStr")]
priceNumerator: BigInt,
#[serde_as(as = "DisplayFromStr")]
minOffer: BigInt,
#[serde_as(as = "Option<serde_with::hex::Hex>")]
xTokenCategory: Option<Vec<u8>>,
@ -153,6 +172,7 @@ pub struct IdoParametersOfferingOffer {
#[serde_as]
#[derive(Serialize, Deserialize, Clone)]
pub struct IdoParametersOffering {
#[serde_as(as = "DisplayFromStr")]
platformFeeNumerator: BigInt,
#[serde_as(as = "serde_with::hex::Hex")]
platformFeeNFTH: Vec<u8>,
@ -160,6 +180,7 @@ pub struct IdoParametersOffering {
delphiCategory: Vec<u8>,
launchConditions: IdoParametersOfferingLaunchConditions,
offer: IdoParametersOfferingOffer,
#[serde_as(as = "DisplayFromStr")]
executionFee: BigInt,
}
@ -169,9 +190,13 @@ pub struct IdoPreInitParameters {
preInitBcmr: IdoPreInitBcmrParameters,
#[serde_as(as = "serde_with::hex::Hex")]
authguardLockingBytecode: Vec<u8>,
#[serde_as(as = "DisplayFromStr")]
authguardNftOutputIndex: BigInt,
#[serde_as(as = "DisplayFromStr")]
permanentLiquidityShareNumerator: BigInt,
#[serde_as(as = "DisplayFromStr")]
offeredTokenTotalSupply: BigInt,
#[serde_as(as = "DisplayFromStr")]
offeredTokenAmount: BigInt,
offering: IdoParametersOffering,
}
@ -182,7 +207,9 @@ pub struct IdoActiveParameters {
preInitBcmr: IdoPreInitBcmrParameters,
#[serde_as(as = "serde_with::hex::Hex")]
collectorNFTH: Vec<u8>,
#[serde_as(as = "DisplayFromStr")]
permanentLiquidityShareNumerator: BigInt,
#[serde_as(as = "DisplayFromStr")]
offeredTokenAmount: BigInt,
offering: IdoParametersOffering,
}
@ -201,48 +228,74 @@ pub struct IdoPreInitState {
authguardCategory: Vec<u8>,
#[serde_as(as = "serde_with::hex::Hex")]
idoCategory: Vec<u8>,
#[serde_as(as = "DisplayFromStr")]
nextTxSetterFlag: BigInt,
#[serde_as(as = "DisplayFromStr")]
oTokenGenerated: BigInt,
}
#[serde_as]
#[derive(Serialize, Deserialize, Clone)]
pub struct IdoActiveState {
#[serde_as(as = "DisplayFromStr")]
counter: BigInt,
}
#[serde_as]
#[derive(Serialize, Deserialize, Clone)]
pub struct IdoDistributingState {
isRefund: bool,
#[serde_as(as = "DisplayFromStr")]
timestamp: BigInt,
#[serde_as(as = "DisplayFromStr")]
counter: BigInt,
#[serde_as(as = "DisplayFromStr")]
earnedAmount: BigInt,
#[serde_as(as = "DisplayFromStr")]
refundAmount: BigInt,
#[serde_as(as = "DisplayFromStr")]
discountAmount: BigInt,
#[serde_as(as = "DisplayFromStr")]
platformEarnedAmount: BigInt,
}
#[serde_as]
#[derive(Serialize, Deserialize, Clone)]
pub struct IdoPostLaunchState {
isRefund: bool,
#[serde_as(as = "DisplayFromStr")]
timestamp: BigInt,
#[serde_as(as = "DisplayFromStr")]
earnedAmount: BigInt,
#[serde_as(as = "DisplayFromStr")]
refundAmount: BigInt,
#[serde_as(as = "DisplayFromStr")]
discountAmount: BigInt,
#[serde_as(as = "DisplayFromStr")]
platformEarnedAmount: BigInt,
}
#[serde_as]
#[derive(Serialize, Deserialize, Clone)]
pub struct IdoPermanentPoolV0 {
#[serde_as(as = "DisplayFromStr")]
tokenAmount: BigInt,
#[serde_as(as = "DisplayFromStr")]
satoshiAmount: BigInt,
}
#[serde_as]
#[derive(Serialize, Deserialize, Clone)]
pub struct IdoDistributedState {
permanentPool: Option<IdoPermanentPoolV0>,
#[serde_as(as = "DisplayFromStr")]
refundAmount: BigInt,
#[serde_as(as = "DisplayFromStr")]
discountAmount: BigInt,
#[serde_as(as = "DisplayFromStr")]
collectorEarnedAmount: BigInt,
#[serde_as(as = "DisplayFromStr")]
platformEarnedAmount: BigInt,
}
@ -273,9 +326,8 @@ fn build_p2nfth_script(nfthash: &[u8]) -> Script {
}
fn build_storage_script(governingOutpointIndex: u32) -> Script {
let encoded = encode_padded_vm_number(&BigInt::from(governingOutpointIndex), 2).unwrap();
let mut bytes = Builder::new()
.push_slice(&encoded)
.push_slice(&encode_padded_vm_number(&BigInt::from(governingOutpointIndex), 2).unwrap())
.into_script()
.to_bytes();
bytes.extend_from_slice(&STORAGE_CONTRACT);
@ -287,7 +339,7 @@ fn build_storage_script_with_data_and_size(governingOutpointIndex: u32, data: &[
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)))
.push_slice(&encode_padded_vm_number(&BigInt::from(governingOutpointIndex), 2).unwrap())
.into_script()
.to_bytes();
bytes.extend_from_slice(&STORAGE_CONTRACT);
@ -358,6 +410,7 @@ fn build_offering_bytecode(maxDiscountRate: &BigInt, discountAnnualRate: &BigInt
}
fn build_launcher_bytecode(collectorNFTH: &[u8], platformFeeNFTH: &[u8], platformFee: &BigInt, delphiCategory: &[u8], expiresAt: &BigInt, deployThreshold: &BigInt, immediateDeployThreshold: &BigInt) -> Vec<u8> {
let rev_delphi_cat: Vec<u8> = delphiCategory.iter().copied().rev().collect();
let mut input_bytecode = Builder::new()
.push_slice(collectorNFTH)
.push_slice(platformFeeNFTH)
@ -366,7 +419,7 @@ fn build_launcher_bytecode(collectorNFTH: &[u8], platformFeeNFTH: &[u8], platfor
.push_slice(&STORAGE_CONTRACT)
.push_slice(&OFFERING_INTEGRATED_TIMELOCKED_P2NFTH_CONTRACT)
.push_slice(&P2NFTH_CONTRACT)
.push_slice(delphiCategory)
.push_slice(&rev_delphi_cat)
.into_script()
.to_bytes();
input_bytecode.extend_from_slice(&bigint_to_push_opcode(expiresAt));
@ -390,7 +443,8 @@ fn build_partial_offering_initiator_bytecode(extensionOutpointIndex: &BigInt, to
bytecode.extend_from_slice(&bigint_to_push_opcode(distDeployOutpointIndex));
bytecode.extend_from_slice(&bigint_to_push_opcode(launcherBytecodeOutpointIndex));
bytecode.extend_from_slice(&bigint_to_push_opcode(offeringBytecodeOutpointIndex));
bytecode.extend_from_slice(&bigint_to_push_opcode(executionFee));
bytecode.push(0x04); // OP_PUSH4
bytecode.extend_from_slice(&encode_padded_vm_number(executionFee, 4).unwrap());
bytecode.extend_from_slice(&OFFERING_INITIATOR_CONTRACT);
bytecode
}
@ -855,8 +909,8 @@ async fn txchain_trim_tracker(
}
const IDO_PREINIT_ANNOUNCEMENT_SIGNATURE: &[u8; 15] = &[
0x6a, 0x4c, 0xba,
0x43, 0x61, 0x75, 0x6c, 0x64, 0x72, 0x6f, 0x6e, 0x49, 0x64, 0x6f, 0x30,
0x6a, 0x4c, 0xbb, // OP_RETURN OP_PUSHDATA1 (187)
0x43, 0x61, 0x75, 0x6c, 0x64, 0x72, 0x6f, 0x6e, 0x49, 0x64, 0x6f, 0x30, // CauldronIdo0
];
fn is_preinit_broadcast(
@ -963,7 +1017,7 @@ struct IdoPreinitParseParamsResult {
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> {
fn parse_ido_preinit_tx_params(tx: &Transaction, delphi_token_id: &[u8], 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() {
@ -986,13 +1040,13 @@ fn parse_ido_preinit_tx_params(tx: &Transaction, platform_fee_nfth: &[u8], error
// params main
match &instructions[1] {
Ok(Instruction::PushBytes(data)) => {
if data.len() < 185 {
if data.len() < 187 {
errors.push(anyhow::anyhow!("Incorrect announcement.mainData size"));
return None;
}
let mut is_valid_ido = true;
// preinit_parameters.offeredTokenTotalSupply < BigInt::from(0)
let 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[170..178]) < BigInt::from(0);
let offeringBcmrStorageOut = tx.output.get(8);
if offeringBcmrStorageOut.is_none() {
@ -1030,31 +1084,31 @@ fn parse_ido_preinit_tx_params(tx: &Transaction, platform_fee_nfth: &[u8], error
},
authguardLockingBytecode: authguardLockingBytecode,
offering: IdoParametersOffering {
platformFeeNFTH: data[10..42].to_vec(),
platformFeeNumerator: vm_number_to_bigint(&data[42..46]),
delphiCategory: data[46..78].to_vec(),
platformFeeNFTH: data[12..44].to_vec(),
platformFeeNumerator: vm_number_to_bigint(&data[44..48]),
delphiCategory: data[48..80].to_vec(),
launchConditions: IdoParametersOfferingLaunchConditions {
expiresAt: vm_number_to_bigint(&data[78..84]),
deployThreshold: vm_number_to_bigint(&data[84..92]),
immediateDeployThreshold: vm_number_to_bigint(&data[92..100]),
expiresAt: vm_number_to_bigint(&data[80..86]),
deployThreshold: vm_number_to_bigint(&data[86..94]),
immediateDeployThreshold: vm_number_to_bigint(&data[94..102]),
},
offer: IdoParametersOfferingOffer {
maxDiscountRateNumerator: vm_number_to_bigint(&data[100..104]),
discountAnnualRateNumerator: vm_number_to_bigint(&data[104..108]),
priceNumerator: vm_number_to_bigint(&data[108..124]),
minOffer: vm_number_to_bigint(&data[124..128]),
xTokenCategory: if vm_number_to_bigint(&data[128..160]) == BigInt::from(0) {
maxDiscountRateNumerator: vm_number_to_bigint(&data[102..106]),
discountAnnualRateNumerator: vm_number_to_bigint(&data[106..110]),
priceNumerator: vm_number_to_bigint(&data[110..126]),
minOffer: vm_number_to_bigint(&data[126..130]),
xTokenCategory: if vm_number_to_bigint(&data[130..162]) == BigInt::from(0) {
None
} else {
Some(data[128..160].to_vec())
Some(data[130..162].to_vec())
},
},
executionFee: vm_number_to_bigint(&data[160..164]),
executionFee: vm_number_to_bigint(&data[162..166]),
},
permanentLiquidityShareNumerator: vm_number_to_bigint(&data[164..168]),
offeredTokenTotalSupply: vm_number_to_bigint(&data[168..176]),
offeredTokenAmount: vm_number_to_bigint(&data[176..184]),
authguardNftOutputIndex: vm_number_to_bigint(&data[184..185]),
permanentLiquidityShareNumerator: vm_number_to_bigint(&data[166..170]),
offeredTokenTotalSupply: vm_number_to_bigint(&data[170..178]),
offeredTokenAmount: vm_number_to_bigint(&data[178..186]),
authguardNftOutputIndex: vm_number_to_bigint(&data[186..187]),
};
// enforce limitation (xToken == NATIVE_BCH)
@ -1101,7 +1155,7 @@ fn parse_ido_preinit_tx_params(tx: &Transaction, platform_fee_nfth: &[u8], error
invalid_ido_reasons.push(anyhow::anyhow!("preinit_parameters.offeredTokenAmount <= BigInt::from(0)"));
is_valid_ido = false;
}
if preinit_parameters.offering.delphiCategory != DELPHI_TOKEN_ID {
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;
}
@ -1148,11 +1202,16 @@ fn parse_ido_preinit_tx(network: Option<Network>, tx: &Transaction, errors: &mut
Some(Network::Bitcoin) => MAINNET_PLATFORM_FEE_NFTH.clone(),
_ => MAINNET_PLATFORM_FEE_NFTH.clone(),
};
let delphi_token_id = match network {
Some(Network::Chipnet) => CHIPNET_DELPHI_TOKEN_ID.clone(),
Some(Network::Bitcoin) => MAINNET_DELPHI_TOKEN_ID.clone(),
_ => MAINNET_DELPHI_TOKEN_ID.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) {
match parse_ido_preinit_tx_params(tx, &delphi_token_id, &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;
@ -1169,14 +1228,16 @@ fn parse_ido_preinit_tx(network: Option<Network>, tx: &Transaction, errors: &mut
// find authguardCategory
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")),
if params.authguardNftOutputIndex >= BigInt::from(0) {
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 state = IdoPreInitState {
@ -1303,7 +1364,7 @@ fn parse_ido_preinit_tx(network: Option<Network>, tx: &Transaction, errors: &mut
&params.permanentLiquidityShareNumerator,
&params.offering.offer.priceNumerator,
),
&BigInt::from(7i64), // permanentPoolOTokenReserveOutpointIndex
&BigInt::from(8i64), // permanentPoolOTokenReserveOutpointIndex
&BigInt::from(0i64), // offeringInitiatorOutpointIndex
),
).to_bytes();
@ -2157,29 +2218,29 @@ pub async fn index_block(
for input_index in [ 0, 1, 3 ] {
let input = tx.input.get(input_index);
if input.is_some() {
if let Some(txchain_item) =
txchain_lookup_next(
pool,
&input.unwrap().previous_output.txid,
input.unwrap().previous_output.vout
)
.await?
{
match ido_lookup(pool, txchain_item.ido_id)
if let Some(txchain_item) =
txchain_lookup_next(
pool,
&input.unwrap().previous_output.txid,
input.unwrap().previous_output.vout
)
.await?
{
match ido_lookup(pool, txchain_item.ido_id)
.await {
Ok(Some(ido)) => {
match on_add_ido_tx(network, pool, &ido, &txchain_item, tx, block_height)
.await {
Err(err) => info!("add tx to an ido failed, txid: {}, {}", hex::encode(tx.txid().to_blob()), err),
_ => (),
}
},
Err(err) => warn!("ido record not found!!, {}", err),
_ => warn!("ido record not found!!"),
}
break;
}
}
Ok(Some(ido)) => {
match on_add_ido_tx(network, pool, &ido, &txchain_item, tx, block_height)
.await {
Err(err) => info!("add tx to an ido failed, txid: {}, {}", hex::encode(tx.txid().to_blob()), err),
_ => (),
}
},
Err(err) => warn!("ido record not found!!, {}", err),
_ => warn!("ido record not found!!"),
}
break;
}
}
}
}
}
@ -2254,6 +2315,49 @@ impl IdoDBRecord {
}
}
#[cfg(test)]
mod tests {
use super::*;
use bitcoincash::blockdata::transaction::Transaction;
use std::sync::LazyLock;
static PREINIT_TEST_TX01: LazyLock<Vec<u8>> = LazyLock::new(|| hex::decode("0200000001e6b6c7e14eb71e53285f252a3ec38a4822befbe5ed15c18303c53c593af14b5900000000644167d23b1ba69109557b9d2efd7c20bc96e794fba690e33cee5b89e3db536045bd0fa04ff78b7e9b1c48b308b88355e2ffc1d92207c5da4c0d72a48765c4bdf88c612103e88d9ab230288089c7363b77431504661a943266c08235cb1ac3998349b75e67000000000c0000000000000000d86a4cbb4361756c64726f6e49646f30072d5fbecfa1bc37a13f58b188f50a5bc1dfb9ab8ae0bf5d3d9a63f6f81efc0b809698001b948240c9cfaa8224383db16e4735efa09c9426d1713d265e849a8e91f2669afefc0a6a000070170000000000007017000000000000002d310180969800041a130d8401000000000000000000007017000000000000000000000000000000000000000000000000000000000000000000001027000080c3c90100e40b540200000000ca9a3b00000000811976a91430da60e54a4d87ecdcb6adf91a98736668b55e9e88ace80300000000000023aa20d82b9eb9f86f02acd8787d6959d24e2a81e8e8ffcf9fae67d94e3b13ab74f6fe87e80300000000000023aa20b0be53828f2700b0fd254708e0b54b100163be607c1c82138ba793a2d667e55f87e80300000000000023aa20542b74123c493fc2f1f74d99586f87355b07c5a450b64111085759cf9cc96d8287e80300000000000023aa20f69ff67df71b9ecd4b6b39af393551468bb2a46fb261e2e5a61cfb27dbcbb25087e80300000000000023aa20e5b1d9865bf78f7cfc02e60ba0bcf327e0d3949f455a37dd8870979c3fc0f42d87e80300000000000023aa20bcf0c9aabff1ff311bccd16c598ac0bc7860253b2c8535cef71dbb2c0a1a782c87e80300000000000023aa207d3e837c0a706c368bacce2c03d1777a33e0a32a26bbf807c1a9b79446b3769787b0040000000000000f0201008178c99dc8c0c88702000075b0040000000000000f0201008178c99dc8c0c88702000075307500000000000023aa2060935eaee5df8476303b86855a14c093d02aaf816409533ad31ef87e79705b7f872bbdcc1d000000001976a914b093805bb56927c08724b92bfdfd77cf07c2cdc288ac00000000").unwrap());
#[test]
fn preinit_detect() {
let tx: Transaction = bitcoincash::consensus::deserialize(&PREINIT_TEST_TX01).expect("should be valid");
assert!(is_preinit_broadcast(&tx));
}
#[test]
fn parse_ido_preinit_from_valid_ido() {
let tx: Transaction = bitcoincash::consensus::deserialize(&PREINIT_TEST_TX01).expect("should be valid");
let mut errors: Vec<Error> = Vec::new();
let mut invalid_ido_reasons: Vec<Error> = Vec::new();
let network = Some(Network::Chipnet);
let result = parse_ido_preinit_tx(network, &tx, &mut errors, &mut invalid_ido_reasons);
let params = result.parameters.expect("parameters should be defined!");
let state = result.state.expect("state should be defined!");
if errors.len() > 0 {
println!("Parse ido preinit failed, txid: {}\nError(s):", hex::encode(tx.txid().to_blob()));
for error in &errors {
println!(" - {}", error);
}
}
if invalid_ido_reasons.len() > 0 {
println!("Invalid ido found, preinit_txid: {}\nReason(s):", hex::encode(tx.txid().to_blob()));
for reason in &invalid_ido_reasons {
println!(" - {}", reason);
}
}
println!("params: {}", String::from_utf8(serde_json::to_vec_pretty(&params).unwrap()).unwrap());
println!("state: {}", String::from_utf8(serde_json::to_vec_pretty(&state).unwrap()).unwrap());
assert!(errors.len() == 0 && invalid_ido_reasons.len() == 0);
assert!(result.is_valid_ido);
}
}
// ─── Public RPC query functions ──────────────────────────────────────────────
pub async fn list_idos(
@ -2266,7 +2370,8 @@ pub async fn list_idos(
) -> Result<Vec<IdoRpcRecord>> {
let mut qb: sqlx::QueryBuilder<sqlx::Sqlite> = sqlx::QueryBuilder::new(
"SELECT id, preinit_txid, init_txid, launch_txid, offering_token_id, offered_token_id, \
status, parameters, state, txchain_entrypoint, txchain_head, is_valid FROM ido WHERE 1=1",
status, parameters, state, txchain_entrypoint, txchain_head, is_valid, is_token_created_at_preinit
FROM ido WHERE 1=1",
);
if let Some(v) = is_valid {
qb.push(" AND is_valid = ");
@ -2299,7 +2404,7 @@ pub async fn get_ido_by_offering_token_id(
) -> Result<Option<IdoRpcRecord>> {
let row = sqlx::query(
"SELECT id, preinit_txid, init_txid, launch_txid, offering_token_id, offered_token_id, \
status, parameters, state, txchain_entrypoint, txchain_head, is_valid \
status, parameters, state, txchain_entrypoint, txchain_head, is_valid, is_token_created_at_preinit \
FROM ido WHERE offering_token_id = ?",
)
.bind(offering_token_id_blob)
@ -2460,7 +2565,7 @@ pub async fn list_txchain_tracker_map(
}
#[cfg(test)]
mod tests {
mod querytests {
use super::*;
use num_bigint::BigInt;
use sqlx::SqlitePool;
@ -2746,6 +2851,7 @@ mod tests {
.bind(distributed as i64)
.execute(pool)
.await
.unwrap();
}

View file

@ -445,7 +445,7 @@ pub async fn index_blocks(
.await?;
db::ido::index_block(
network,
network,
&db.ido_w,
&sorted_txs,
&blockhash,