From 8baf205001db36f3a65f09d9f4f5e74872342076 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 18 May 2022 00:15:00 +0300 Subject: [PATCH 01/11] Added `add_required_native_input_scripts` and `add_required_plutus_input_scripts` API --- rust/pkg/cardano_serialization_lib.js.flow | 183 +++++++- rust/src/address.rs | 2 +- rust/src/lib.rs | 8 +- rust/src/plutus.rs | 18 + rust/src/tx_builder.rs | 493 +++++++++++++++++++-- 5 files changed, 649 insertions(+), 55 deletions(-) diff --git a/rust/pkg/cardano_serialization_lib.js.flow b/rust/pkg/cardano_serialization_lib.js.flow index 2976aceb..e543e64b 100644 --- a/rust/pkg/cardano_serialization_lib.js.flow +++ b/rust/pkg/cardano_serialization_lib.js.flow @@ -255,6 +255,7 @@ declare export var NativeScriptKind: {| declare export var ScriptHashNamespace: {| +NativeScript: 0, // 0 + +PlutusScript: 1, // 1 |}; /** @@ -1982,6 +1983,17 @@ declare export class HeaderBody { declare export class Int { free(): void; + /** + * @returns {Uint8Array} + */ + to_bytes(): Uint8Array; + + /** + * @param {Uint8Array} bytes + * @returns {Int} + */ + static from_bytes(bytes: Uint8Array): Int; + /** * @param {BigNum} x * @returns {Int} @@ -2723,10 +2735,9 @@ declare export class NativeScript { static from_bytes(bytes: Uint8Array): NativeScript; /** - * @param {number} namespace * @returns {ScriptHash} */ - hash(namespace: number): ScriptHash; + hash(): ScriptHash; /** * @param {ScriptPubkey} script_pubkey @@ -3168,6 +3179,11 @@ declare export class PlutusScript { * @returns {Uint8Array} */ bytes(): Uint8Array; + + /** + * @returns {ScriptHash} + */ + hash(): ScriptHash; } /** */ @@ -3206,6 +3222,64 @@ declare export class PlutusScripts { */ add(elem: PlutusScript): void; } +/** + */ +declare export class PlutusWitness { + free(): void; + + /** + * @param {PlutusScript} script + * @param {PlutusData} datum + * @param {Redeemer} redeemer + * @returns {PlutusWitness} + */ + static new( + script: PlutusScript, + datum: PlutusData, + redeemer: Redeemer + ): PlutusWitness; + + /** + * @returns {PlutusScript} + */ + script(): PlutusScript; + + /** + * @returns {PlutusData} + */ + datum(): PlutusData; + + /** + * @returns {Redeemer} + */ + redeemer(): Redeemer; +} +/** + */ +declare export class PlutusWitnesses { + free(): void; + + /** + * @returns {PlutusWitnesses} + */ + static new(): PlutusWitnesses; + + /** + * @returns {number} + */ + len(): number; + + /** + * @param {number} index + * @returns {PlutusWitness} + */ + get(index: number): PlutusWitness; + + /** + * @param {PlutusWitness} elem + */ + add(elem: PlutusWitness): void; +} /** */ declare export class Pointer { @@ -5081,6 +5155,13 @@ declare export class TransactionBuilder { ): void; /** + * This method adds the input to the builder BUT leaves a missing spot for the witness native script + * + * After adding the input with this method, use `.add_required_native_input_scripts` + * and `.add_required_plutus_input_scripts` to add the witness scripts + * + * Or instead use `.add_native_script_input` and `.add_plutus_script_input` + * to add inputs right along with the script, instead of the script hash * @param {ScriptHash} hash * @param {TransactionInput} input * @param {Value} amount @@ -5091,6 +5172,30 @@ declare export class TransactionBuilder { amount: Value ): void; + /** + * This method will add the input to the builder and also register the required native script witness + * @param {NativeScript} script + * @param {TransactionInput} input + * @param {Value} amount + */ + add_native_script_input( + script: NativeScript, + input: TransactionInput, + amount: Value + ): void; + + /** + * This method will add the input to the builder and also register the required plutus witness + * @param {PlutusWitness} witness + * @param {TransactionInput} input + * @param {Value} amount + */ + add_plutus_script_input( + witness: PlutusWitness, + input: TransactionInput, + amount: Value + ): void; + /** * @param {ByronAddress} hash * @param {TransactionInput} input @@ -5103,12 +5208,54 @@ declare export class TransactionBuilder { ): void; /** + * Note that for script inputs this method will use underlying generic `.add_script_input` + * which leaves a required empty spot for the script witness (or witnesses in case of Plutus). + * You can use `.add_native_script_input` or `.add_plutus_script_input` directly to register the input along with the witness. * @param {Address} address * @param {TransactionInput} input * @param {Value} amount */ add_input(address: Address, input: TransactionInput, amount: Value): void; + /** + * Returns the number of still missing input scripts (either native or plutus) + * Use `.add_required_native_input_scripts` or `.add_required_plutus_input_scripts` to add the missing scripts + * @returns {number} + */ + count_missing_input_scripts(): number; + + /** + * Try adding the specified scripts as witnesses for ALREADY ADDED script inputs + * Any scripts that don't match any of the previously added inputs will be ignored + * Returns the number of remaining required missing witness scripts + * Use `.count_missing_input_scripts` to find the number of still missing scripts + * @param {NativeScripts} scripts + * @returns {number} + */ + add_required_native_input_scripts(scripts: NativeScripts): number; + + /** + * Try adding the specified scripts as witnesses for ALREADY ADDED script inputs + * Any scripts that don't match any of the previously added inputs will be ignored + * Returns the number of remaining required missing witness scripts + * Use `.count_missing_input_scripts` to find the number of still missing scripts + * @param {PlutusWitnesses} scripts + * @returns {number} + */ + add_required_plutus_input_scripts(scripts: PlutusWitnesses): number; + + /** + * Returns a copy of the current script input witness scripts in the builder + * @returns {NativeScripts | void} + */ + get_native_input_scripts(): NativeScripts | void; + + /** + * Returns a copy of the current plutus input witness scripts in the builder + * @returns {PlutusWitnesses | void} + */ + get_plutus_input_scripts(): PlutusWitnesses | void; + /** * calculates how much the fee would increase if you added a given output * @param {Address} address @@ -5345,6 +5492,31 @@ declare export class TransactionBuilder { */ add_change_if_needed(address: Address): boolean; + /** + * This method will calculate the script hash data + * using the plutus datums and redeemers already present in the builder + * along with the provided cost model, and will register the calculated value + * in the builder to be used when building the tx body. + * In case there are no plutus input witnesses present - nothing will change + * You can set specific hash value using `.set_script_data_hash` + * @param {Costmdls} cost_models + */ + calc_script_data_hash(cost_models: Costmdls): void; + + /** + * Sets the specified hash value. + * Alternatively you can use `.calc_script_data_hash` to calculate the hash automatically. + * Or use `.remove_script_data_hash` to delete the previously set value + * @param {ScriptDataHash} hash + */ + set_script_data_hash(hash: ScriptDataHash): void; + + /** + * Deletes any previously set plutus data hash value. + * Use `.set_script_data_hash` or `.calc_script_data_hash` to set it. + */ + remove_script_data_hash(): void; + /** * @returns {number} */ @@ -5367,10 +5539,17 @@ declare export class TransactionBuilder { * Returns full Transaction object with the body and the auxiliary data * NOTE: witness_set will contain all mint_scripts if any been added or set * NOTE: is_valid set to true + * NOTE: Will fail in case there are any script inputs added with no corresponding witness * @returns {Transaction} */ build_tx(): Transaction; + /** + * Similar to `.build_tx()` but will NOT fail in case there are missing script witnesses + * @returns {Transaction} + */ + build_tx_unsafe(): Transaction; + /** * warning: sum of all parts of a transaction must equal 0. You cannot just set the fee to the min value and forget about it * warning: min_fee may be slightly larger than the actual minimum fee (ex: a few lovelaces) diff --git a/rust/src/address.rs b/rust/src/address.rs index 430930fd..5fbd9ca8 100644 --- a/rust/src/address.rs +++ b/rust/src/address.rs @@ -1031,7 +1031,7 @@ mod tests { let oneof_native_script = NativeScript::new_script_n_of_k(&ScriptNOfK::new(1, &pubkey_native_scripts)); let script_hash = ScriptHash::from_bytes( - oneof_native_script.hash(ScriptHashNamespace::NativeScript).to_bytes() + oneof_native_script.hash().to_bytes() ).unwrap(); let spend_cred = StakeCredential::from_scripthash(&script_hash); diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 09db52b5..9d060b06 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1636,14 +1636,14 @@ to_from_bytes!(NativeScript); #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] pub enum ScriptHashNamespace { NativeScript, - // TODO: do we need to update this for Plutus? + PlutusScript, } #[wasm_bindgen] impl NativeScript { - pub fn hash(&self, namespace: ScriptHashNamespace) -> ScriptHash { + pub fn hash(&self) -> ScriptHash { let mut bytes = Vec::with_capacity(self.to_bytes().len() + 1); - bytes.extend_from_slice(&vec![namespace as u8]); + bytes.extend_from_slice(&vec![ScriptHashNamespace::NativeScript as u8]); bytes.extend_from_slice(&self.to_bytes()); ScriptHash::from(blake2b224(bytes.as_ref())) } @@ -2873,7 +2873,7 @@ mod tests { let script = NativeScript::new_script_pubkey(&ScriptPubkey::new(&keyhash)); - let script_hash = script.hash(ScriptHashNamespace::NativeScript); + let script_hash = script.hash(); assert_eq!(hex::encode(&script_hash.to_bytes()), "187b8d3ddcb24013097c003da0b8d8f7ddcf937119d8f59dccd05a0f"); } diff --git a/rust/src/plutus.rs b/rust/src/plutus.rs index 242cea76..7d48bfe9 100644 --- a/rust/src/plutus.rs +++ b/rust/src/plutus.rs @@ -31,6 +31,13 @@ impl PlutusScript { pub fn bytes(&self) -> Vec { self.0.clone() } + + pub fn hash(&self) -> ScriptHash { + let mut bytes = Vec::with_capacity(self.0.len() + 1); + bytes.extend_from_slice(&vec![ScriptHashNamespace::PlutusScript as u8]); + bytes.extend_from_slice(&self.0); + ScriptHash::from(blake2b224(bytes.as_ref())) + } } #[wasm_bindgen] @@ -1316,4 +1323,15 @@ mod tests { "a141005901d59f1a000302590001011a00060bc719026d00011a000249f01903e800011a000249f018201a0025cea81971f70419744d186419744d186419744d186419744d186419744d186419744d18641864186419744d18641a000249f018201a000249f018201a000249f018201a000249f01903e800011a000249f018201a000249f01903e800081a000242201a00067e2318760001011a000249f01903e800081a000249f01a0001b79818f7011a000249f0192710011a0002155e19052e011903e81a000249f01903e8011a000249f018201a000249f018201a000249f0182001011a000249f0011a000249f0041a000194af18f8011a000194af18f8011a0002377c190556011a0002bdea1901f1011a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a000242201a00067e23187600010119f04c192bd200011a000249f018201a000242201a00067e2318760001011a000242201a00067e2318760001011a0025cea81971f704001a000141bb041a000249f019138800011a000249f018201a000302590001011a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a00330da70101ff" ); } + + #[test] + fn test_plutus_script_hash() { + let hash = EnterpriseAddress::from_address( + &Address::from_bech32("addr1w896t6qnpsjs32xhw8jl3kw34pqz69kgd72l8hqw83w0k3qahx2sv").unwrap() + ).unwrap().payment_cred().to_scripthash().unwrap(); + let script = PlutusScript::from_bytes( + hex::decode("590e6f590e6c0100003323332223322333222332232332233223232333222323332223233333333222222223233322232333322223232332232323332223232332233223232333332222233223322332233223322332222323232232232325335303233300a3333573466e1cd55cea8042400046664446660a40060040026eb4d5d0a8041bae35742a00e66a05046666ae68cdc39aab9d37540029000102b11931a982599ab9c04f04c04a049357426ae89401c8c98d4c124cd5ce0268250240239999ab9a3370ea0089001102b11999ab9a3370ea00a9000102c11931a982519ab9c04e04b0490480473333573466e1cd55cea8012400046601a64646464646464646464646666ae68cdc39aab9d500a480008cccccccccc06ccd40a48c8c8cccd5cd19b8735573aa0049000119810981c9aba15002302e357426ae8940088c98d4c164cd5ce02e82d02c02b89aab9e5001137540026ae854028cd40a40a8d5d0a804999aa8183ae502f35742a010666aa060eb940bcd5d0a80399a8148211aba15006335029335505304b75a6ae854014c8c8c8cccd5cd19b8735573aa0049000119a8119919191999ab9a3370e6aae7540092000233502b33504175a6ae854008c118d5d09aba25002232635305d3357380c20bc0b80b626aae7940044dd50009aba150023232323333573466e1cd55cea80124000466a05266a082eb4d5d0a80118231aba135744a004464c6a60ba66ae7018417817016c4d55cf280089baa001357426ae8940088c98d4c164cd5ce02e82d02c02b89aab9e5001137540026ae854010cd40a5d71aba15003335029335505375c40026ae854008c0e0d5d09aba2500223263530553357380b20ac0a80a626ae8940044d5d1280089aba25001135744a00226ae8940044d5d1280089aba25001135744a00226aae7940044dd50009aba150023232323333573466e1d4005200623020303a357426aae79400c8cccd5cd19b875002480108c07cc110d5d09aab9e500423333573466e1d400d20022301f302f357426aae7940148cccd5cd19b875004480008c088dd71aba135573ca00c464c6a60a066ae7015014413c13813413012c4d55cea80089baa001357426ae8940088c98d4c124cd5ce026825024023882489931a982419ab9c4910350543500049047135573ca00226ea80044d55ce9baa001135744a00226aae7940044dd50009109198008018011000911111111109199999999980080580500480400380300280200180110009109198008018011000891091980080180109000891091980080180109000891091980080180109000909111180200290911118018029091111801002909111180080290008919118011bac0013200135503c2233335573e0024a01c466a01a60086ae84008c00cd5d100101811919191999ab9a3370e6aae75400d200023330073232323333573466e1cd55cea8012400046601a605c6ae854008cd404c0a8d5d09aba25002232635303433573807006a06606426aae7940044dd50009aba150033335500b75ca0146ae854008cd403dd71aba135744a004464c6a606066ae700d00c40bc0b84d5d1280089aab9e5001137540024442466600200800600440024424660020060044002266aa002eb9d6889119118011bab00132001355036223233335573e0044a012466a01066aa05c600c6aae754008c014d55cf280118021aba200302b1357420022244004244244660020080062400224464646666ae68cdc3a800a400046a05e600a6ae84d55cf280191999ab9a3370ea00490011281791931a981399ab9c02b028026025024135573aa00226ea80048c8c8cccd5cd19b8735573aa004900011980318039aba15002375a6ae84d5d1280111931a981219ab9c028025023022135573ca00226ea80048848cc00400c00880048c8cccd5cd19b8735573aa002900011bae357426aae7940088c98d4c080cd5ce01201080f80f09baa00112232323333573466e1d400520042500723333573466e1d4009200223500a3006357426aae7940108cccd5cd19b87500348000940288c98d4c08ccd5ce01381201101081000f89aab9d50011375400224244460060082244400422444002240024646666ae68cdc3a800a4004400c46666ae68cdc3a80124000400c464c6a603666ae7007c0700680640604d55ce9baa0011220021220012001232323232323333573466e1d4005200c200b23333573466e1d4009200a200d23333573466e1d400d200823300b375c6ae854014dd69aba135744a00a46666ae68cdc3a8022400c46601a6eb8d5d0a8039bae357426ae89401c8cccd5cd19b875005480108cc048c050d5d0a8049bae357426ae8940248cccd5cd19b875006480088c050c054d5d09aab9e500b23333573466e1d401d2000230133016357426aae7940308c98d4c080cd5ce01201080f80f00e80e00d80d00c80c09aab9d5004135573ca00626aae7940084d55cf280089baa00121222222230070082212222222330060090082122222223005008122222220041222222200322122222223300200900822122222223300100900820012323232323333573466e1d400520022333008375a6ae854010dd69aba15003375a6ae84d5d1280191999ab9a3370ea00490001180518059aba135573ca00c464c6a602266ae7005404804003c0384d55cea80189aba25001135573ca00226ea80048488c00800c888488ccc00401401000c80048c8c8cccd5cd19b875001480088c018dd71aba135573ca00646666ae68cdc3a80124000460106eb8d5d09aab9e5004232635300b33573801e01801401201026aae7540044dd5000909118010019091180080190008891119191999ab9a3370e6aae75400920002335500b300635742a004600a6ae84d5d1280111931a980419ab9c00c009007006135573ca00226ea800526120012001112212330010030021120014910350543100222123330010040030022001121223002003112200112001120012001122002122001200111232300100122330033002002001332323233322233322233223332223322332233322233223322332233223233322232323322323232323333222232332232323222323222325335301a5335301a333573466e1cc8cccd54c05048004c8cd406488ccd406400c004008d4058004cd4060888c00cc008004800488cdc0000a40040029000199aa98068900091299a980e299a9a81a1a98169a98131a9812001110009110019119a98188011281c11a81c8009080f880e899a8148010008800a8141a981028009111111111005240040380362038266ae712413c53686f756c642062652065786163746c79206f6e652073637269707420696e70757420746f2061766f696420646f75626c65207361742069737375650001b15335303500315335301a5335301a333573466e20ccc064ccd54c03448005402540a0cc020d4c0c00188880094004074074cdc09a9818003111001a80200d80e080e099ab9c49010f73656c6c6572206e6f7420706169640001b15335301a333573466e20ccc064cc88ccd54c03c48005402d40a8cc028004009400401c074075401006c07040704cd5ce24810d66656573206e6f7420706169640001b101b15335301a3322353022002222222222253353503e33355301f1200133502322533535040002210031001503f253353027333573466e3c0300040a40a04d41040045410000c840a4409d4004d4c0c001888800840704cd5ce2491c4f6e6c792073656c6c65722063616e2063616e63656c206f666665720001b101b135301d00122002153353016333573466e2540040d406005c40d4540044cdc199b8235302b001222003480c920d00f2235301a0012222222222333553011120012235302a002222353034003223353038002253353026333573466e3c0500040a009c4cd40cc01401c401c801d40b0024488cd54c02c480048d4d5408c00488cd54098008cd54c038480048d4d5409800488cd540a4008ccd4d540340048cc0e12000001223303900200123303800148000004cd54c02c480048d4d5408c00488cd54098008ccd4d540280048cd54c03c480048d4d5409c00488cd540a8008d5404400400488ccd5540200580080048cd54c03c480048d4d5409c00488cd540a8008d5403c004004ccd55400c044008004444888ccd54c018480054080cd54c02c480048d4d5408c00488cd54098008d54034004ccd54c0184800488d4d54090008894cd4c05cccd54c04048004c8cd405488ccd4d402c00c88008008004d4d402400488004cd4024894cd4c064008406c40040608d4d5409c00488cc028008014018400c4cd409001000d4084004cd54c02c480048d4d5408c00488c8cd5409c00cc004014c8004d540d8894cd4d40900044d5403400c884d4d540a4008894cd4c070cc0300080204cd5404801c0044c01800c00848848cc00400c00848004c8004d540b488448894cd4d40780044008884cc014008ccd54c01c480040140100044484888c00c01044884888cc0080140104484888c004010448004c8004d540a08844894cd4d406000454068884cd406cc010008cd54c01848004010004c8004d5409c88448894cd4d40600044d401800c884ccd4024014c010008ccd54c01c4800401401000448d4d400c0048800448d4d40080048800848848cc00400c0084800488ccd5cd19b8f002001006005222323230010053200135502522335350130014800088d4d54060008894cd4c02cccd5cd19b8f00200900d00c13007001130060033200135502422335350120014800088d4d5405c008894cd4c028ccd5cd19b8f00200700c00b10011300600312200212200120014881002212330010030022001222222222212333333333300100b00a009008007006005004003002200122123300100300220012221233300100400300220011122002122122330010040031200111221233001003002112001221233001003002200121223002003212230010032001222123330010040030022001121223002003112200112001122002122001200122337000040029040497a0088919180080091198019801001000a4411c28f07a93d7715db0bdc1766c8bd5b116602b105c02c54fc3bcd0d4680001").unwrap().clone(), + ).unwrap(); + assert_eq!(script.hash(), hash); + } } diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index fd7de19f..ab70fc0a 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -3,6 +3,7 @@ use super::fees; use super::utils; use super::output_builder::{TransactionOutputAmountBuilder}; use std::collections::{BTreeMap, BTreeSet, HashSet}; +use linked_hash_map::LinkedHashMap; // comes from witsVKeyNeeded in the Ledger spec fn witness_keys_for_cert(cert_enum: &Certificate, keys: &mut BTreeSet) { @@ -93,14 +94,6 @@ fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Resul Some(result) }, }; - let script_keys: Option = match tx_builder.input_types.scripts.len() { - 0 => None, - _x => { - // TODO: figure out how to populate fake witnesses for these - // return Err(JsError::from_str("Script inputs not supported yet")) - None - }, - }; let bootstrap_keys = match tx_builder.input_types.bootstraps.len() { 0 => None, _x => { @@ -116,26 +109,21 @@ fn fake_full_tx(tx_builder: &TransactionBuilder, body: TransactionBody) -> Resul Some(result) }, }; - let full_script_keys = match &tx_builder.mint_scripts { - None => script_keys, - Some(witness_scripts) => { - let mut ns = script_keys - .map(|x| { x.clone() }) - .unwrap_or(NativeScripts::new()); - witness_scripts.0.iter().for_each(|s| { - ns.add(s); - }); - Some(ns) + let (plutus_scripts, plutus_data, redeemers) = { + if let Some(s) = tx_builder.get_plutus_input_scripts() { + let (s, d, r) = s.collect(); + (Some(s), Some(d), Some(r)) + } else { + (None, None, None) } }; let witness_set = TransactionWitnessSet { vkeys, - native_scripts: full_script_keys, + native_scripts: tx_builder.get_combined_native_scripts(), bootstraps: bootstrap_keys, - // TODO: plutus support? - plutus_scripts: None, - plutus_data: None, - redeemers: None, + plutus_scripts, + plutus_data, + redeemers, }; Ok(Transaction { body, @@ -152,9 +140,8 @@ fn assert_required_mint_scripts(mint: &Mint, maybe_mint_scripts: Option<&NativeS )); } let mint_scripts = maybe_mint_scripts.unwrap(); - let witness_hashes: HashSet = mint_scripts.0.iter().map(|script| { - script.hash(ScriptHashNamespace::NativeScript) - }).collect(); + let witness_hashes: HashSet = mint_scripts.0.iter() + .map(|script| { script.hash() }).collect(); for mint_hash in mint.keys().0.iter() { if !witness_hashes.contains(mint_hash) { return Err(JsError::from_str( @@ -181,11 +168,80 @@ fn min_fee(tx_builder: &TransactionBuilder) -> Result { } +#[wasm_bindgen] +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct PlutusWitnesses(Vec); + +#[wasm_bindgen] +impl PlutusWitnesses { + pub fn new() -> Self { + Self(Vec::new()) + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn get(&self, index: usize) -> PlutusWitness { + self.0[index].clone() + } + + pub fn add(&mut self, elem: &PlutusWitness) { + self.0.push(elem.clone()); + } + + fn collect(&self) -> (PlutusScripts, PlutusList, Redeemers) { + let mut s = PlutusScripts::new(); + let mut d = PlutusList::new(); + let mut r = Redeemers::new(); + self.0.iter().for_each(|w| { + s.add(&w.script); + d.add(&w.datum); + r.add(&w.redeemer); + }); + (s, d, r) + } +} + +#[wasm_bindgen] +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct PlutusWitness { + script: PlutusScript, + datum: PlutusData, + redeemer: Redeemer, +} + +#[wasm_bindgen] +impl PlutusWitness { + + pub fn new(script: PlutusScript, datum: PlutusData, redeemer: Redeemer) -> Self { + Self { script, datum, redeemer } + } + + pub fn script(&self) -> PlutusScript { + self.script.clone() + } + + pub fn datum(&self) -> PlutusData { + self.datum.clone() + } + + pub fn redeemer(&self) -> Redeemer { + self.redeemer.clone() + } +} + +#[derive(Clone, Debug)] +enum WitnessType { + NativeScriptWitness(NativeScript), + PlutusScriptWitness(PlutusWitness), +} + // We need to know how many of each type of witness will be in the transaction so we can calculate the tx fee #[derive(Clone, Debug)] struct MockWitnessSet { vkeys: BTreeSet, - scripts: BTreeSet, + scripts: LinkedHashMap>, bootstraps: BTreeSet>, } @@ -305,7 +361,7 @@ impl TransactionBuilderConfigBuilder { #[derive(Clone, Debug)] pub struct TransactionBuilder { config: TransactionBuilderConfig, - inputs: Vec, + inputs: Vec<(TxBuilderInput, Option)>, outputs: TransactionOutputs, fee: Option, ttl: Option, // absolute slot number @@ -316,6 +372,7 @@ pub struct TransactionBuilder { input_types: MockWitnessSet, mint: Option, mint_scripts: Option, + script_data_hash: Option, } #[wasm_bindgen] @@ -575,27 +632,64 @@ impl TransactionBuilder { /// 1) mock witnesses have different lengths depending on the type which changes the expecting fee /// 2) Witnesses are a set so we need to get rid of duplicates to avoid over-estimating the fee pub fn add_key_input(&mut self, hash: &Ed25519KeyHash, input: &TransactionInput, amount: &Value) { - self.inputs.push(TxBuilderInput { + let inp = TxBuilderInput { input: input.clone(), amount: amount.clone(), - }); + }; + self.inputs.push((inp, None)); self.input_types.vkeys.insert(hash.clone()); } + + /// This method adds the input to the builder BUT leaves a missing spot for the witness native script + /// + /// After adding the input with this method, use `.add_required_native_input_scripts` + /// and `.add_required_plutus_input_scripts` to add the witness scripts + /// + /// Or instead use `.add_native_script_input` and `.add_plutus_script_input` + /// to add inputs right along with the script, instead of the script hash pub fn add_script_input(&mut self, hash: &ScriptHash, input: &TransactionInput, amount: &Value) { - self.inputs.push(TxBuilderInput { + let inp = TxBuilderInput { input: input.clone(), amount: amount.clone(), - }); - self.input_types.scripts.insert(hash.clone()); + }; + self.inputs.push((inp, Some(hash.clone()))); + if !self.input_types.scripts.contains_key(hash) { + self.input_types.scripts.insert(hash.clone(), None); + } } + + /// This method will add the input to the builder and also register the required native script witness + pub fn add_native_script_input(&mut self, script: &NativeScript, input: &TransactionInput, amount: &Value) { + let hash = script.hash(); + self.add_script_input(&hash, input, amount); + self.input_types.scripts.insert( + hash, + Some(WitnessType::NativeScriptWitness(script.clone())), + ); + } + + /// This method will add the input to the builder and also register the required plutus witness + pub fn add_plutus_script_input(&mut self, witness: &PlutusWitness, input: &TransactionInput, amount: &Value) { + let hash = witness.script.hash(); + self.add_script_input(&hash, input, amount); + self.input_types.scripts.insert( + hash, + Some(WitnessType::PlutusScriptWitness(witness.clone())), + ); + } + pub fn add_bootstrap_input(&mut self, hash: &ByronAddress, input: &TransactionInput, amount: &Value) { - self.inputs.push(TxBuilderInput { + let inp = TxBuilderInput { input: input.clone(), amount: amount.clone(), - }); + }; + self.inputs.push((inp, None)); self.input_types.bootstraps.insert(hash.to_bytes()); } + /// Note that for script inputs this method will use underlying generic `.add_script_input` + /// which leaves a required empty spot for the script witness (or witnesses in case of Plutus). + /// You can use `.add_native_script_input` or `.add_plutus_script_input` directly to register the input along with the witness. pub fn add_input(&mut self, address: &Address, input: &TransactionInput, amount: &Value) { match &BaseAddress::from_address(address) { Some(addr) => { @@ -644,6 +738,68 @@ impl TransactionBuilder { } } + /// Returns the number of still missing input scripts (either native or plutus) + /// Use `.add_required_native_input_scripts` or `.add_required_plutus_input_scripts` to add the missing scripts + pub fn count_missing_input_scripts(&self) -> usize { + self.input_types.scripts.values().filter(|s| { s.is_none() }).count() + } + + /// Try adding the specified scripts as witnesses for ALREADY ADDED script inputs + /// Any scripts that don't match any of the previously added inputs will be ignored + /// Returns the number of remaining required missing witness scripts + /// Use `.count_missing_input_scripts` to find the number of still missing scripts + pub fn add_required_native_input_scripts(&mut self, scripts: &NativeScripts) -> usize { + scripts.0.iter().for_each(|s: &NativeScript| { + let hash = s.hash(); + if self.input_types.scripts.contains_key(&hash) { + self.input_types.scripts.insert( + hash, + Some(WitnessType::NativeScriptWitness(s.clone())), + ); + } + }); + self.count_missing_input_scripts() + } + + /// Try adding the specified scripts as witnesses for ALREADY ADDED script inputs + /// Any scripts that don't match any of the previously added inputs will be ignored + /// Returns the number of remaining required missing witness scripts + /// Use `.count_missing_input_scripts` to find the number of still missing scripts + pub fn add_required_plutus_input_scripts(&mut self, scripts: &PlutusWitnesses) -> usize { + scripts.0.iter().for_each(|s: &PlutusWitness| { + let hash = s.script.hash(); + if self.input_types.scripts.contains_key(&hash) { + self.input_types.scripts.insert( + hash, + Some(WitnessType::PlutusScriptWitness(s.clone())), + ); + } + }); + self.count_missing_input_scripts() + } + + /// Returns a copy of the current script input witness scripts in the builder + pub fn get_native_input_scripts(&self) -> Option { + let mut scripts = NativeScripts::new(); + self.input_types.scripts.values().for_each(|option| { + if let Some(WitnessType::NativeScriptWitness(s)) = option { + scripts.add(&s); + } + }); + if scripts.len() > 0 { Some(scripts) } else { None } + } + + /// Returns a copy of the current plutus input witness scripts in the builder + pub fn get_plutus_input_scripts(&self) -> Option { + let mut scripts = PlutusWitnesses::new(); + self.input_types.scripts.values().for_each(|option| { + if let Some(WitnessType::PlutusScriptWitness(s)) = option { + scripts.add(&s); + } + }); + if scripts.len() > 0 { Some(scripts) } else { None } + } + /// calculates how much the fee would increase if you added a given output pub fn fee_for_input(&self, address: &Address, input: &TransactionInput, amount: &Value) -> Result { let mut self_copy = self.clone(); @@ -822,7 +978,7 @@ impl TransactionBuilder { /// It will be securely added to existing or new Mint in this builder /// It will replace any existing mint assets with the same PolicyID pub fn set_mint_asset(&mut self, policy_script: &NativeScript, mint_assets: &MintAssets) { - let policy_id: PolicyID = policy_script.hash(ScriptHashNamespace::NativeScript); + let policy_id: PolicyID = policy_script.hash(); self._set_mint_asset(&policy_id, policy_script, mint_assets); } @@ -839,7 +995,7 @@ impl TransactionBuilder { /// It will be securely added to existing or new Mint in this builder /// It will replace any previous existing amount same PolicyID and AssetName pub fn add_mint_asset(&mut self, policy_script: &NativeScript, asset_name: &AssetName, amount: Int) { - let policy_id: PolicyID = policy_script.hash(ScriptHashNamespace::NativeScript); + let policy_id: PolicyID = policy_script.hash(); self._add_mint_asset(&policy_id, policy_script, asset_name, amount); } @@ -858,7 +1014,7 @@ impl TransactionBuilder { if !amount.is_positive() { return Err(JsError::from_str("Output value must be positive!")); } - let policy_id: PolicyID = policy_script.hash(ScriptHashNamespace::NativeScript); + let policy_id: PolicyID = policy_script.hash(); self._add_mint_asset(&policy_id, policy_script, asset_name, amount.clone()); let multiasset = Mint::new_from_entry( &policy_id, @@ -886,7 +1042,7 @@ impl TransactionBuilder { if !amount.is_positive() { return Err(JsError::from_str("Output value must be positive!")); } - let policy_id: PolicyID = policy_script.hash(ScriptHashNamespace::NativeScript); + let policy_id: PolicyID = policy_script.hash(); self._add_mint_asset(&policy_id, policy_script, asset_name, amount.clone()); let multiasset = Mint::new_from_entry( &policy_id, @@ -911,12 +1067,13 @@ impl TransactionBuilder { auxiliary_data: None, input_types: MockWitnessSet { vkeys: BTreeSet::new(), - scripts: BTreeSet::new(), + scripts: LinkedHashMap::new(), bootstraps: BTreeSet::new(), }, validity_start_interval: None, mint: None, mint_scripts: None, + script_data_hash: None, } } @@ -924,7 +1081,7 @@ impl TransactionBuilder { pub fn get_explicit_input(&self) -> Result { self.inputs .iter() - .try_fold(Value::zero(), |acc, ref tx_builder_input| { + .try_fold(Value::zero(), |acc, (ref tx_builder_input, _)| { acc.checked_add(&tx_builder_input.amount) }) } @@ -1238,10 +1395,40 @@ impl TransactionBuilder { } } + /// This method will calculate the script hash data + /// using the plutus datums and redeemers already present in the builder + /// along with the provided cost model, and will register the calculated value + /// in the builder to be used when building the tx body. + /// In case there are no plutus input witnesses present - nothing will change + /// You can set specific hash value using `.set_script_data_hash` + pub fn calc_script_data_hash(&mut self, cost_models: &Costmdls) { + if let Some(pw) = self.get_plutus_input_scripts() { + let (_, datums, redeemers) = pw.collect(); + self.script_data_hash = + Some(hash_script_data(&redeemers, cost_models, Some(datums))); + } + } + + /// Sets the specified hash value. + /// Alternatively you can use `.calc_script_data_hash` to calculate the hash automatically. + /// Or use `.remove_script_data_hash` to delete the previously set value + pub fn set_script_data_hash(&mut self, hash: &ScriptDataHash) { + self.script_data_hash = Some(hash.clone()); + } + + /// Deletes any previously set plutus data hash value. + /// Use `.set_script_data_hash` or `.calc_script_data_hash` to set it. + pub fn remove_script_data_hash(&mut self) { + self.script_data_hash = None; + } + fn build_and_size(&self) -> Result<(TransactionBody, usize), JsError> { let fee = self.fee.ok_or_else(|| JsError::from_str("Fee not specified"))?; + // + let inputs = self.inputs.iter() + .map(|(ref tx_builder_input, _)| tx_builder_input.input.clone()).collect(); let built = TransactionBody { - inputs: TransactionInputs(self.inputs.iter().map(|ref tx_builder_input| tx_builder_input.input.clone()).collect()), + inputs: TransactionInputs(inputs), outputs: self.outputs.clone(), fee: fee, ttl: self.ttl, @@ -1254,8 +1441,7 @@ impl TransactionBuilder { }, validity_start_interval: self.validity_start_interval, mint: self.mint.clone(), - // TODO: update for use with Alonzo - script_data_hash: None, + script_data_hash: self.script_data_hash.clone(), collateral: None, required_signers: None, network_id: None, @@ -1290,14 +1476,31 @@ impl TransactionBuilder { } } + fn get_combined_native_scripts(&self) -> Option { + let mut ns = NativeScripts::new(); + if let Some(input_scripts) = self.get_native_input_scripts() { + input_scripts.0.iter().for_each(|s| { ns.add(s); }); + } + if let Some(mint_scripts) = &self.mint_scripts { + mint_scripts.0.iter().for_each(|s| { ns.add(s); }); + } + if ns.len() > 0 { Some(ns) } else { None } + } + // This function should be producing the total witness-set // that is created by the tx-builder itself, // before the transaction is getting signed by the actual wallet. // E.g. scripts or something else that has been used during the tx preparation fn get_witness_set(&self) -> TransactionWitnessSet { let mut wit = TransactionWitnessSet::new(); - if let Some(scripts) = self.mint_scripts.as_ref() { - wit.set_native_scripts(scripts); + if let Some(scripts) = self.get_combined_native_scripts() { + wit.set_native_scripts(&scripts); + } + if let Some(pw) = self.get_plutus_input_scripts() { + let (scripts, datums, redeemers) = pw.collect(); + wit.set_plutus_scripts(&scripts); + wit.set_plutus_data(&datums); + wit.set_redeemers(&redeemers); } wit } @@ -1305,7 +1508,18 @@ impl TransactionBuilder { /// Returns full Transaction object with the body and the auxiliary data /// NOTE: witness_set will contain all mint_scripts if any been added or set /// NOTE: is_valid set to true + /// NOTE: Will fail in case there are any script inputs added with no corresponding witness pub fn build_tx(&self) -> Result { + if self.count_missing_input_scripts() > 0 { + return Err(JsError::from_str( + "There are some script inputs added that don't have the corresponding script provided as a witness!", + )); + } + self.build_tx_unsafe() + } + + /// Similar to `.build_tx()` but will NOT fail in case there are missing script witnesses + pub fn build_tx_unsafe(&self) -> Result { Ok(Transaction { body: self.build()?, witness_set: self.get_witness_set(), @@ -3671,11 +3885,11 @@ mod tests { let mint_script = NativeScript::new_script_pubkey( &ScriptPubkey::new(&hash) ); - let policy_id = mint_script.hash(ScriptHashNamespace::NativeScript); + let policy_id = mint_script.hash(); (mint_script, policy_id, hash) } - fn mint_script_and_policy(x: u8) -> (NativeScript, PolicyID) { + fn mint_script_and_policy(x: u8) -> (NativeScript, ScriptHash) { let (m, p, _) = mint_script_and_policy_and_hash(x); (m, p) } @@ -4227,5 +4441,188 @@ mod tests { assert_eq!(ma2_output.get(&policy_id2).unwrap().get(&name).unwrap(), to_bignum(40)); } + fn create_base_address_from_script_hash(sh: &ScriptHash) -> Address { + BaseAddress::new( + NetworkInfo::testnet().network_id(), + &StakeCredential::from_scripthash(sh), + &StakeCredential::from_keyhash(&fake_key_hash(0)) + ).to_address() + } + + #[test] + fn test_set_input_scripts() { + let mut tx_builder = create_reallistic_tx_builder(); + let (script1, hash1) = mint_script_and_policy(0); + let (script2, hash2) = mint_script_and_policy(1); + let (script3, _hash3) = mint_script_and_policy(2); + // Trying to set native scripts to the builder + let rem0 = tx_builder.add_required_native_input_scripts( + &NativeScripts::from(vec![script1.clone(), script2.clone(), script3.clone()]), + ); + assert_eq!(rem0, 0); + let missing0 = tx_builder.count_missing_input_scripts(); + assert_eq!(missing0, 0); + // Adding two script inputs using script1 and script2 hashes + tx_builder.add_input( + &create_base_address_from_script_hash(&hash1), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)) + ); + tx_builder.add_input( + &create_base_address_from_script_hash(&hash2), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)) + ); + // Setting a non-matching script will not change anything + let rem1 = tx_builder.add_required_native_input_scripts( + &NativeScripts::from(vec![script3.clone()]), + ); + assert_eq!(rem1, 2); + let missing1 = tx_builder.count_missing_input_scripts(); + assert_eq!(missing1, 2); + // Setting one of the required scripts leaves one to be required + let rem2 = tx_builder.add_required_native_input_scripts( + &NativeScripts::from(vec![script1.clone(), script3.clone()]), + ); + assert_eq!(rem2, 1); + let missing2 = tx_builder.count_missing_input_scripts(); + assert_eq!(missing2, 1); + // Setting one non-required script again does not change anything + // But shows the state has changed + let rem3 = tx_builder.add_required_native_input_scripts( + &NativeScripts::from(vec![script3.clone()]), + ); + assert_eq!(rem3, 1); + let missing3 = tx_builder.count_missing_input_scripts(); + assert_eq!(missing3, 1); + // Setting two required scripts will show both of them added + // And the remainder required is zero + let rem4 = tx_builder.add_required_native_input_scripts( + &NativeScripts::from(vec![script1.clone(), script2.clone()]), + ); + assert_eq!(rem4, 0); + let missing4 = tx_builder.count_missing_input_scripts(); + assert_eq!(missing4, 0); + // Setting empty scripts does not change anything + // But shows the state has changed + let rem5 = tx_builder.add_required_native_input_scripts( + &NativeScripts::new() + ); + assert_eq!(rem5, 0); + } + + #[test] + fn test_add_native_script_input() { + let mut tx_builder = create_reallistic_tx_builder(); + let (script1, _hash1) = mint_script_and_policy(0); + let (script2, _hash2) = mint_script_and_policy(1); + let (script3, hash3) = mint_script_and_policy(2); + // Adding two script inputs directly with their witness + tx_builder.add_native_script_input( + &script1, + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)) + ); + tx_builder.add_native_script_input( + &script2, + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)) + ); + // Adding one script input indirectly via hash3 address + tx_builder.add_input( + &create_base_address_from_script_hash(&hash3), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)) + ); + // Checking missing input scripts shows one + // Because first two inputs already have their witness + let missing1 = tx_builder.count_missing_input_scripts(); + assert_eq!(missing1, 1); + // Setting the required script leaves none to be required` + let rem1 = tx_builder.add_required_native_input_scripts( + &NativeScripts::from(vec![script3.clone()]), + ); + assert_eq!(rem1, 0); + let missing2 = tx_builder.count_missing_input_scripts(); + assert_eq!(missing2, 0); + } + + fn unsafe_tx_len(b: &TransactionBuilder) -> usize { + b.build_tx_unsafe().unwrap().to_bytes().len() + } + + #[test] + fn test_native_input_scripts_are_added_to_the_witnesses() { + let mut tx_builder = create_reallistic_tx_builder(); + let (script1, _hash1) = mint_script_and_policy(0); + let (script2, hash2) = mint_script_and_policy(1); + tx_builder.set_fee(&to_bignum(42)); + tx_builder.add_native_script_input( + &script1, + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)) + ); + let tx_len_before_new_script_input = unsafe_tx_len(&tx_builder); + tx_builder.add_input( + &create_base_address_from_script_hash(&hash2), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)) + ); + let tx_len_after_new_script_input = unsafe_tx_len(&tx_builder); + // Tx size increased cuz input is added even without the witness + assert!(tx_len_after_new_script_input > tx_len_before_new_script_input); + tx_builder.add_required_native_input_scripts( + &NativeScripts::from(vec![script2.clone()]), + ); + let tx_len_after_adding_script_witness = unsafe_tx_len(&tx_builder); + // Tx size increased cuz the witness is added to the witnesses + assert!(tx_len_after_adding_script_witness > tx_len_after_new_script_input); + tx_builder.add_required_native_input_scripts( + &NativeScripts::from(vec![script1.clone(), script2.clone()]), + ); + let tx_len_after_adding_script_witness_again = unsafe_tx_len(&tx_builder); + // Tx size did not change because calling to add same witnesses again doesn't change anything + assert!(tx_len_after_adding_script_witness == tx_len_after_adding_script_witness_again); + let tx: Transaction = tx_builder.build_tx_unsafe().unwrap(); + assert!(tx.witness_set.native_scripts.is_some()); + let native_scripts = tx.witness_set.native_scripts.unwrap(); + assert_eq!(native_scripts.len(), 2); + assert_eq!(native_scripts.get(0), script1); + assert_eq!(native_scripts.get(1), script2); + } + + #[test] + fn test_building_with_missing_witness_script_fails() { + let mut tx_builder = create_reallistic_tx_builder(); + let (script1, _hash1) = mint_script_and_policy(0); + let (script2, hash2) = mint_script_and_policy(1); + tx_builder.set_fee(&to_bignum(42)); + // Ok to build before any inputs + assert!(tx_builder.build_tx().is_ok()); + // Adding native script input which adds the witness right away + tx_builder.add_native_script_input( + &script1, + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)) + ); + // Ok to build when witness is added along with the input + assert!(tx_builder.build_tx().is_ok()); + // Adding script input without the witness + tx_builder.add_input( + &create_base_address_from_script_hash(&hash2), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)) + ); + // Not ok to build when missing a witness + assert!(tx_builder.build_tx().is_err()); + // Can force to build using unsafe + assert!(tx_builder.build_tx_unsafe().is_ok()); + // Adding the missing witness script + tx_builder.add_required_native_input_scripts( + &NativeScripts::from(vec![script2.clone()]), + ); + // Ok to build when all witnesses are added + assert!(tx_builder.build_tx().is_ok()); + } } From 89cc870f3f3951a0f62985bf86325c322d9d6386 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 18 May 2022 13:08:01 +0300 Subject: [PATCH 02/11] Added setting correct input indexes to redeemers when building witness set --- rust/pkg/cardano_serialization_lib.js.flow | 3 +- rust/src/plutus.rs | 9 +++++ rust/src/tx_builder.rs | 43 +++++++++++++++++++--- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/rust/pkg/cardano_serialization_lib.js.flow b/rust/pkg/cardano_serialization_lib.js.flow index 17d1ec70..3bc770ea 100644 --- a/rust/pkg/cardano_serialization_lib.js.flow +++ b/rust/pkg/cardano_serialization_lib.js.flow @@ -5403,7 +5403,8 @@ declare export class TransactionBuilder { get_native_input_scripts(): NativeScripts | void; /** - * Returns a copy of the current plutus input witness scripts in the builder + * Returns a copy of the current plutus input witness scripts in the builder. + * NOTE: each plutus witness will be cloned with a specific corresponding input index * @returns {PlutusWitnesses | void} */ get_plutus_input_scripts(): PlutusWitnesses | void; diff --git a/rust/src/plutus.rs b/rust/src/plutus.rs index 7d48bfe9..5a6c3bf9 100644 --- a/rust/src/plutus.rs +++ b/rust/src/plutus.rs @@ -533,6 +533,15 @@ impl Redeemer { ex_units: ex_units.clone(), } } + + pub(crate) fn clone_with_index(&self, index: &BigNum) -> Self { + Self { + tag: self.tag.clone(), + index: index.clone(), + data: self.data.clone(), + ex_units: self.ex_units.clone(), + } + } } #[wasm_bindgen] diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 4d525f07..c7672952 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -229,6 +229,14 @@ impl PlutusWitness { pub fn redeemer(&self) -> Redeemer { self.redeemer.clone() } + + fn clone_with_redeemer_index(&self, index: &BigNum) -> Self { + Self { + script: self.script.clone(), + datum: self.datum.clone(), + redeemer: self.redeemer.clone_with_index(index), + } + } } #[derive(Clone, Debug)] @@ -789,12 +797,33 @@ impl TransactionBuilder { if scripts.len() > 0 { Some(scripts) } else { None } } - /// Returns a copy of the current plutus input witness scripts in the builder + /// Returns a copy of the current plutus input witness scripts in the builder. + /// NOTE: each plutus witness will be cloned with a specific corresponding input index pub fn get_plutus_input_scripts(&self) -> Option { + /* + * === EXPLANATION === + * The `Redeemer` object contains the `.index` field which is supposed to point + * exactly to the index of the corresponding input in the inputs array. We want to + * simplify and automate this as much as possible for the user to not have to care about it. + * + * For this we store the script hash along with the input, when it was registered, and + * now we create a map of script hashes to their input indexes. + * + * The registered witnesses are then each cloned with the new correct redeemer input index. + */ + let script_hash_index_map: BTreeMap<&ScriptHash, BigNum> = self.inputs.iter().enumerate() + .fold(BTreeMap::new(), |mut m, (i, (_, hash_option))| { + if let Some(hash) = hash_option { + m.insert(hash, to_bignum(i as u64)); + } + m + }); let mut scripts = PlutusWitnesses::new(); - self.input_types.scripts.values().for_each(|option| { + self.input_types.scripts.iter().for_each(|(hash, option)| { if let Some(WitnessType::PlutusScriptWitness(s)) = option { - scripts.add(&s); + if let Some(idx) = script_hash_index_map.get(&hash) { + scripts.add(&s.clone_with_redeemer_index(&idx)); + } } }); if scripts.len() > 0 { Some(scripts) } else { None } @@ -1444,13 +1473,12 @@ impl TransactionBuilder { fn build_and_size(&self) -> Result<(TransactionBody, usize), JsError> { let fee = self.fee.ok_or_else(|| JsError::from_str("Fee not specified"))?; - // let inputs = self.inputs.iter() .map(|(ref tx_builder_input, _)| tx_builder_input.input.clone()).collect(); let built = TransactionBody { inputs: TransactionInputs(inputs), outputs: self.outputs.clone(), - fee: fee, + fee, ttl: self.ttl, certs: self.certs.clone(), withdrawals: self.withdrawals.clone(), @@ -1535,6 +1563,11 @@ impl TransactionBuilder { "There are some script inputs added that don't have the corresponding script provided as a witness!", )); } + if self.script_data_hash.is_none() && self.get_plutus_input_scripts().is_some() { + return Err(JsError::from_str( + "Plutus inputs are present, but script data hash is not specified", + )); + } self.build_tx_unsafe() } From ad08af55bf2805d9785a283c67a8141a59ff2f80 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 18 May 2022 13:34:19 +0300 Subject: [PATCH 03/11] Beta version bump: 10.1.0-beta.1 --- package-lock.json | 2 +- package.json | 2 +- rust/Cargo.lock | 2 +- rust/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7b5f55b9..5cec60a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cardano-serialization-lib", - "version": "10.0.5-beta.1", + "version": "10.1.0-beta.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 180677aa..4651572a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cardano-serialization-lib", - "version": "10.0.5-beta.1", + "version": "10.1.0-beta.1", "description": "(De)serialization functions for the Cardano blockchain along with related utility functions", "scripts": { "rust:build-nodejs": "(rimraf ./rust/pkg && cd rust; wasm-pack build --target=nodejs; wasm-pack pack) && npm run js:flowgen", diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 341d259a..c496de08 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -52,7 +52,7 @@ checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "cardano-serialization-lib" -version = "10.0.5-beta.1" +version = "10.1.0-beta.1" dependencies = [ "bech32", "cbor_event", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index b8040f9c..08a0ecd7 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cardano-serialization-lib" -version = "10.0.5-beta.1" +version = "10.1.0-beta.1" edition = "2018" authors = ["EMURGO"] license = "MIT" From aa94d0ca9a4e9932b5b272555ac4fe0184eede65 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 18 May 2022 13:45:53 +0300 Subject: [PATCH 04/11] Added `ScriptHashNamespace.scrip_hash_prefix` --- rust/src/lib.rs | 14 +++++++++++++- rust/src/plutus.rs | 4 +++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 60042d0e..de76b295 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1774,11 +1774,23 @@ pub enum ScriptHashNamespace { PlutusScript, } +impl ScriptHashNamespace { + pub fn scrip_hash_prefix(&self) -> u8 { + match self { + ScriptHashNamespace::NativeScript => 0 as u8, + ScriptHashNamespace::PlutusScript => 1 as u8, + } + } +} + #[wasm_bindgen] impl NativeScript { + pub fn hash(&self) -> ScriptHash { let mut bytes = Vec::with_capacity(self.to_bytes().len() + 1); - bytes.extend_from_slice(&vec![ScriptHashNamespace::NativeScript as u8]); + bytes.extend_from_slice(&vec![ + ScriptHashNamespace::NativeScript.scrip_hash_prefix(), + ]); bytes.extend_from_slice(&self.to_bytes()); ScriptHash::from(blake2b224(bytes.as_ref())) } diff --git a/rust/src/plutus.rs b/rust/src/plutus.rs index 5a6c3bf9..07bc9dc5 100644 --- a/rust/src/plutus.rs +++ b/rust/src/plutus.rs @@ -34,7 +34,9 @@ impl PlutusScript { pub fn hash(&self) -> ScriptHash { let mut bytes = Vec::with_capacity(self.0.len() + 1); - bytes.extend_from_slice(&vec![ScriptHashNamespace::PlutusScript as u8]); + bytes.extend_from_slice(&vec![ + ScriptHashNamespace::PlutusScript.scrip_hash_prefix(), + ]); bytes.extend_from_slice(&self.0); ScriptHash::from(blake2b224(bytes.as_ref())) } From 91184eccf696585f73463089ba202c0c1df800a5 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 18 May 2022 14:45:49 +0300 Subject: [PATCH 05/11] tests --- rust/src/tx_builder.rs | 130 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 125 insertions(+), 5 deletions(-) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index c7672952..c3875328 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -203,6 +203,15 @@ impl PlutusWitnesses { } } +impl From> for PlutusWitnesses { + fn from(scripts: Vec) -> Self { + scripts.iter().fold(PlutusWitnesses::new(), |mut scripts, s| { + scripts.add(s); + scripts + }) + } +} + #[wasm_bindgen] #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] pub struct PlutusWitness { @@ -214,8 +223,12 @@ pub struct PlutusWitness { #[wasm_bindgen] impl PlutusWitness { - pub fn new(script: PlutusScript, datum: PlutusData, redeemer: Redeemer) -> Self { - Self { script, datum, redeemer } + pub fn new(script: &PlutusScript, datum: &PlutusData, redeemer: &Redeemer) -> Self { + Self { + script: script.clone(), + datum: datum.clone(), + redeemer: redeemer.clone(), + } } pub fn script(&self) -> PlutusScript { @@ -1612,10 +1625,12 @@ mod tests { Bip32PrivateKey::from_bip39_entropy(&entropy, &[]) } + fn fake_bytes(x: u8) -> Vec { + vec![x, 239, 181, 120, 142, 135, 19, 200, 68, 223, 211, 43, 46, 145, 222, 30, 48, 159, 239, 255, 213, 85, 248, 39, 204, 158, 225, 100] + } + fn fake_key_hash(x: u8) -> Ed25519KeyHash { - Ed25519KeyHash::from_bytes( - vec![x, 239, 181, 120, 142, 135, 19, 200, 68, 223, 211, 43, 46, 145, 222, 30, 48, 159, 239, 255, 213, 85, 248, 39, 204, 158, 225, 100] - ).unwrap() + Ed25519KeyHash::from_bytes(fake_bytes(x)).unwrap() } fn harden(index: u32) -> u32 { @@ -3947,6 +3962,11 @@ mod tests { (m, p) } + fn plutus_script_and_hash(x: u8) -> (PlutusScript, ScriptHash) { + let s = PlutusScript::new(fake_bytes(x)); + (s.clone(), s.hash()) + } + #[test] fn set_mint_asset_with_empty_mint() { let mut tx_builder = create_default_tx_builder(); @@ -4677,5 +4697,105 @@ mod tests { // Ok to build when all witnesses are added assert!(tx_builder.build_tx().is_ok()); } + + #[test] + fn test_adding_plutus_script_input() { + let mut tx_builder = create_reallistic_tx_builder(); + let (script1, _) = plutus_script_and_hash(0); + let datum = PlutusData::new_bytes(fake_bytes(1)); + let redeemer_datum = PlutusData::new_bytes(fake_bytes(2)); + let redeemer = Redeemer::new( + &RedeemerTag::new_spend(), + &to_bignum(0), + &redeemer_datum, + &ExUnits::new(&to_bignum(1), &to_bignum(2)), + ); + tx_builder.add_plutus_script_input( + &PlutusWitness::new(&script1, &datum, &redeemer), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + tx_builder.set_fee(&to_bignum(42)); + // There are no missing script witnesses + assert_eq!(tx_builder.count_missing_input_scripts(), 0); + let tx: Transaction = tx_builder.build_tx_unsafe().unwrap(); + assert!(tx.witness_set.plutus_scripts.is_some()); + assert_eq!(tx.witness_set.plutus_scripts.unwrap().get(0), script1); + assert!(tx.witness_set.plutus_data.is_some()); + assert_eq!(tx.witness_set.plutus_data.unwrap().get(0), datum); + assert!(tx.witness_set.redeemers.is_some()); + assert_eq!(tx.witness_set.redeemers.unwrap().get(0), redeemer); + } + + #[test] + fn test_adding_plutus_script_witnesses() { + let mut tx_builder = create_reallistic_tx_builder(); + tx_builder.set_fee(&to_bignum(42)); + let (script1, hash1) = plutus_script_and_hash(0); + let (script2, hash2) = plutus_script_and_hash(1); + let (script3, hash3) = plutus_script_and_hash(3); + let datum1 = PlutusData::new_bytes(fake_bytes(10)); + let datum2 = PlutusData::new_bytes(fake_bytes(11)); + let redeemer1 = Redeemer::new( + &RedeemerTag::new_spend(), + &to_bignum(0), + &PlutusData::new_bytes(fake_bytes(20)), + &ExUnits::new(&to_bignum(1), &to_bignum(2)), + ); + let redeemer2 = Redeemer::new( + &RedeemerTag::new_spend(), + &to_bignum(1), + &PlutusData::new_bytes(fake_bytes(21)), + &ExUnits::new(&to_bignum(1), &to_bignum(2)), + ); + tx_builder.add_input( + &create_base_address_from_script_hash(&hash1), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + tx_builder.add_input( + &create_base_address_from_script_hash(&hash2), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + // There are TWO missing script witnesses + assert_eq!(tx_builder.count_missing_input_scripts(), 2); + // Calling to add two plutus witnesses, one of which is irrelevant + tx_builder.add_required_plutus_input_scripts( + &PlutusWitnesses::from(vec![ + PlutusWitness::new(&script1, &datum1, &redeemer1), + PlutusWitness::new(&script3, &datum2, &redeemer2), + ]), + ); + // There is now ONE missing script witnesses + assert_eq!(tx_builder.count_missing_input_scripts(), 1); + // Calling to add the one remaining relevant plutus witness now + tx_builder.add_required_plutus_input_scripts( + &PlutusWitnesses::from(vec![ + PlutusWitness::new(&script2, &datum2, &redeemer2), + ]), + ); + // There is now no missing script witnesses + assert_eq!(tx_builder.count_missing_input_scripts(), 0); + let tx: Transaction = tx_builder.build_tx_unsafe().unwrap(); + // Check there are two correct scripts + assert!(tx.witness_set.plutus_scripts.is_some()); + let pscripts = tx.witness_set.plutus_scripts.unwrap(); + assert_eq!(pscripts.len(), 2); + assert_eq!(pscripts.get(0), script1); + assert_eq!(pscripts.get(1), script2); + // Check there are two correct datums + assert!(tx.witness_set.plutus_data.is_some()); + let datums = tx.witness_set.plutus_data.unwrap(); + assert_eq!(datums.len(), 2); + assert_eq!(datums.get(0), datum1); + assert_eq!(datums.get(1), datum2); + // Check there are two correct redeemers + assert!(tx.witness_set.redeemers.is_some()); + let redeems = tx.witness_set.redeemers.unwrap(); + assert_eq!(redeems.len(), 2); + assert_eq!(redeems.get(0), redeemer1); + assert_eq!(redeems.get(1), redeemer2); + } } From 2d625f2da3ae130b5fef3adf50faf50a7e55e208 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 18 May 2022 15:34:45 +0300 Subject: [PATCH 06/11] Added `TxBuilderConstants.plutus_default_cost_models` plus tests --- rust/pkg/cardano_serialization_lib.js.flow | 10 +++ rust/src/lib.rs | 1 + rust/src/plutus.rs | 18 +++++ rust/src/tx_builder.rs | 82 +++++++++++++++++++++- rust/src/tx_builder_constants.rs | 43 ++++++++++++ 5 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 rust/src/tx_builder_constants.rs diff --git a/rust/pkg/cardano_serialization_lib.js.flow b/rust/pkg/cardano_serialization_lib.js.flow index 3bc770ea..9955fb1f 100644 --- a/rust/pkg/cardano_serialization_lib.js.flow +++ b/rust/pkg/cardano_serialization_lib.js.flow @@ -6336,6 +6336,16 @@ declare export class TransactionWitnessSets { */ add(elem: TransactionWitnessSet): void; } +/** + */ +declare export class TxBuilderConstants { + free(): void; + + /** + * @returns {Costmdls} + */ + static plutus_default_cost_models(): Costmdls; +} /** */ declare export class URL { diff --git a/rust/src/lib.rs b/rust/src/lib.rs index de76b295..e87ce644 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -48,6 +48,7 @@ pub mod output_builder; pub mod plutus; pub mod serialization; pub mod tx_builder; +pub mod tx_builder_constants; pub mod typed_bytes; pub mod emip3; #[macro_use] diff --git a/rust/src/plutus.rs b/rust/src/plutus.rs index 07bc9dc5..383da0a8 100644 --- a/rust/src/plutus.rs +++ b/rust/src/plutus.rs @@ -161,6 +161,12 @@ impl CostModel { } } +impl From<[i32; 166]> for CostModel { + fn from(values: [i32; 166]) -> Self { + CostModel(values.iter().map(|x| { Int::new_i32(*x).clone() }).collect()) + } +} + #[wasm_bindgen] #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] pub struct Costmdls(std::collections::BTreeMap); @@ -498,6 +504,12 @@ impl PlutusList { } } +impl From> for PlutusList { + fn from(elems: Vec) -> Self { + Self { elems, definite_encoding: None } + } +} + #[wasm_bindgen] #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] pub struct Redeemer { @@ -609,6 +621,12 @@ impl Redeemers { } } +impl From> for Redeemers { + fn from(values: Vec) -> Self { + Self(values) + } +} + #[wasm_bindgen] #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] pub struct Strings(Vec); diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index c3875328..20aca4fd 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -1608,6 +1608,7 @@ impl TransactionBuilder { mod tests { use super::*; use fees::*; + use crate::tx_builder_constants::TxBuilderConstants; use super::output_builder::{TransactionOutputBuilder}; const MAX_VALUE_SIZE: u32 = 4000; @@ -1626,11 +1627,11 @@ mod tests { } fn fake_bytes(x: u8) -> Vec { - vec![x, 239, 181, 120, 142, 135, 19, 200, 68, 223, 211, 43, 46, 145, 222, 30, 48, 159, 239, 255, 213, 85, 248, 39, 204, 158, 225, 100] + vec![x, 239, 181, 120, 142, 135, 19, 200, 68, 223, 211, 43, 46, 145, 222, 30, 48, 159, 239, 255, 213, 85, 248, 39, 204, 158, 225, 100, 1, 2, 3, 4] } fn fake_key_hash(x: u8) -> Ed25519KeyHash { - Ed25519KeyHash::from_bytes(fake_bytes(x)).unwrap() + Ed25519KeyHash::from_bytes((&fake_bytes(x)[0..28]).to_vec()).unwrap() } fn harden(index: u32) -> u32 { @@ -4797,5 +4798,82 @@ mod tests { assert_eq!(redeems.get(0), redeemer1); assert_eq!(redeems.get(1), redeemer2); } + + #[test] + fn test_existing_plutus_scripts_require_data_hash() { + let mut tx_builder = create_reallistic_tx_builder(); + tx_builder.set_fee(&to_bignum(42)); + let (script1, _) = plutus_script_and_hash(0); + let datum = PlutusData::new_bytes(fake_bytes(1)); + let redeemer_datum = PlutusData::new_bytes(fake_bytes(2)); + let redeemer = Redeemer::new( + &RedeemerTag::new_spend(), + &to_bignum(0), + &redeemer_datum, + &ExUnits::new(&to_bignum(1), &to_bignum(2)), + ); + tx_builder.add_plutus_script_input( + &PlutusWitness::new(&script1, &datum, &redeemer), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + + // Using SAFE `.build_tx` + let res = tx_builder.build_tx(); + assert!(res.is_err()); + if let Err(e) = res { + assert!(e.as_string().unwrap().contains("script data hash")); + } + + // Setting script data hash removes the error + tx_builder.set_script_data_hash( + &ScriptDataHash::from_bytes(fake_bytes(42)).unwrap(), + ); + // Using SAFE `.build_tx` + let res2 = tx_builder.build_tx(); + assert!(res2.is_ok()); + + // Removing script data hash will cause error again + tx_builder.remove_script_data_hash(); + // Using SAFE `.build_tx` + let res3 = tx_builder.build_tx(); + assert!(res3.is_err()); + } + + #[test] + fn test_calc_script_hash_data() { + let mut tx_builder = create_reallistic_tx_builder(); + tx_builder.set_fee(&to_bignum(42)); + let (script1, _) = plutus_script_and_hash(0); + let datum = PlutusData::new_bytes(fake_bytes(1)); + let redeemer_datum = PlutusData::new_bytes(fake_bytes(2)); + let redeemer = Redeemer::new( + &RedeemerTag::new_spend(), + &to_bignum(0), + &redeemer_datum, + &ExUnits::new(&to_bignum(1), &to_bignum(2)), + ); + tx_builder.add_plutus_script_input( + &PlutusWitness::new(&script1, &datum, &redeemer), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + + // Setting script data hash removes the error + tx_builder.calc_script_data_hash( + &TxBuilderConstants::plutus_default_cost_models(), + ); + + // Using SAFE `.build_tx` + let res2 = tx_builder.build_tx(); + assert!(res2.is_ok()); + + let data_hash = hash_script_data( + &Redeemers::from(vec![redeemer.clone()]), + &TxBuilderConstants::plutus_default_cost_models(), + Some(PlutusList::from(vec![datum])), + ); + assert_eq!(tx_builder.script_data_hash.unwrap(), data_hash); + } } diff --git a/rust/src/tx_builder_constants.rs b/rust/src/tx_builder_constants.rs new file mode 100644 index 00000000..3e2c147b --- /dev/null +++ b/rust/src/tx_builder_constants.rs @@ -0,0 +1,43 @@ +use super::*; +use crate::Epoch; +use crate::plutus::{Costmdls, CostModel, Language}; + +// The first element is the cost model, which is an array of 166 operations costs, ordered by asc operaion names. +// The second value is the pre-calculated `language_views_encoding` value required for the script hash creation. +// The cost-model values are taken from the genesis block - https://github.com/input-output-hk/cardano-node/blob/master/configuration/cardano/mainnet-alonzo-genesis.json#L26-L195 +// The keys on the genesis block object (operation names) are sorted plain alphabetically. +const PLUTUS_DEFAULT_COST_MODELS: [([i32; 166], &str); 1] = [ + ( + [197209, 0, 1, 1, 396231, 621, 0, 1, 150000, 1000, 0, 1, 150000, 32, 2477736, 29175, 4, 29773, 100, 29773, 100, 29773, 100, 29773, 100, 29773, 100, 29773, 100, 100, 100, 29773, 100, 150000, 32, 150000, 32, 150000, 32, 150000, 1000, 0, 1, 150000, 32, 150000, 1000, 0, 8, 148000, 425507, 118, 0, 1, 1, 150000, 1000, 0, 8, 150000, 112536, 247, 1, 150000, 10000, 1, 136542, 1326, 1, 1000, 150000, 1000, 1, 150000, 32, 150000, 32, 150000, 32, 1, 1, 150000, 1, 150000, 4, 103599, 248, 1, 103599, 248, 1, 145276, 1366, 1, 179690, 497, 1, 150000, 32, 150000, 32, 150000, 32, 150000, 32, 150000, 32, 150000, 32, 148000, 425507, 118, 0, 1, 1, 61516, 11218, 0, 1, 150000, 32, 148000, 425507, 118, 0, 1, 1, 148000, 425507, 118, 0, 1, 1, 2477736, 29175, 4, 0, 82363, 4, 150000, 5000, 0, 1, 150000, 32, 197209, 0, 1, 1, 150000, 32, 150000, 32, 150000, 32, 150000, 32, 150000, 32, 150000, 32, 150000, 32, 3345831, 1, 1], + "a141005901d59f1a000302590001011a00060bc719026d00011a000249f01903e800011a000249f018201a0025cea81971f70419744d186419744d186419744d186419744d186419744d186419744d18641864186419744d18641a000249f018201a000249f018201a000249f018201a000249f01903e800011a000249f018201a000249f01903e800081a000242201a00067e2318760001011a000249f01903e800081a000249f01a0001b79818f7011a000249f0192710011a0002155e19052e011903e81a000249f01903e8011a000249f018201a000249f018201a000249f0182001011a000249f0011a000249f0041a000194af18f8011a000194af18f8011a0002377c190556011a0002bdea1901f1011a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a000242201a00067e23187600010119f04c192bd200011a000249f018201a000242201a00067e2318760001011a000242201a00067e2318760001011a0025cea81971f704001a000141bb041a000249f019138800011a000249f018201a000302590001011a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a00330da70101ff", + ), +]; + +#[wasm_bindgen] +pub struct TxBuilderConstants(); + +#[wasm_bindgen] +impl TxBuilderConstants { + + pub fn plutus_default_cost_models() -> Costmdls { + let mut res = Costmdls::new(); + res.insert( + &Language::new_plutus_v1(), + &CostModel::from(PLUTUS_DEFAULT_COST_MODELS[0].0), + ); + res + } +} + +#[cfg(test)] +mod tests { + use crate::tx_builder_constants::*; + + #[test] + pub fn cost_model_test() { + assert_eq!( + hex::encode(TxBuilderConstants::plutus_default_cost_models().language_views_encoding()), + PLUTUS_DEFAULT_COST_MODELS[0].1, + ); + } +} \ No newline at end of file From 789749c1de0154c51304b64233a7e68d9f1ea957 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 18 May 2022 19:23:06 +0300 Subject: [PATCH 07/11] Updated `ScriptHashNamespace` enum definition --- rust/src/lib.rs | 15 +++------------ rust/src/plutus.rs | 2 +- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/rust/src/lib.rs b/rust/src/lib.rs index e87ce644..cb9475d2 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1771,17 +1771,8 @@ to_from_bytes!(NativeScript); #[wasm_bindgen] #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] pub enum ScriptHashNamespace { - NativeScript, - PlutusScript, -} - -impl ScriptHashNamespace { - pub fn scrip_hash_prefix(&self) -> u8 { - match self { - ScriptHashNamespace::NativeScript => 0 as u8, - ScriptHashNamespace::PlutusScript => 1 as u8, - } - } + NativeScript = 0, + PlutusScript = 1, } #[wasm_bindgen] @@ -1790,7 +1781,7 @@ impl NativeScript { pub fn hash(&self) -> ScriptHash { let mut bytes = Vec::with_capacity(self.to_bytes().len() + 1); bytes.extend_from_slice(&vec![ - ScriptHashNamespace::NativeScript.scrip_hash_prefix(), + ScriptHashNamespace::NativeScript as u8, ]); bytes.extend_from_slice(&self.to_bytes()); ScriptHash::from(blake2b224(bytes.as_ref())) diff --git a/rust/src/plutus.rs b/rust/src/plutus.rs index 383da0a8..601bccf3 100644 --- a/rust/src/plutus.rs +++ b/rust/src/plutus.rs @@ -35,7 +35,7 @@ impl PlutusScript { pub fn hash(&self) -> ScriptHash { let mut bytes = Vec::with_capacity(self.0.len() + 1); bytes.extend_from_slice(&vec![ - ScriptHashNamespace::PlutusScript.scrip_hash_prefix(), + ScriptHashNamespace::PlutusScript as u8, ]); bytes.extend_from_slice(&self.0); ScriptHash::from(blake2b224(bytes.as_ref())) From 941aca334f18c10e41311a0c23347d17beee8972 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 18 May 2022 19:51:22 +0300 Subject: [PATCH 08/11] tests --- rust/src/tx_builder.rs | 69 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 20aca4fd..b82ffc95 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -4875,5 +4875,74 @@ mod tests { ); assert_eq!(tx_builder.script_data_hash.unwrap(), data_hash); } + + #[test] + fn test_plutus_witness_redeemer_index_auto_changing() { + let mut tx_builder = create_reallistic_tx_builder(); + tx_builder.set_fee(&to_bignum(42)); + let (script1, _) = plutus_script_and_hash(0); + let (script2, _) = plutus_script_and_hash(1); + let datum1 = PlutusData::new_bytes(fake_bytes(10)); + let datum2 = PlutusData::new_bytes(fake_bytes(11)); + + // Creating redeemers with indexes ZERO + let redeemer1 = Redeemer::new( + &RedeemerTag::new_spend(), + &to_bignum(0), + &PlutusData::new_bytes(fake_bytes(20)), + &ExUnits::new(&to_bignum(1), &to_bignum(2)), + ); + let redeemer2 = Redeemer::new( + &RedeemerTag::new_spend(), + &to_bignum(0), + &PlutusData::new_bytes(fake_bytes(21)), + &ExUnits::new(&to_bignum(1), &to_bignum(2)), + ); + + // Add a regular NON-script input first + tx_builder.add_input( + &byron_address(), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + + // Adding two plutus inputs then + // both have redeemers with index ZERO + tx_builder.add_plutus_script_input( + &PlutusWitness::new(&script1, &datum1, &redeemer1), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + tx_builder.add_plutus_script_input( + &PlutusWitness::new(&script2, &datum2, &redeemer2), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + + // Calc the script data hash + tx_builder.calc_script_data_hash( + &TxBuilderConstants::plutus_default_cost_models(), + ); + + let tx: Transaction = tx_builder.build_tx().unwrap(); + assert!(tx.witness_set.redeemers.is_some()); + let redeems = tx.witness_set.redeemers.unwrap(); + assert_eq!(redeems.len(), 2); + + fn compare_redeems(r1: Redeemer, r2: Redeemer) { + assert_eq!(r1.tag(), r2.tag()); + assert_eq!(r1.data(), r2.data()); + assert_eq!(r1.ex_units(), r2.ex_units()); + } + + compare_redeems(redeems.get(0), redeemer1); + compare_redeems(redeems.get(1), redeemer2); + + // Note the redeemers from the result transaction are equal with source redeemers + // In everything EXCEPT the index field, the indexes have changed to 1 and 2 + // To match the position of their corresponding input + assert_eq!(redeems.get(0).index(), to_bignum(1)); + assert_eq!(redeems.get(1).index(), to_bignum(2)); + } } From 2b4b853848e94938dbe2781e95186f7c817a3dcd Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Thu, 19 May 2022 11:25:52 +0300 Subject: [PATCH 09/11] renamed `WitnessType` to `ScriptWitnessType` --- rust/src/tx_builder.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index b82ffc95..91f5c002 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -253,7 +253,7 @@ impl PlutusWitness { } #[derive(Clone, Debug)] -enum WitnessType { +enum ScriptWitnessType { NativeScriptWitness(NativeScript), PlutusScriptWitness(PlutusWitness), } @@ -262,7 +262,7 @@ enum WitnessType { #[derive(Clone, Debug)] struct MockWitnessSet { vkeys: BTreeSet, - scripts: LinkedHashMap>, + scripts: LinkedHashMap>, bootstraps: BTreeSet>, } @@ -685,7 +685,7 @@ impl TransactionBuilder { self.add_script_input(&hash, input, amount); self.input_types.scripts.insert( hash, - Some(WitnessType::NativeScriptWitness(script.clone())), + Some(ScriptWitnessType::NativeScriptWitness(script.clone())), ); } @@ -695,7 +695,7 @@ impl TransactionBuilder { self.add_script_input(&hash, input, amount); self.input_types.scripts.insert( hash, - Some(WitnessType::PlutusScriptWitness(witness.clone())), + Some(ScriptWitnessType::PlutusScriptWitness(witness.clone())), ); } @@ -775,7 +775,7 @@ impl TransactionBuilder { if self.input_types.scripts.contains_key(&hash) { self.input_types.scripts.insert( hash, - Some(WitnessType::NativeScriptWitness(s.clone())), + Some(ScriptWitnessType::NativeScriptWitness(s.clone())), ); } }); @@ -792,7 +792,7 @@ impl TransactionBuilder { if self.input_types.scripts.contains_key(&hash) { self.input_types.scripts.insert( hash, - Some(WitnessType::PlutusScriptWitness(s.clone())), + Some(ScriptWitnessType::PlutusScriptWitness(s.clone())), ); } }); @@ -803,7 +803,7 @@ impl TransactionBuilder { pub fn get_native_input_scripts(&self) -> Option { let mut scripts = NativeScripts::new(); self.input_types.scripts.values().for_each(|option| { - if let Some(WitnessType::NativeScriptWitness(s)) = option { + if let Some(ScriptWitnessType::NativeScriptWitness(s)) = option { scripts.add(&s); } }); @@ -833,7 +833,7 @@ impl TransactionBuilder { }); let mut scripts = PlutusWitnesses::new(); self.input_types.scripts.iter().for_each(|(hash, option)| { - if let Some(WitnessType::PlutusScriptWitness(s)) = option { + if let Some(ScriptWitnessType::PlutusScriptWitness(s)) = option { if let Some(idx) = script_hash_index_map.get(&hash) { scripts.add(&s.clone_with_redeemer_index(&idx)); } From efa10aebbfa2583b6de2237614dc26131af519de Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Thu, 19 May 2022 12:05:26 +0300 Subject: [PATCH 10/11] tests --- rust/src/tx_builder.rs | 106 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 91f5c002..43b9db57 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -4944,5 +4944,111 @@ mod tests { assert_eq!(redeems.get(0).index(), to_bignum(1)); assert_eq!(redeems.get(1).index(), to_bignum(2)); } + + #[test] + fn test_native_and_plutus_scripts_together() { + let mut tx_builder = create_reallistic_tx_builder(); + tx_builder.set_fee(&to_bignum(42)); + let (pscript1, _) = plutus_script_and_hash(0); + let (pscript2, phash2) = plutus_script_and_hash(1); + let (nscript1, _) = mint_script_and_policy(0); + let (nscript2, nhash2) = mint_script_and_policy(1); + let datum1 = PlutusData::new_bytes(fake_bytes(10)); + let datum2 = PlutusData::new_bytes(fake_bytes(11)); + // Creating redeemers with indexes ZERO + let redeemer1 = Redeemer::new( + &RedeemerTag::new_spend(), + &to_bignum(0), + &PlutusData::new_bytes(fake_bytes(20)), + &ExUnits::new(&to_bignum(1), &to_bignum(2)), + ); + let redeemer2 = Redeemer::new( + &RedeemerTag::new_spend(), + &to_bignum(0), + &PlutusData::new_bytes(fake_bytes(21)), + &ExUnits::new(&to_bignum(1), &to_bignum(2)), + ); + + // Add one plutus input directly with witness + tx_builder.add_plutus_script_input( + &PlutusWitness::new(&pscript1, &datum1, &redeemer1), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + // Add one native input directly with witness + tx_builder.add_native_script_input( + &nscript1, + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + // Add one plutus input generically without witness + tx_builder.add_input( + &create_base_address_from_script_hash(&phash2), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + // Add one native input generically without witness + tx_builder.add_input( + &create_base_address_from_script_hash(&nhash2), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(1_000_000)), + ); + + // There are two missing script witnesses + assert_eq!(tx_builder.count_missing_input_scripts(), 2); + + let remaining1 = tx_builder.add_required_plutus_input_scripts( + &PlutusWitnesses::from(vec![ + PlutusWitness::new(&pscript2, &datum2, &redeemer2), + ]), + ); + + // There is one missing script witness now + assert_eq!(remaining1, 1); + assert_eq!(tx_builder.count_missing_input_scripts(), 1); + + let remaining2 = tx_builder.add_required_native_input_scripts( + &NativeScripts::from(vec![nscript2.clone()]), + ); + + // There are no missing script witnesses now + assert_eq!(remaining2, 0); + assert_eq!(tx_builder.count_missing_input_scripts(), 0); + + tx_builder.calc_script_data_hash( + &TxBuilderConstants::plutus_default_cost_models(), + ); + + let tx: Transaction = tx_builder.build_tx().unwrap(); + + let wits = tx.witness_set; + assert!(wits.native_scripts.is_some()); + assert!(wits.plutus_scripts.is_some()); + assert!(wits.plutus_data.is_some()); + assert!(wits.redeemers.is_some()); + + let nscripts = wits.native_scripts.unwrap(); + assert_eq!(nscripts.len(), 2); + assert_eq!(nscripts.get(0), nscript1); + assert_eq!(nscripts.get(1), nscript2); + + let pscripts = wits.plutus_scripts.unwrap(); + assert_eq!(pscripts.len(), 2); + assert_eq!(pscripts.get(0), pscript1); + assert_eq!(pscripts.get(1), pscript2); + + let datums = wits.plutus_data.unwrap(); + assert_eq!(datums.len(), 2); + assert_eq!(datums.get(0), datum1); + assert_eq!(datums.get(1), datum2); + + let redeems = wits.redeemers.unwrap(); + assert_eq!(redeems.len(), 2); + assert_eq!(redeems.get(0), redeemer1); + + // The second plutus input redeemer index has automatically changed to 2 + // because it was added on the third position + assert_eq!(redeems.get(1), redeemer2.clone_with_index(&to_bignum(2))); + } } From 29d31e11a733c26c0b4a9bbf721792298392e53e Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Thu, 19 May 2022 18:28:28 +0300 Subject: [PATCH 11/11] Removed unused import --- rust/src/tx_builder_constants.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rust/src/tx_builder_constants.rs b/rust/src/tx_builder_constants.rs index 3e2c147b..064cfb69 100644 --- a/rust/src/tx_builder_constants.rs +++ b/rust/src/tx_builder_constants.rs @@ -1,5 +1,4 @@ use super::*; -use crate::Epoch; use crate::plutus::{Costmdls, CostModel, Language}; // The first element is the cost model, which is an array of 166 operations costs, ordered by asc operaion names.