ido bcmr ser/deser fixes

This commit is contained in:
Hossein Zoda 2026-05-20 03:49:02 +03:00
parent c4eaad4cec
commit 210a51f274

View file

@ -473,7 +473,12 @@ fn build_partial_ido_initiator_bytecode(postLaunchPartialBytecode: &[u8], perman
fn create_rebuild_ipfs_placeholder_bcmr_partial_inputs(data: &IpfsBcmrWithPlaceholder) -> Vec<u8> { fn create_rebuild_ipfs_placeholder_bcmr_partial_inputs(data: &IpfsBcmrWithPlaceholder) -> Vec<u8> {
let mut bcmrUrisWithReplaceCalls = Vec::new(); let mut bcmrUrisWithReplaceCalls = Vec::new();
bcmrUrisWithReplaceCalls.extend_from_slice(&data.metadata.bcmrUrisReplaceCalls); bcmrUrisWithReplaceCalls.extend_from_slice(&data.metadata.bcmrUrisReplaceCalls);
bcmrUrisWithReplaceCalls.extend_from_slice(&data.urisBytecode); bcmrUrisWithReplaceCalls.extend_from_slice(
&Builder::new()
.push_slice(&data.urisBytecode)
.into_script()
.to_bytes()
);
Builder::new() Builder::new()
.push_slice(&data.contentHash) .push_slice(&data.contentHash)
.push_slice(&data.metadata.registryReplaceCalls) .push_slice(&data.metadata.registryReplaceCalls)
@ -495,7 +500,7 @@ fn build_preinit_bytecode(parameters: &IdoPreInitLockParameters, state: &IdoPreI
Some(bcmr) => create_rebuild_ipfs_placeholder_bcmr_partial_inputs(bcmr), Some(bcmr) => create_rebuild_ipfs_placeholder_bcmr_partial_inputs(bcmr),
_ => Vec::new(), _ => Vec::new(),
}; };
let oToken_bcmr_partial_inputs = match &parameters.preInitBcmr.offering { let oToken_bcmr_partial_inputs = match &parameters.preInitBcmr.oToken {
Some(bcmr) => create_rebuild_ipfs_placeholder_bcmr_partial_inputs(bcmr), Some(bcmr) => create_rebuild_ipfs_placeholder_bcmr_partial_inputs(bcmr),
_ => Vec::new(), _ => Vec::new(),
}; };
@ -933,11 +938,11 @@ fn deserialize_ipfs_bcmr_with_placeholder(
} }
if bcmr_data.len() == 0 || if bcmr_data.len() == 0 ||
!(bcmr_data[0] > 0 && bcmr_data[0] < 75) || !(bcmr_data[0] > 0 && bcmr_data[0] < 75) ||
bcmr_data[0].to_usize().unwrap() <= bcmr_data.len() { bcmr_data[0].to_usize().unwrap() >= bcmr_data.len() {
return Err(anyhow::anyhow!("bcmr needs metadata!")); return Err(anyhow::anyhow!("bcmr needs metadata!"));
} }
let metadata_size = bcmr_data[0].to_usize().unwrap(); let metadata_size = bcmr_data[0].to_usize().unwrap();
let metadata_script = Script::from(bcmr_data[0..metadata_size].to_vec()); let metadata_script = Script::from(bcmr_data[1..(metadata_size+1)].to_vec());
let metadata_inst_list: Vec<_> = metadata_script.instructions().collect(); let metadata_inst_list: Vec<_> = metadata_script.instructions().collect();
if metadata_inst_list.len() != 2 { if metadata_inst_list.len() != 2 {
return Err(anyhow::anyhow!("bcmr metadata should have two push opcodes!")); return Err(anyhow::anyhow!("bcmr metadata should have two push opcodes!"));
@ -948,13 +953,13 @@ fn deserialize_ipfs_bcmr_with_placeholder(
return Err(anyhow::anyhow!("oToken bcmr metadata opcode#0 is not a push opcode")); return Err(anyhow::anyhow!("oToken bcmr metadata opcode#0 is not a push opcode"));
} }
}; };
let bcmrUrisReplaceCalls = match &metadata_inst_list[0] { let bcmrUrisReplaceCalls = match &metadata_inst_list[1] {
Ok(Instruction::PushBytes(data)) => data.to_vec(), Ok(Instruction::PushBytes(data)) => data.to_vec(),
_ => { _ => {
return Err(anyhow::anyhow!("oToken bcmr metadata opcode#0 is not a push opcode")); return Err(anyhow::anyhow!("oToken bcmr metadata opcode#0 is not a push opcode"));
} }
}; };
let bcmr_opreturn: Vec<_> = bcmr_data[metadata_size..bcmr_data.len()].to_vec(); let bcmr_opreturn: Vec<_> = bcmr_data[(metadata_size+1)..bcmr_data.len()].to_vec();
if bcmr_opreturn.len() < BCMR_SIGNATURE.len() + 32 || if bcmr_opreturn.len() < BCMR_SIGNATURE.len() + 32 ||
&bcmr_opreturn[0..BCMR_SIGNATURE.len()] != BCMR_SIGNATURE { &bcmr_opreturn[0..BCMR_SIGNATURE.len()] != BCMR_SIGNATURE {
return Err(anyhow::anyhow!("invalid bcmr opreturn")); return Err(anyhow::anyhow!("invalid bcmr opreturn"));
@ -983,8 +988,12 @@ fn serialize_ipfs_bcmr_with_placeholder(
bcmr_bytecode.extend_from_slice(&BCMR_SIGNATURE); bcmr_bytecode.extend_from_slice(&BCMR_SIGNATURE);
bcmr_bytecode.extend_from_slice(&value.contentHash); bcmr_bytecode.extend_from_slice(&value.contentHash);
bcmr_bytecode.extend_from_slice(&value.urisBytecode); bcmr_bytecode.extend_from_slice(&value.urisBytecode);
let metadata_push = Builder::new()
.push_slice(&metadata_bytecode)
.into_script()
.to_bytes();
let mut bytes = Vec::new(); let mut bytes = Vec::new();
bytes.extend_from_slice(&metadata_bytecode); bytes.extend_from_slice(&metadata_push);
bytes.extend_from_slice(&bcmr_bytecode); bytes.extend_from_slice(&bcmr_bytecode);
bytes bytes
}, },
@ -1062,7 +1071,7 @@ fn parse_ido_preinit_tx_params(tx: &Transaction, delphi_token_id: &[u8], platfor
let offeringBcmr = offeringBcmrResult.unwrap(); 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() {
errors.push(anyhow::anyhow!("oToken bcmr storage not defined!")); errors.push(anyhow::anyhow!("oToken bcmr storage not defined!"));
@ -2322,6 +2331,7 @@ mod tests {
use std::sync::LazyLock; use std::sync::LazyLock;
static PREINIT_TEST_TX01: LazyLock<Vec<u8>> = LazyLock::new(|| hex::decode("0200000001e6b6c7e14eb71e53285f252a3ec38a4822befbe5ed15c18303c53c593af14b5900000000644167d23b1ba69109557b9d2efd7c20bc96e794fba690e33cee5b89e3db536045bd0fa04ff78b7e9b1c48b308b88355e2ffc1d92207c5da4c0d72a48765c4bdf88c612103e88d9ab230288089c7363b77431504661a943266c08235cb1ac3998349b75e67000000000c0000000000000000d86a4cbb4361756c64726f6e49646f30072d5fbecfa1bc37a13f58b188f50a5bc1dfb9ab8ae0bf5d3d9a63f6f81efc0b809698001b948240c9cfaa8224383db16e4735efa09c9426d1713d265e849a8e91f2669afefc0a6a000070170000000000007017000000000000002d310180969800041a130d8401000000000000000000007017000000000000000000000000000000000000000000000000000000000000000000001027000080c3c90100e40b540200000000ca9a3b00000000811976a91430da60e54a4d87ecdcb6adf91a98736668b55e9e88ace80300000000000023aa20d82b9eb9f86f02acd8787d6959d24e2a81e8e8ffcf9fae67d94e3b13ab74f6fe87e80300000000000023aa20b0be53828f2700b0fd254708e0b54b100163be607c1c82138ba793a2d667e55f87e80300000000000023aa20542b74123c493fc2f1f74d99586f87355b07c5a450b64111085759cf9cc96d8287e80300000000000023aa20f69ff67df71b9ecd4b6b39af393551468bb2a46fb261e2e5a61cfb27dbcbb25087e80300000000000023aa20e5b1d9865bf78f7cfc02e60ba0bcf327e0d3949f455a37dd8870979c3fc0f42d87e80300000000000023aa20bcf0c9aabff1ff311bccd16c598ac0bc7860253b2c8535cef71dbb2c0a1a782c87e80300000000000023aa207d3e837c0a706c368bacce2c03d1777a33e0a32a26bbf807c1a9b79446b3769787b0040000000000000f0201008178c99dc8c0c88702000075b0040000000000000f0201008178c99dc8c0c88702000075307500000000000023aa2060935eaee5df8476303b86855a14c093d02aaf816409533ad31ef87e79705b7f872bbdcc1d000000001976a914b093805bb56927c08724b92bfdfd77cf07c2cdc288ac00000000").unwrap()); static PREINIT_TEST_TX01: LazyLock<Vec<u8>> = LazyLock::new(|| hex::decode("0200000001e6b6c7e14eb71e53285f252a3ec38a4822befbe5ed15c18303c53c593af14b5900000000644167d23b1ba69109557b9d2efd7c20bc96e794fba690e33cee5b89e3db536045bd0fa04ff78b7e9b1c48b308b88355e2ffc1d92207c5da4c0d72a48765c4bdf88c612103e88d9ab230288089c7363b77431504661a943266c08235cb1ac3998349b75e67000000000c0000000000000000d86a4cbb4361756c64726f6e49646f30072d5fbecfa1bc37a13f58b188f50a5bc1dfb9ab8ae0bf5d3d9a63f6f81efc0b809698001b948240c9cfaa8224383db16e4735efa09c9426d1713d265e849a8e91f2669afefc0a6a000070170000000000007017000000000000002d310180969800041a130d8401000000000000000000007017000000000000000000000000000000000000000000000000000000000000000000001027000080c3c90100e40b540200000000ca9a3b00000000811976a91430da60e54a4d87ecdcb6adf91a98736668b55e9e88ace80300000000000023aa20d82b9eb9f86f02acd8787d6959d24e2a81e8e8ffcf9fae67d94e3b13ab74f6fe87e80300000000000023aa20b0be53828f2700b0fd254708e0b54b100163be607c1c82138ba793a2d667e55f87e80300000000000023aa20542b74123c493fc2f1f74d99586f87355b07c5a450b64111085759cf9cc96d8287e80300000000000023aa20f69ff67df71b9ecd4b6b39af393551468bb2a46fb261e2e5a61cfb27dbcbb25087e80300000000000023aa20e5b1d9865bf78f7cfc02e60ba0bcf327e0d3949f455a37dd8870979c3fc0f42d87e80300000000000023aa20bcf0c9aabff1ff311bccd16c598ac0bc7860253b2c8535cef71dbb2c0a1a782c87e80300000000000023aa207d3e837c0a706c368bacce2c03d1777a33e0a32a26bbf807c1a9b79446b3769787b0040000000000000f0201008178c99dc8c0c88702000075b0040000000000000f0201008178c99dc8c0c88702000075307500000000000023aa2060935eaee5df8476303b86855a14c093d02aaf816409533ad31ef87e79705b7f872bbdcc1d000000001976a914b093805bb56927c08724b92bfdfd77cf07c2cdc288ac00000000").unwrap());
static PREINIT_TEST_TX02: LazyLock<Vec<u8>> = LazyLock::new(|| hex::decode("0200000001e2365768e0f14db4f3686481a0f53c9bc8e5f5093b858503744c42605653a99b0b0000006441cb694ca0293e4c34ebaf8339aa81b32c9965fea6503e1ef5748f6c1d7b6cfce8904643e1cb4d09458b44004a632dbd103bc14dc1faaebfaaf5cdf7cba9c94f1c612103b67517abb88bd00b5d81154aeab86a2e65ab952e5d24d178e1c3ea82112834fc000000000c0000000000000000d86a4cbb4361756c64726f6e49646f30072d5fbecfa1bc37a13f58b188f50a5bc1dfb9ab8ae0bf5d3d9a63f6f81efc0b809698001b948240c9cfaa8224383db16e4735efa09c9426d1713d265e849a8e91f2669afefc0a6a0000c027090000000000c027090000000000002d310180969800a92669e1030000000000000000000000c027090000000000000000000000000000000000000000000000000000000000000000001027000080c3c9010010a5d4e800000000e8764817000000811976a91495570c60bbdeaf648a259bb177911169e060a76788ace80300000000000023aa200b8614ab1785d536b190f9244cb8eac7e4139bac23c9cd198a48d987cd94196287e80300000000000023aa201c8817a666e4a1ede4afafd752fd372f7271c3a2036507b29902e9ea1835f27587e80300000000000023aa208abe922d98f699c05865a769eeeb05300e7d74982d0b38ba8c03774db8bc257c87e80300000000000023aa20f69ff67df71b9ecd4b6b39af393551468bb2a46fb261e2e5a61cfb27dbcbb25087e80300000000000023aa20e5b1d9865bf78f7cfc02e60ba0bcf327e0d3949f455a37dd8870979c3fc0f42d87e80300000000000023aa20bcf0c9aabff1ff311bccd16c598ac0bc7860253b2c8535cef71dbb2c0a1a782c87e80300000000000023aa206ead0ce800334c2decb31c9f6b4d3a6a1bf03789513206a107b464481c917bb687b0040000000000000f0201008178c99dc8c0c88702000075b004000000000000c70201008178c99dc8c0c8874cb90c0602c400024a0104011901526a0442434d52206f6465e0112ce788c1ac118d7783add22cc3d0d28b3e3616b89be097cd6919b84968747470733a2f2f697066732e72696674656e2e6e65742f75415655534947396b5a6541524c4f6549776177526a5865447264497377394453697a343246726962344a664e61526d3438697066733a2f2f75415655534947396b5a6541524c4f6549776177526a5865447264497377394453697a343246726962344a664e61526d34b70075307500000000000023aa2060935eaee5df8476303b86855a14c093d02aaf816409533ad31ef87e79705b7f87a91ecc1d000000001976a914b093805bb56927c08724b92bfdfd77cf07c2cdc288ac00000000").unwrap());
#[test] #[test]
fn preinit_detect() { fn preinit_detect() {
@ -2336,8 +2346,6 @@ mod tests {
let mut invalid_ido_reasons: Vec<Error> = Vec::new(); let mut invalid_ido_reasons: Vec<Error> = Vec::new();
let network = Some(Network::Chipnet); let network = Some(Network::Chipnet);
let result = parse_ido_preinit_tx(network, &tx, &mut errors, &mut invalid_ido_reasons); 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 { if errors.len() > 0 {
println!("Parse ido preinit failed, txid: {}\nError(s):", hex::encode(tx.txid().to_blob())); println!("Parse ido preinit failed, txid: {}\nError(s):", hex::encode(tx.txid().to_blob()));
for error in &errors { for error in &errors {
@ -2350,11 +2358,41 @@ mod tests {
println!(" - {}", reason); println!(" - {}", reason);
} }
} }
let params = result.parameters.expect("parameters should be defined!");
let state = result.state.expect("state should be defined!");
println!("params: {}", String::from_utf8(serde_json::to_vec_pretty(&params).unwrap()).unwrap()); 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()); println!("state: {}", String::from_utf8(serde_json::to_vec_pretty(&state).unwrap()).unwrap());
assert!(errors.len() == 0 && invalid_ido_reasons.len() == 0); assert!(errors.len() == 0 && invalid_ido_reasons.len() == 0);
assert!(result.is_valid_ido); assert!(result.is_valid_ido);
} }
#[test]
fn parse_ido_preinit_from_valid_ido_with_bcmr() {
let tx: Transaction = bitcoincash::consensus::deserialize(&PREINIT_TEST_TX02).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);
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);
}
}
let params = result.parameters.expect("parameters should be defined!");
let state = result.state.expect("state should be defined!");
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);
}
} }