Skip to content

Commit c5c593b

Browse files
committed
wallet-abi web pass #1
1 parent b454552 commit c5c593b

114 files changed

Lines changed: 11771 additions & 13028 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/cli/src/commands/pre_lock.rs

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use lending_contracts::sdk::parameters::{
2424
FirstNFTParameters, LendingParameters, SecondNFTParameters,
2525
};
2626

27-
use lending_contracts::sdk::taproot_unspendable_internal_key;
27+
use lending_contracts::sdk::{decode_pre_lock_metadata, taproot_unspendable_internal_key};
2828
use simplicity_contracts::sdk::validation::TxOutExt;
2929
use simplicityhl::elements::bitcoin::secp256k1;
3030
use simplicityhl::elements::hashes::Hash;
@@ -37,7 +37,7 @@ use simplicityhl::simplicity::hex::DisplayHex;
3737
use simplicityhl::tracker::TrackerLogLevel;
3838

3939
use simplicity_contracts::sdk::taproot_pubkey_gen::get_random_seed;
40-
use simplicity_contracts_cli::explorer::{broadcast_tx, fetch_utxo};
40+
use simplicity_contracts_cli::explorer::{ExplorerError, broadcast_tx, fetch_utxo};
4141
use simplicity_contracts_cli::modules::utils::derive_keypair;
4242

4343
use simplicityhl_core::{
@@ -289,13 +289,20 @@ impl PreLock {
289289
let borrower_nft_utxo = OutPoint::new(*pre_lock_tx_id, 3);
290290
let lender_nft_utxo = OutPoint::new(*pre_lock_tx_id, 4);
291291
let op_return_utxo = OutPoint::new(*pre_lock_tx_id, 5);
292+
let borrower_output_script_hash_utxo = OutPoint::new(*pre_lock_tx_id, 6);
292293

293294
let pre_lock_tx_out = fetch_utxo(pre_lock_utxo).await?;
294295
let first_parameters_nft_tx_out = fetch_utxo(first_parameters_nft_utxo).await?;
295296
let second_parameters_nft_tx_out = fetch_utxo(second_parameters_nft_utxo).await?;
296297
let borrower_nft_tx_out = fetch_utxo(borrower_nft_utxo).await?;
297298
let lender_nft_tx_out = fetch_utxo(lender_nft_utxo).await?;
298299
let op_return_tx_out = fetch_utxo(op_return_utxo).await?;
300+
let borrower_output_script_hash_tx_out =
301+
match fetch_utxo(borrower_output_script_hash_utxo).await {
302+
Ok(tx_out) => Some(tx_out),
303+
Err(ExplorerError::OutputIndexOutOfBounds { .. }) => None,
304+
Err(err) => return Err(err.into()),
305+
};
299306

300307
let (pre_lock_asset_id, _) = pre_lock_tx_out.explicit()?;
301308
let (first_parameters_nft_asset_id, first_parameters_nft_value) =
@@ -325,17 +332,31 @@ impl PreLock {
325332
.push_bytes()
326333
.unwrap();
327334

328-
let (op_return_pub_key, op_return_asset_id) = op_return_bytes.split_at(32);
329-
330-
let principal_asset_id: [u8; 32] =
331-
op_return_asset_id.try_into().expect("Length must be 32");
332-
333-
let borrower_public_key = XOnlyPublicKey::from_slice(op_return_pub_key).unwrap();
335+
let borrower_output_script_hash_bytes = borrower_output_script_hash_tx_out
336+
.as_ref()
337+
.filter(|tx_out| tx_out.is_null_data())
338+
.and_then(|tx_out| {
339+
let mut op_return_instr_iter = tx_out.script_pubkey.instructions_minimal();
340+
let _ = op_return_instr_iter.next()?;
341+
op_return_instr_iter
342+
.next()
343+
.and_then(Result::ok)
344+
.and_then(|instruction| instruction.push_bytes())
345+
});
346+
347+
let metadata =
348+
decode_pre_lock_metadata(op_return_bytes, borrower_output_script_hash_bytes)?;
349+
let principal_asset_id = metadata.principal_asset_id();
350+
let borrower_public_key =
351+
XOnlyPublicKey::from_slice(&metadata.borrower_pub_key()).unwrap();
334352

335353
println!("Pre Lock covenant info:");
336354
println!("Assets Info:");
337355
println!("\tCollateral asset id: {}", pre_lock_asset_id.to_hex());
338-
println!("\tPrincipal asset id: {}", principal_asset_id.to_hex());
356+
println!(
357+
"\tPrincipal asset id: {}",
358+
AssetId::from_slice(&principal_asset_id)?.to_hex()
359+
);
339360
println!(
340361
"\tFirst Parameters NFT asset id: {}",
341362
first_parameters_nft_asset_id.to_hex()
@@ -351,6 +372,18 @@ impl PreLock {
351372
println!("\tLender NFT asset id: {}", lender_nft_asset_id.to_hex());
352373
println!("Lending Offer Info:");
353374
println!("\tBorrower public key: {borrower_public_key}");
375+
if let Some(borrower_output_script) = metadata.borrower_output_script() {
376+
println!(
377+
"\tBorrower output script: {}",
378+
borrower_output_script.to_hex()
379+
);
380+
}
381+
if let Some(borrower_output_script_hash) = metadata.borrower_output_script_hash() {
382+
println!(
383+
"\tBorrower output script hash: {}",
384+
borrower_output_script_hash.to_hex()
385+
);
386+
}
354387
println!("\tCollateral amount: {}", lending_params.collateral_amount);
355388
println!("\tPrincipal amount: {}", lending_params.principal_amount);
356389
println!(
@@ -771,6 +804,7 @@ impl PreLock {
771804
(*lender_nft_utxo, lender_nft_tx_out.clone()),
772805
(*fee_utxo, fee_tx_out.clone()),
773806
&pre_lock_arguments,
807+
Some(&to_address.script_pubkey()),
774808
*fee_amount,
775809
NETWORK,
776810
)?;

crates/contracts/src/error.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ pub enum PreLockError {
3737
NotAPreLockTransaction { txid: String },
3838
#[error("Invalid OP_RETURN metadata bytes: {bytes}")]
3939
InvalidOpReturnBytes { bytes: String },
40+
#[error(
41+
"Pre lock borrower output script hashes differ: borrower NFT {borrower_nft_output_script_hash}, principal {principal_output_script_hash}"
42+
)]
43+
InconsistentBorrowerOutputScriptHashes {
44+
borrower_nft_output_script_hash: String,
45+
principal_output_script_hash: String,
46+
},
47+
#[error("Borrower output script hash mismatch: expected {expected_hash}, actual {actual_hash}")]
48+
BorrowerOutputScriptHashMismatch {
49+
expected_hash: String,
50+
actual_hash: String,
51+
},
4052
}
4153

4254
/// Errors from transaction building operations.

crates/contracts/src/pre_lock/mod.rs

Lines changed: 86 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ mod pre_lock_tests {
140140
use crate::sdk::parameters::LendingParameters;
141141
use crate::sdk::{
142142
build_pre_lock_cancellation, build_pre_lock_creation, build_pre_lock_lending_creation,
143-
taproot_unspendable_internal_key,
143+
decode_pre_lock_metadata, extract_arguments_from_tx, taproot_unspendable_internal_key,
144144
};
145145

146146
use super::*;
@@ -178,6 +178,7 @@ mod pre_lock_tests {
178178
second_parameters_nft_amount: u64,
179179
borrower_pub_key: &XOnlyPublicKey,
180180
lending_params: &LendingParameters,
181+
borrower_output_script_hash: Option<[u8; 32]>,
181182
) -> Result<((PartiallySignedTransaction, Address), PreLockArguments)> {
182183
// Calculate script hash for the AssetAuth covenant with the Lender NFT auth
183184
let asset_auth_arguments = AssetAuthArguments {
@@ -222,9 +223,9 @@ mod pre_lock_tests {
222223
.script_pubkey();
223224
let parameters_nft_output_script_hash = hash_script(&script_auth_script);
224225

225-
// Calculate P2TR script hash
226-
let borrower_p2tr_address = get_p2pk_address(borrower_pub_key, NETWORK)?;
227-
let borrower_p2tr_script_hash = hash_script(&borrower_p2tr_address.script_pubkey());
226+
let borrower_p2tr_script_hash = borrower_output_script_hash.unwrap_or(hash_script(
227+
&get_p2pk_address(borrower_pub_key, NETWORK)?.script_pubkey(),
228+
));
228229

229230
let pre_lock_arguments = PreLockArguments::new(
230231
collateral_asset_id.into_inner().0,
@@ -304,6 +305,7 @@ mod pre_lock_tests {
304305
},
305306
),
306307
&pre_lock_arguments,
308+
None,
307309
100,
308310
NETWORK,
309311
)?,
@@ -369,6 +371,7 @@ mod pre_lock_tests {
369371
second_parameters_amount,
370372
&test_borrower_key,
371373
&lending_params,
374+
None,
372375
)?;
373376

374377
let pst = pst.extract_tx()?;
@@ -389,15 +392,86 @@ mod pre_lock_tests {
389392
.push_bytes()
390393
.unwrap();
391394

392-
let (op_return_pub_key, op_return_asset_id) = op_return_bytes.split_at(32);
393-
394-
let op_return_asset_id: [u8; 32] =
395-
op_return_asset_id.try_into().expect("Length must be 32");
395+
let borrower_output_script_hash_bytes = pst.output.get(6).and_then(|tx_out| {
396+
if !tx_out.is_null_data() {
397+
return None;
398+
}
399+
400+
let mut op_return_instr_iter = tx_out.script_pubkey.instructions_minimal();
401+
let _ = op_return_instr_iter.next()?;
402+
op_return_instr_iter
403+
.next()
404+
.and_then(Result::ok)
405+
.and_then(|instruction| instruction.push_bytes())
406+
});
396407

397-
let op_return_public_key = XOnlyPublicKey::from_slice(op_return_pub_key).unwrap();
408+
let metadata =
409+
decode_pre_lock_metadata(op_return_bytes, borrower_output_script_hash_bytes)?;
410+
let op_return_public_key =
411+
XOnlyPublicKey::from_slice(&metadata.borrower_pub_key()).unwrap();
412+
let borrower_p2tr_script_hash =
413+
hash_script(&get_p2pk_address(&test_borrower_key, NETWORK)?.script_pubkey());
398414

399415
assert!(op_return_public_key.serialize() == test_borrower_key.serialize());
400-
assert!(principal_asset_id.into_inner().0 == op_return_asset_id);
416+
assert!(principal_asset_id.into_inner().0 == metadata.principal_asset_id());
417+
assert_eq!(
418+
metadata.borrower_output_script_hash(),
419+
Some(borrower_p2tr_script_hash)
420+
);
421+
422+
Ok(())
423+
}
424+
425+
#[test]
426+
fn test_pre_lock_creation_round_trips_custom_borrower_output_script_hash() -> Result<()> {
427+
let keypair = Keypair::from_secret_key(
428+
&Secp256k1::new(),
429+
&secp256k1::SecretKey::from_slice(&[1u8; 32])?,
430+
);
431+
let test_borrower_key = keypair.x_only_public_key().0;
432+
433+
let principal_asset_id = AssetId::from_str(LIQUID_TESTNET_TEST_ASSET_ID_STR)?;
434+
let (
435+
first_parameters_nft_asset_id,
436+
second_parameters_nft_asset_id,
437+
borrower_nft_asset_id,
438+
lender_nft_asset_id,
439+
) = create_test_assets()?;
440+
441+
let lending_params = LendingParameters {
442+
collateral_amount: 10_000,
443+
principal_amount: 4_000,
444+
loan_expiration_time: 100,
445+
principal_interest_rate: 250,
446+
};
447+
let (first_parameters_amount, second_parameters_amount) =
448+
lending_params.encode_parameters_nft_amounts(2)?;
449+
let borrower_output_script_hash = hash_script(&Script::new_p2pkh(&PubkeyHash::hash(
450+
&test_borrower_key.serialize(),
451+
)));
452+
453+
let ((pst, _), pre_lock_arguments) = get_creation_pst(
454+
*LIQUID_TESTNET_BITCOIN_ASSET,
455+
principal_asset_id,
456+
first_parameters_nft_asset_id,
457+
second_parameters_nft_asset_id,
458+
borrower_nft_asset_id,
459+
lender_nft_asset_id,
460+
first_parameters_amount,
461+
second_parameters_amount,
462+
&test_borrower_key,
463+
&lending_params,
464+
Some(borrower_output_script_hash),
465+
)?;
466+
467+
let tx = pst.extract_tx()?;
468+
let extracted_arguments = extract_arguments_from_tx(&tx, NETWORK)?;
469+
470+
assert_eq!(extracted_arguments, pre_lock_arguments);
471+
assert_eq!(
472+
extracted_arguments.principal_output_script_hash(),
473+
borrower_output_script_hash
474+
);
401475

402476
Ok(())
403477
}
@@ -439,6 +513,7 @@ mod pre_lock_tests {
439513
second_parameters_amount,
440514
&test_borrower_key,
441515
&lending_params,
516+
None,
442517
)?;
443518

444519
let pst = pst.extract_tx()?;
@@ -612,6 +687,7 @@ mod pre_lock_tests {
612687
second_parameters_amount,
613688
&test_borrower_key,
614689
&lending_params,
690+
None,
615691
)?;
616692

617693
let pst = pst.extract_tx()?;

crates/contracts/src/sdk/pre_lock/creation.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::script_auth::build_arguments::ScriptAuthArguments;
1414
use crate::script_auth::get_script_auth_address;
1515
use crate::sdk::basic::{add_base_input_from_utxo, check_asset_id, check_asset_value};
1616
use crate::sdk::parameters::{FirstNFTParameters, SecondNFTParameters};
17+
use crate::sdk::pre_lock::metadata::PreLockMetadata;
1718
use crate::sdk::taproot_unspendable_internal_key;
1819

1920
/// Create a new pre lock contract.
@@ -40,6 +41,7 @@ pub fn build_pre_lock_creation(
4041
lender_nft_utxo: (OutPoint, TxOut),
4142
fee_utxo: (OutPoint, TxOut),
4243
pre_lock_arguments: &PreLockArguments,
44+
borrower_output_script: Option<&Script>,
4345
fee_amount: u64,
4446
network: SimplicityNetwork,
4547
) -> Result<(PartiallySignedTransaction, Address), TransactionBuildError> {
@@ -192,10 +194,12 @@ pub fn build_pre_lock_creation(
192194
None,
193195
));
194196

195-
// Add OP_RETURN output with the Borrower public key and the Principal asset id
196-
let mut op_return_data = [0u8; 64];
197-
op_return_data[..32].copy_from_slice(&pre_lock_arguments.borrower_pub_key());
198-
op_return_data[32..].copy_from_slice(&pre_lock_arguments.principal_asset_id());
197+
// Persist the borrower signing key and principal asset in the primary metadata output.
198+
let metadata = PreLockMetadata::from_pre_lock_arguments(
199+
pre_lock_arguments,
200+
borrower_output_script.map(simplicityhl::elements::Script::as_bytes),
201+
)?;
202+
let op_return_data = metadata.encode();
199203

200204
pst.add_output(Output::new_explicit(
201205
Script::new_op_return(&op_return_data),
@@ -204,6 +208,15 @@ pub fn build_pre_lock_creation(
204208
None,
205209
));
206210

211+
if let Some(borrower_output_metadata) = metadata.encode_borrower_output_metadata() {
212+
pst.add_output(Output::new_explicit(
213+
Script::new_op_return(&borrower_output_metadata),
214+
0,
215+
AssetId::default(),
216+
None,
217+
));
218+
}
219+
207220
// Return collateral asset change
208221
if is_collateral_change_needed {
209222
pst.add_output(Output::new_explicit(

0 commit comments

Comments
 (0)