From 94f721a7fe1651fe49a0ae03a1c576e8b4a07733 Mon Sep 17 00:00:00 2001 From: lisicky Date: Thu, 1 Jun 2023 20:23:39 +0400 Subject: [PATCH 1/4] add scripts and withdrawals builders --- rust/pkg/cardano_serialization_lib.js.flow | 274 +++++++++-- rust/src/address.rs | 7 + rust/src/fakes.rs | 5 + rust/src/lib.rs | 84 ++-- rust/src/plutus.rs | 12 +- rust/src/tx_builder.rs | 475 +++++++++++++++++--- rust/src/tx_builder/certificates_builder.rs | 227 ++++++++++ rust/src/tx_builder/mint_builder.rs | 8 +- rust/src/tx_builder/script_structs.rs | 291 ++++++++++++ rust/src/tx_builder/tx_inputs_builder.rs | 268 ++--------- rust/src/tx_builder/withdrawals_builder.rs | 181 ++++++++ rust/src/utils.rs | 7 + 12 files changed, 1459 insertions(+), 380 deletions(-) create mode 100644 rust/src/tx_builder/certificates_builder.rs create mode 100644 rust/src/tx_builder/script_structs.rs create mode 100644 rust/src/tx_builder/withdrawals_builder.rs diff --git a/rust/pkg/cardano_serialization_lib.js.flow b/rust/pkg/cardano_serialization_lib.js.flow index 02e51771..2af783b6 100644 --- a/rust/pkg/cardano_serialization_lib.js.flow +++ b/rust/pkg/cardano_serialization_lib.js.flow @@ -61,33 +61,6 @@ declare export function decode_metadatum_to_json_str( schema: number ): string; -/** - * @param {Transaction} tx - * @param {LinearFee} linear_fee - * @returns {BigNum} - */ -declare export function min_fee(tx: Transaction, linear_fee: LinearFee): BigNum; - -/** - * @param {ExUnits} ex_units - * @param {ExUnitPrices} ex_unit_prices - * @returns {BigNum} - */ -declare export function calculate_ex_units_ceil_cost( - ex_units: ExUnits, - ex_unit_prices: ExUnitPrices -): BigNum; - -/** - * @param {Transaction} tx - * @param {ExUnitPrices} ex_unit_prices - * @returns {BigNum} - */ -declare export function min_script_fee( - tx: Transaction, - ex_unit_prices: ExUnitPrices -): BigNum; - /** * @param {string} password * @param {string} salt @@ -124,6 +97,33 @@ declare export function create_send_all( config: TransactionBuilderConfig ): TransactionBatchList; +/** + * @param {Transaction} tx + * @param {LinearFee} linear_fee + * @returns {BigNum} + */ +declare export function min_fee(tx: Transaction, linear_fee: LinearFee): BigNum; + +/** + * @param {ExUnits} ex_units + * @param {ExUnitPrices} ex_unit_prices + * @returns {BigNum} + */ +declare export function calculate_ex_units_ceil_cost( + ex_units: ExUnits, + ex_unit_prices: ExUnitPrices +): BigNum; + +/** + * @param {Transaction} tx + * @param {ExUnitPrices} ex_unit_prices + * @returns {BigNum} + */ +declare export function min_script_fee( + tx: Transaction, + ex_unit_prices: ExUnitPrices +): BigNum; + /** * @param {TransactionHash} tx_body_hash * @param {ByronAddress} addr @@ -399,6 +399,14 @@ declare export var MetadataJsonSchema: {| +DetailedSchema: 2, // 2 |}; +/** + */ + +declare export var StakeCredKind: {| + +Key: 0, // 0 + +Script: 1, // 1 +|}; + /** */ @@ -412,9 +420,9 @@ declare export var CoinSelectionStrategyCIP2: {| /** */ -declare export var StakeCredKind: {| - +Key: 0, // 0 - +Script: 1, // 1 +declare export var CborContainerType: {| + +Array: 0, // 0 + +Map: 1, // 1 |}; /** @@ -426,14 +434,6 @@ declare export var ScriptSchema: {| +Node: 1, // 1 |}; -/** - */ - -declare export var CborContainerType: {| - +Array: 0, // 0 - +Map: 1, // 1 -|}; - /** */ declare export class Address { @@ -1722,6 +1722,11 @@ declare export class Certificate { * @returns {MoveInstantaneousRewardsCert | void} */ as_move_instantaneous_rewards_cert(): MoveInstantaneousRewardsCert | void; + + /** + * @returns {boolean} + */ + has_required_script_witness(): boolean; } /** */ @@ -1787,6 +1792,75 @@ declare export class Certificates { */ add(elem: Certificate): void; } +/** + */ +declare export class CertificatesBuilder { + free(): void; + + /** + * @returns {CertificatesBuilder} + */ + static new(): CertificatesBuilder; + + /** + * @param {Certificate} cert + */ + add(cert: Certificate): void; + + /** + * @param {Certificate} cert + * @param {PlutusWitness} witness + */ + add_with_plutus_witness(cert: Certificate, witness: PlutusWitness): void; + + /** + * @param {Certificate} cert + * @param {NativeScriptSource} native_script_source + */ + add_with_native_script( + cert: Certificate, + native_script_source: NativeScriptSource + ): void; + + /** + * @returns {PlutusWitnesses} + */ + get_plutus_witnesses(): PlutusWitnesses; + + /** + * @returns {TransactionInputs} + */ + get_ref_inputs(): TransactionInputs; + + /** + * @returns {NativeScripts} + */ + get_native_scripts(): NativeScripts; + + /** + * @param {BigNum} pool_deposit + * @param {BigNum} key_deposit + * @returns {Value} + */ + get_certificates_refund(pool_deposit: BigNum, key_deposit: BigNum): Value; + + /** + * @param {BigNum} pool_deposit + * @param {BigNum} key_deposit + * @returns {BigNum} + */ + get_certificates_deposit(pool_deposit: BigNum, key_deposit: BigNum): BigNum; + + /** + * @returns {boolean} + */ + has_plutus_scripts(): boolean; + + /** + * @returns {Certificates} + */ + build(): Certificates; +} /** */ declare export class ConstrPlutusData { @@ -3970,7 +4044,7 @@ declare export class MintBuilder { /** * @returns {Redeemers} */ - get_redeeemers(): Redeemers; + get_redeemers(): Redeemers; /** * @returns {boolean} @@ -4431,6 +4505,29 @@ declare export class NativeScript { */ get_required_signers(): Ed25519KeyHashes; } +/** + */ +declare export class NativeScriptSource { + free(): void; + + /** + * @param {NativeScript} script + * @returns {NativeScriptSource} + */ + static new(script: NativeScript): NativeScriptSource; + + /** + * @param {ScriptHash} script_hash + * @param {TransactionInput} input + * @param {Ed25519KeyHashes} required_signers + * @returns {NativeScriptSource} + */ + static new_ref_input( + script_hash: ScriptHash, + input: TransactionInput, + required_signers: Ed25519KeyHashes + ): NativeScriptSource; +} /** */ declare export class NativeScripts { @@ -7254,6 +7351,11 @@ declare export class StakeCredential { */ kind(): number; + /** + * @returns {boolean} + */ + has_script_hash(): boolean; + /** * @returns {Uint8Array} */ @@ -7418,6 +7520,11 @@ declare export class StakeDelegation { stake_credential: StakeCredential, pool_keyhash: Ed25519KeyHash ): StakeDelegation; + + /** + * @returns {boolean} + */ + has_script_credentials(): boolean; } /** */ @@ -7472,6 +7579,11 @@ declare export class StakeDeregistration { * @returns {StakeDeregistration} */ static new(stake_credential: StakeCredential): StakeDeregistration; + + /** + * @returns {boolean} + */ + has_script_credentials(): boolean; } /** */ @@ -8370,15 +8482,31 @@ declare export class TransactionBuilder { set_validity_start_interval_bignum(validity_start_interval: BigNum): void; /** + * !!! DEPRECATED !!! + * Can emit error if add a cert with script credential. + * Use set_certs_builder instead. * @param {Certificates} certs */ set_certs(certs: Certificates): void; /** + * @param {CertificatesBuilder} certs + */ + set_certs_builder(certs: CertificatesBuilder): void; + + /** + * !!! DEPRECATED !!! + * Can emit error if add a withdrawal with script credential. + * Use set_withdrawals_builder instead. * @param {Withdrawals} withdrawals */ set_withdrawals(withdrawals: Withdrawals): void; + /** + * @param {WithdrawalsBuilder} withdrawals + */ + set_withdrawals_builder(withdrawals: WithdrawalsBuilder): void; + /** * @returns {AuxiliaryData | void} */ @@ -10504,6 +10632,74 @@ declare export class Withdrawals { */ keys(): RewardAddresses; } +/** + */ +declare export class WithdrawalsBuilder { + free(): void; + + /** + * @returns {WithdrawalsBuilder} + */ + static new(): WithdrawalsBuilder; + + /** + * @param {RewardAddress} address + * @param {BigNum} coin + */ + add(address: RewardAddress, coin: BigNum): void; + + /** + * @param {RewardAddress} address + * @param {BigNum} coin + * @param {PlutusWitness} witness + */ + add_with_plutus_witness( + address: RewardAddress, + coin: BigNum, + witness: PlutusWitness + ): void; + + /** + * @param {RewardAddress} address + * @param {BigNum} coin + * @param {NativeScriptSource} native_script_source + */ + add_with_native_script( + address: RewardAddress, + coin: BigNum, + native_script_source: NativeScriptSource + ): void; + + /** + * @returns {PlutusWitnesses} + */ + get_plutus_witnesses(): PlutusWitnesses; + + /** + * @returns {TransactionInputs} + */ + get_ref_inputs(): TransactionInputs; + + /** + * @returns {NativeScripts} + */ + get_native_scripts(): NativeScripts; + + /** + * @returns {Value} + */ + get_total_withdrawals(): Value; + + /** + * @returns {boolean} + */ + has_plutus_scripts(): boolean; + + /** + * @returns {Withdrawals} + */ + build(): Withdrawals; +} export type AddressJSON = string; export type AssetNameJSON = string; export type AssetNamesJSON = string[]; diff --git a/rust/src/address.rs b/rust/src/address.rs index 4cfea39a..59d8009a 100644 --- a/rust/src/address.rs +++ b/rust/src/address.rs @@ -153,6 +153,13 @@ impl StakeCredential { } } + pub fn has_script_hash(&self) -> bool { + match &self.0 { + StakeCredType::Key(_) => false, + StakeCredType::Script(_) => true, + } + } + fn to_raw_bytes(&self) -> Vec { match &self.0 { StakeCredType::Key(hash) => hash.to_bytes(), diff --git a/rust/src/fakes.rs b/rust/src/fakes.rs index ed953089..c846f876 100644 --- a/rust/src/fakes.rs +++ b/rust/src/fakes.rs @@ -1,5 +1,6 @@ #![allow(dead_code)] use crate::{to_bignum, Address, BaseAddress, Bip32PrivateKey, DataHash, Ed25519KeyHash, Ed25519Signature, NetworkInfo, StakeCredential, TransactionHash, TransactionIndex, TransactionInput, TransactionOutput, Value, Vkey, PolicyID}; +use crate::crypto::ScriptHash; pub(crate) fn fake_bytes_32(x: u8) -> Vec { vec![ @@ -16,6 +17,10 @@ pub(crate) fn fake_key_hash(x: u8) -> Ed25519KeyHash { Ed25519KeyHash::from_bytes((&fake_bytes_32(x)[0..28]).to_vec()).unwrap() } +pub(crate) fn fake_script_hash(x: u8) -> ScriptHash { + ScriptHash::from_bytes((&fake_bytes_32(x)[0..28]).to_vec()).unwrap() +} + pub(crate) fn fake_base_address(x: u8) -> Address { BaseAddress::new( NetworkInfo::testnet().network_id(), diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 196d9e30..f10515a2 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -78,7 +78,7 @@ type DeltaCoin = Int; #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct UnitInterval { numerator: BigNum, @@ -697,7 +697,7 @@ impl PartialEq for TransactionOutput { #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct StakeRegistration { stake_credential: StakeCredential, @@ -720,10 +720,10 @@ impl StakeRegistration { #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct StakeDeregistration { - stake_credential: StakeCredential, + pub(crate) stake_credential: StakeCredential, } impl_to_from!(StakeDeregistration); @@ -739,11 +739,15 @@ impl StakeDeregistration { stake_credential: stake_credential.clone(), } } + + pub fn has_script_credentials(&self) -> bool { + self.stake_credential.has_script_hash() + } } #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct StakeDelegation { stake_credential: StakeCredential, @@ -768,13 +772,17 @@ impl StakeDelegation { pool_keyhash: pool_keyhash.clone(), } } + + pub fn has_script_credentials(&self) -> bool { + self.stake_credential.has_script_hash() + } } #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] -pub struct Ed25519KeyHashes(Vec); +pub struct Ed25519KeyHashes(pub(crate) Vec); impl_to_from!(Ed25519KeyHashes); @@ -805,9 +813,18 @@ impl Ed25519KeyHashes { } } +impl IntoIterator for Ed25519KeyHashes { + type Item = Ed25519KeyHash; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct Relays(Vec); @@ -834,7 +851,7 @@ impl Relays { #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct PoolParams { operator: Ed25519KeyHash, @@ -915,7 +932,7 @@ impl PoolParams { #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct PoolRegistration { pool_params: PoolParams, @@ -938,7 +955,7 @@ impl PoolRegistration { #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct PoolRetirement { pool_keyhash: Ed25519KeyHash, @@ -967,7 +984,7 @@ impl PoolRetirement { #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct GenesisKeyDelegation { genesishash: GenesisHash, @@ -1006,7 +1023,7 @@ impl GenesisKeyDelegation { #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct MoveInstantaneousRewardsCert { move_instantaneous_reward: MoveInstantaneousReward, @@ -1040,7 +1057,7 @@ pub enum CertificateKind { } #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub enum CertificateEnum { StakeRegistration(StakeRegistration), @@ -1054,7 +1071,7 @@ pub enum CertificateEnum { #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct Certificate(CertificateEnum); @@ -1162,6 +1179,14 @@ impl Certificate { _ => None, } } + + pub fn has_required_script_witness(&self) -> bool { + match &self.0 { + CertificateEnum::StakeDeregistration(x) => x.has_script_credentials(), + CertificateEnum::StakeDelegation(x) => x.has_script_credentials(), + _ => false, + } + } } #[wasm_bindgen] @@ -1169,6 +1194,7 @@ impl Certificate { Clone, Copy, Debug, + Hash, Eq, Ord, PartialEq, @@ -1183,7 +1209,7 @@ pub enum MIRPot { } #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub enum MIREnum { ToOtherPot(Coin), @@ -1200,7 +1226,7 @@ pub enum MIRKind { } #[wasm_bindgen] -#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] pub struct MIRToStakeCredentials { rewards: linked_hash_map::LinkedHashMap, } @@ -1278,7 +1304,7 @@ impl JsonSchema for MIRToStakeCredentials { #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct MoveInstantaneousReward { pot: MIRPot, @@ -1333,7 +1359,7 @@ type Port = u16; #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct Ipv4([u8; 4]); @@ -1363,7 +1389,7 @@ impl Ipv4 { #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct Ipv6([u8; 16]); @@ -1395,7 +1421,7 @@ static URL_MAX_LEN: usize = 64; #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct URL(String); @@ -1431,7 +1457,7 @@ static DNS_NAME_MAX_LEN: usize = 64; #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct DNSRecordAorAAAA(String); @@ -1465,7 +1491,7 @@ impl DNSRecordAorAAAA { #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct DNSRecordSRV(String); @@ -1499,7 +1525,7 @@ impl DNSRecordSRV { #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct SingleHostAddr { port: Option, @@ -1534,7 +1560,7 @@ impl SingleHostAddr { #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct SingleHostName { port: Option, @@ -1563,7 +1589,7 @@ impl SingleHostName { #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct MultiHostName { dns_name: DNSRecordSRV, @@ -1593,7 +1619,7 @@ pub enum RelayKind { } #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub enum RelayEnum { SingleHostAddr(SingleHostAddr), @@ -1603,7 +1629,7 @@ pub enum RelayEnum { #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct Relay(RelayEnum); @@ -1655,7 +1681,7 @@ impl Relay { #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct PoolMetadata { url: URL, diff --git a/rust/src/plutus.rs b/rust/src/plutus.rs index 9e79027a..b32b6fae 100644 --- a/rust/src/plutus.rs +++ b/rust/src/plutus.rs @@ -948,6 +948,15 @@ impl Redeemer { ex_units: self.ex_units.clone(), } } + + pub(crate) fn clone_with_index_and_tag(&self, index: &BigNum, tag: &RedeemerTag) -> Self { + Self { + tag: tag.clone(), + index: index.clone(), + data: self.data.clone(), + ex_units: self.ex_units.clone(), + } + } } #[wasm_bindgen] @@ -955,6 +964,7 @@ impl Redeemer { Copy, Clone, Debug, + Hash, Eq, Ord, PartialEq, @@ -972,7 +982,7 @@ pub enum RedeemerTagKind { #[wasm_bindgen] #[derive( - Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, + Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema, )] pub struct RedeemerTag(RedeemerTagKind); diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 074a6f9f..03fa1632 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -6,7 +6,10 @@ mod test_batch; pub mod tx_inputs_builder; pub mod tx_batch_builder; pub mod mint_builder; +pub mod certificates_builder; +pub mod withdrawals_builder; mod batch_tools; +mod script_structs; use super::fees; use super::output_builder::TransactionOutputAmountBuilder; @@ -15,39 +18,11 @@ use super::*; use crate::tx_builder::tx_inputs_builder::{get_bootstraps, TxInputsBuilder}; use linked_hash_map::LinkedHashMap; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; -use tx_inputs_builder::{PlutusWitness, PlutusWitnesses}; +use crate::tx_builder::certificates_builder::CertificatesBuilder; +use crate::tx_builder::withdrawals_builder::WithdrawalsBuilder; +use crate::tx_builder::script_structs::{PlutusWitness, PlutusWitnesses}; use crate::tx_builder::mint_builder::{MintBuilder, MintWitness}; -// comes from witsVKeyNeeded in the Ledger spec -fn witness_keys_for_cert(cert_enum: &Certificate) -> RequiredSigners { - let mut set = RequiredSigners::new(); - match &cert_enum.0 { - // stake key registrations do not require a witness - CertificateEnum::StakeRegistration(_cert) => {} - CertificateEnum::StakeDeregistration(cert) => { - set.add(&cert.stake_credential().to_keyhash().unwrap()); - } - CertificateEnum::StakeDelegation(cert) => { - set.add(&cert.stake_credential().to_keyhash().unwrap()); - } - CertificateEnum::PoolRegistration(cert) => { - for owner in &cert.pool_params().pool_owners().0 { - set.add(&owner.clone()); - } - set.add(&Ed25519KeyHash::from_bytes(cert.pool_params().operator().to_bytes()).unwrap()); - } - CertificateEnum::PoolRetirement(cert) => { - set.add(&Ed25519KeyHash::from_bytes(cert.pool_keyhash().to_bytes()).unwrap()); - } - CertificateEnum::GenesisKeyDelegation(cert) => { - set.add(&Ed25519KeyHash::from_bytes(cert.genesis_delegate_hash().to_bytes()).unwrap()); - } - // not witness as there is no single core node or genesis key that posts the certificate - CertificateEnum::MoveInstantaneousRewardsCert(_cert) => {} - } - set -} - pub(crate) fn fake_private_key() -> Bip32PrivateKey { Bip32PrivateKey::from_bytes(&[ 0xb8, 0xf2, 0xbe, 0xce, 0x9b, 0xdf, 0xe2, 0xb0, 0x28, 0x2f, 0x5b, 0xad, 0x70, 0x55, 0x62, @@ -86,6 +61,12 @@ fn count_needed_vkeys(tx_builder: &TransactionBuilder) -> usize { if let Some(mint_builder) = &tx_builder.mint { input_hashes.extend(RequiredSignersSet::from(&mint_builder.get_native_scripts())); } + if let Some(withdrawals_builder) = &tx_builder.withdrawals { + input_hashes.extend(withdrawals_builder.get_required_signers()); + } + if let Some(certs_builder) = &tx_builder.certs { + input_hashes.extend(certs_builder.get_required_signers()); + } input_hashes.len() } @@ -352,8 +333,8 @@ pub struct TransactionBuilder { outputs: TransactionOutputs, fee: Option, ttl: Option, // absolute slot number - certs: Option, - withdrawals: Option, + certs: Option, + withdrawals: Option, auxiliary_data: Option, validity_start_interval: Option, mint: Option, @@ -973,20 +954,48 @@ impl TransactionBuilder { self.validity_start_interval = Some(validity_start_interval.clone()) } - pub fn set_certs(&mut self, certs: &Certificates) { - self.certs = Some(certs.clone()); + /// !!! DEPRECATED !!! + /// Can emit error if add a cert with script credential. + /// Use set_certs_builder instead. + #[deprecated( + since = "11.4.1", + note = "Can emit error if add a cert with script credential. Use set_certs_builder instead." + )] + pub fn set_certs(&mut self, certs: &Certificates) -> Result<(), JsError> { + let mut builder = CertificatesBuilder::new(); for cert in &certs.0 { - self.inputs - .add_required_signers(&witness_keys_for_cert(cert)) + builder.add(cert)?; } + + self.certs = Some(builder); + + Ok(()) } - pub fn set_withdrawals(&mut self, withdrawals: &Withdrawals) { - self.withdrawals = Some(withdrawals.clone()); - for (withdrawal, _coin) in &withdrawals.0 { - self.inputs - .add_required_signer(&withdrawal.payment_cred().to_keyhash().unwrap()) + pub fn set_certs_builder(&mut self, certs: &CertificatesBuilder) { + self.certs = Some(certs.clone()); + } + + /// !!! DEPRECATED !!! + /// Can emit error if add a withdrawal with script credential. + /// Use set_withdrawals_builder instead. + #[deprecated( + since = "11.4.1", + note = "Can emit error if add a withdrawal with script credential. Use set_withdrawals_builder instead." + )] + pub fn set_withdrawals(&mut self, withdrawals: &Withdrawals) -> Result<(), JsError>{ + let mut withdrawals_builder = WithdrawalsBuilder::new(); + for(withdrawal, coin) in &withdrawals.0 { + withdrawals_builder.add(&withdrawal, &coin)?; } + + self.withdrawals = Some(withdrawals_builder); + + Ok(()) + } + + pub fn set_withdrawals_builder(&mut self, withdrawals: &WithdrawalsBuilder) { + self.withdrawals = Some(withdrawals.clone()); } pub fn get_auxiliary_data(&self) -> Option { @@ -1259,6 +1268,18 @@ impl TransactionBuilder { } } + if let Some(withdrawals) = &self.withdrawals { + for input in withdrawals.get_ref_inputs().0 { + inputs.insert(input); + } + } + + if let Some(certs) = &self.certs { + for input in certs.get_ref_inputs().0 { + inputs.insert(input); + } + } + let vec_inputs = inputs.into_iter().collect(); TransactionInputs(vec_inputs) } @@ -1274,12 +1295,18 @@ impl TransactionBuilder { /// withdrawals and refunds pub fn get_implicit_input(&self) -> Result { - internal_get_implicit_input( - &self.withdrawals, - &self.certs, - &self.config.pool_deposit, - &self.config.key_deposit, - ) + let mut implicit_input = Value::zero(); + if let Some(withdrawals) = &self.withdrawals { + implicit_input = implicit_input.checked_add(&withdrawals.get_total_withdrawals()?)?; + } + if let Some(refunds) = &self.certs { + implicit_input = implicit_input.checked_add(&refunds.get_certificates_refund( + &self.config.pool_deposit, + &self.config.key_deposit + )?)?; + } + + Ok(implicit_input) } /// Returns mint as tuple of (mint_value, burn_value) or two zero values @@ -1322,11 +1349,14 @@ impl TransactionBuilder { } pub fn get_deposit(&self) -> Result { - internal_get_deposit( - &self.certs, - &self.config.pool_deposit, - &self.config.key_deposit, - ) + if let Some(certs) = &self.certs { + Ok(certs.get_certificates_deposit( + &self.config.pool_deposit, + &self.config.key_deposit, + )?) + } else { + Ok(Coin::zero()) + } } pub fn get_fee_if_set(&self) -> Option { @@ -1718,6 +1748,14 @@ impl TransactionBuilder { used_langs.append(&mut mint_builder.get_used_plutus_lang_versions()); plutus_witnesses.0.append(&mut mint_builder.get_plutus_witnesses().0) } + if let Some(certs_builder) = &self.certs { + used_langs.append(&mut certs_builder.get_used_plutus_lang_versions()); + plutus_witnesses.0.append(&mut certs_builder.get_plutus_witnesses().0) + } + if let Some(mut withdrawals_builder) = self.withdrawals.clone() { + used_langs.append(&mut withdrawals_builder.get_used_plutus_lang_versions()); + plutus_witnesses.0.append(&mut withdrawals_builder.get_plutus_witnesses().0) + } if plutus_witnesses.len() > 0 { let (_scripts, datums, redeemers) = plutus_witnesses.collect(); @@ -1764,23 +1802,18 @@ impl TransactionBuilder { let fee = self .fee .ok_or_else(|| JsError::from_str("Fee not specified"))?; + let built = TransactionBody { inputs: self.inputs.inputs(), outputs: self.outputs.clone(), fee, ttl: self.ttl, - certs: self.certs.clone(), - withdrawals: self.withdrawals.clone(), + certs: self.certs.as_ref().map(|x| x.build()), + withdrawals: self.withdrawals.as_ref().map(|x| x.build()), update: None, - auxiliary_data_hash: match &self.auxiliary_data { - None => None, - Some(x) => Some(utils::hash_auxiliary_data(x)), - }, + auxiliary_data_hash: self.auxiliary_data.as_ref().map(|x| utils::hash_auxiliary_data(x)), validity_start_interval: self.validity_start_interval, - mint: match &self.mint { - None => None, - Some(mint_builder) => Some(mint_builder.build()), - }, + mint: self.mint.as_ref().map(|x| x.build()), script_data_hash: self.script_data_hash.clone(), collateral: self.collateral.inputs_option(), required_signers: self.required_signers.to_option(), @@ -1835,6 +1868,17 @@ impl TransactionBuilder { ns.add(s); }); } + if let Some(certificates_builder) = &self.certs { + certificates_builder.get_native_scripts().0.iter().for_each(|s| { + ns.add(s); + }); + } + if let Some(withdrawals_builder) = &self.withdrawals { + withdrawals_builder.get_native_scripts().0.iter().for_each(|s| { + ns.add(s); + }); + } + if ns.len() > 0 { Some(ns) } else { @@ -1859,6 +1903,16 @@ impl TransactionBuilder { res.add(s); }) } + if let Some(certificates_builder) = &self.certs { + certificates_builder.get_plutus_witnesses().0.iter().for_each(|s| { + res.add(s); + }) + } + if let Some(withdrawals_builder) = &self.withdrawals { + withdrawals_builder.get_plutus_witnesses().0.iter().for_each(|s| { + res.add(s); + }) + } if res.len() > 0 { Some(res) } else { @@ -1887,9 +1941,21 @@ impl TransactionBuilder { } fn has_plutus_inputs(&self) -> bool { - let has_in_inputs = self.inputs.get_plutus_input_scripts().is_some(); - let has_in_mint = self.mint.as_ref().map_or(false, |m| m.has_plutus_scripts()); - return has_in_inputs || has_in_mint; + if self.inputs.has_plutus_scripts() { + return true; + } + if self.mint.as_ref().map_or(false, |m| m.has_plutus_scripts()) { + return true; + } + if self.certs.as_ref().map_or(false, |c| c.has_plutus_scripts()) { + return true; + } + if self.withdrawals.as_ref().map_or(false, |w| w.has_plutus_scripts()) { + return true; + } + + return false; + } /// Returns full Transaction object with the body and the auxiliary data @@ -1941,10 +2007,11 @@ impl TransactionBuilder { mod tests { use super::output_builder::TransactionOutputBuilder; use super::*; - use crate::fakes::{fake_base_address, fake_bytes_32, fake_key_hash, fake_policy_id, fake_tx_hash, fake_tx_input, fake_tx_input2, fake_value, fake_value2}; + use crate::fakes::{fake_base_address, fake_bytes_32, fake_key_hash, fake_policy_id, fake_script_hash, fake_tx_hash, fake_tx_input, fake_tx_input2, fake_value, fake_value2}; use crate::tx_builder_constants::TxBuilderConstants; use fees::*; - use crate::tx_builder::tx_inputs_builder::{InputsWithScriptWitness, InputWithScriptWitness, PlutusScriptSource}; + use crate::tx_builder::script_structs::{PlutusScriptSource}; + use crate::tx_builder::tx_inputs_builder::{InputsWithScriptWitness, InputWithScriptWitness }; const MAX_VALUE_SIZE: u32 = 4000; const MAX_TX_SIZE: u32 = 8000; // might be out of date but suffices for our tests @@ -2271,7 +2338,7 @@ mod tests { &stake_cred, &stake.to_raw_key().hash(), // in reality, this should be the pool owner's key, not ours ))); - tx_builder.set_certs(&certs); + tx_builder.set_certs(&certs).unwrap(); let change_cred = StakeCredential::from_keyhash(&change_key.to_raw_key().hash()); let change_addr = BaseAddress::new( @@ -7985,10 +8052,10 @@ mod tests { ); let output_adress = Address::from_bech32("addr_test1qpm5njmgzf4t7225v6j34wl30xfrufzt3jtqtdzf3en9ahpmnhtmynpasyc8fq75zv0uaj86vzsr7g3g8q5ypgu5fwtqr9zsgj").unwrap(); - let output_value = Value::new(&Coin::from(500000u64)); + let output_value = Value::new(&Coin::from(5000000u64)); let output = TransactionOutput::new(&output_adress, &output_value); - tx_builder.add_output(&output); + tx_builder.add_output(&output).unwrap(); let mut col_builder = TxInputsBuilder::new(); col_builder.add_input(&colateral_adress, &colateral_input, &Value::new(&Coin::from(1000000000u64))); tx_builder.set_collateral(&col_builder); @@ -8021,12 +8088,274 @@ mod tests { tx_builder.set_inputs(&in_builder); - tx_builder.calc_script_data_hash(&TxBuilderConstants::plutus_vasil_cost_models()); - tx_builder.add_change_if_needed(&output_adress); + tx_builder.calc_script_data_hash(&TxBuilderConstants::plutus_vasil_cost_models()).unwrap(); + tx_builder.add_change_if_needed(&output_adress).unwrap(); let build_res = tx_builder.build_tx(); assert!(&build_res.is_ok()); let tx = build_res.unwrap(); assert_eq!(tx.witness_set.plutus_scripts.unwrap().len(), 1usize); assert_eq!(tx.witness_set.redeemers.unwrap().len(), 2usize); } + + #[test] + fn build_tx_with_certs_withdrawals_plutus_script_address() { + let mut tx_builder = create_tx_builder_with_key_deposit(1_000_000); + let spend = root_key_15() + .derive(harden(1852)) + .derive(harden(1815)) + .derive(harden(0)) + .derive(0) + .derive(0) + .to_public(); + let change_key = root_key_15() + .derive(harden(1852)) + .derive(harden(1815)) + .derive(harden(0)) + .derive(1) + .derive(0) + .to_public(); + let stake = root_key_15() + .derive(harden(1852)) + .derive(harden(1815)) + .derive(harden(0)) + .derive(2) + .derive(0) + .to_public(); + let reward = root_key_15() + .derive(harden(1852)) + .derive(harden(1815)) + .derive(harden(0)) + .derive(3) + .derive(1) + .to_public(); + + let redeemer_cert1 = Redeemer::from_json("\ + { + \"tag\": \"Spend\", + \"index\": \"0\", + \"data\": \"{\\\"constructor\\\":0,\\\"fields\\\":[]}\", + \"ex_units\": { + \"mem\": \"1\", + \"steps\": \"1\" + } + }").unwrap(); + + let redeemer_cert2 = Redeemer::from_json("\ + { + \"tag\": \"Spend\", + \"index\": \"0\", + \"data\": \"{\\\"constructor\\\":0,\\\"fields\\\":[]}\", + \"ex_units\": { + \"mem\": \"2\", + \"steps\": \"2\" + } + }").unwrap(); + + let redeemer_cert3 = Redeemer::from_json("\ + { + \"tag\": \"Spend\", + \"index\": \"0\", + \"data\": \"{\\\"constructor\\\":0,\\\"fields\\\":[]}\", + \"ex_units\": { + \"mem\": \"2\", + \"steps\": \"2\" + } + }").unwrap(); + + let redeemer_withdraw1 = Redeemer::from_json("\ + { + \"tag\": \"Spend\", + \"index\": \"0\", + \"data\": \"{\\\"constructor\\\":0,\\\"fields\\\":[]}\", + \"ex_units\": { + \"mem\": \"4\", + \"steps\": \"4\" + } + }").unwrap(); + + let redeemer_withdraw2 = Redeemer::from_json("\ + { + \"tag\": \"Spend\", + \"index\": \"0\", + \"data\": \"{\\\"constructor\\\":0,\\\"fields\\\":[]}\", + \"ex_units\": { + \"mem\": \"5\", + \"steps\": \"5\" + } + }").unwrap(); + + let stake_cred = StakeCredential::from_keyhash(&stake.to_raw_key().hash()); + tx_builder.add_key_input( + &spend.to_raw_key().hash(), + &TransactionInput::new(&genesis_id(), 0), + &Value::new(&to_bignum(5_000_000)), + ); + tx_builder.set_ttl(1000); + let (cert_script1, cert_script_hash1) = plutus_script_and_hash(1); + let cert_script_cred1 = StakeCredential::from_scripthash(&cert_script_hash1); + + let (cert_script2, cert_script_hash2) = plutus_script_and_hash(2); + let cert_script_cred2 = StakeCredential::from_scripthash(&cert_script_hash2); + + let cert_script_hash3 = fake_script_hash(3); + let cert_script_cred3 = StakeCredential::from_scripthash(&cert_script_hash3); + + let (withdraw_script1, withdraw_script_hash1) = plutus_script_and_hash(3); + let withdraw_script_cred1 = StakeCredential::from_scripthash(&withdraw_script_hash1); + + let withdraw_script_hash2 = fake_script_hash(3); + let withdraw_script_cred2 = StakeCredential::from_scripthash(&withdraw_script_hash2); + + let cert_witness_1 = PlutusWitness::new_without_datum( + &cert_script1, + &redeemer_cert1 + ); + let cert_witness_2= PlutusWitness::new_without_datum( + &cert_script2, + &redeemer_cert2 + ); + + let ref_cert_script_input_3 = fake_tx_input(1); + let ref_cert_withdrawal_input_2 = fake_tx_input(2); + let plutus_cert_source = PlutusScriptSource::new_ref_input_with_lang_ver( + &cert_script_hash3, + &ref_cert_script_input_3, + &Language::new_plutus_v2() + ); + let plutus_withdrawal_source = PlutusScriptSource::new_ref_input_with_lang_ver( + &withdraw_script_hash2, + &ref_cert_withdrawal_input_2, + &Language::new_plutus_v2() + ); + + let cert_witness_3 = PlutusWitness::new_with_ref_without_datum( + &plutus_cert_source, + &redeemer_cert3 + ); + let withdraw_witness1 = PlutusWitness::new_without_datum( + &withdraw_script1, + &redeemer_withdraw1 + ); + let withdraw_witness2 = PlutusWitness::new_with_ref_without_datum( + &plutus_withdrawal_source, + &redeemer_withdraw2 + ); + + + let mut certs = CertificatesBuilder::new(); + certs.add(&Certificate::new_stake_registration( + &StakeRegistration::new(&stake_cred), + )).unwrap(); + certs.add_with_plutus_witness( + &Certificate::new_stake_delegation(&StakeDelegation::new( + &cert_script_cred1, + &stake.to_raw_key().hash(), // in reality, this should be the pool owner's key, not ours + )), + &cert_witness_1 + ).unwrap(); + certs.add_with_plutus_witness( + &Certificate::new_stake_delegation(&StakeDelegation::new( + &cert_script_cred2, + &stake.to_raw_key().hash(), // in reality, this should be the pool owner's key, not ours + )), + &cert_witness_2 + ).unwrap(); + certs.add(&Certificate::new_stake_delegation(&StakeDelegation::new( + &stake_cred, + &stake.to_raw_key().hash(), // in reality, this should be the pool owner's key, not ours + ))).unwrap(); + certs.add_with_plutus_witness( + &Certificate::new_stake_deregistration(&StakeDeregistration::new( + &cert_script_cred3 + )), + &cert_witness_3 + ).unwrap(); + + tx_builder.set_certs_builder(&certs); + + let mut withdrawals = WithdrawalsBuilder::new(); + let reward_cred = StakeCredential::from_keyhash(&reward.to_raw_key().hash()); + withdrawals.add( + &RewardAddress::new(NetworkInfo::testnet().network_id(), &reward_cred), + &Coin::from(1u32), + ).unwrap(); + withdrawals.add_with_plutus_witness( + &RewardAddress::new(NetworkInfo::testnet().network_id(), &withdraw_script_cred1), + &Coin::from(2u32), + &withdraw_witness1 + ).unwrap(); + withdrawals.add_with_plutus_witness( + &RewardAddress::new(NetworkInfo::testnet().network_id(), &withdraw_script_cred2), + &Coin::from(3u32), + &withdraw_witness2 + ).unwrap(); + tx_builder.set_withdrawals_builder(&withdrawals); + + let change_cred = StakeCredential::from_keyhash(&change_key.to_raw_key().hash()); + let change_addr = BaseAddress::new( + NetworkInfo::testnet().network_id(), + &change_cred, + &stake_cred, + ) + .to_address(); + let cost_models = TxBuilderConstants::plutus_default_cost_models(); + let collateral_input = fake_tx_input(1); + let collateral_addr = BaseAddress::new( + NetworkInfo::testnet().network_id(), + &StakeCredential::from_keyhash(&fake_key_hash(1)), + &StakeCredential::from_keyhash(&fake_key_hash(2)), + ).to_address(); + let mut collateral_builder = TxInputsBuilder::new(); + collateral_builder.add_input(&collateral_addr, &collateral_input, &Value::new(&Coin::from(123u32))); + tx_builder.set_collateral(&collateral_builder); + tx_builder.calc_script_data_hash(&cost_models).unwrap(); + tx_builder.add_change_if_needed(&change_addr).unwrap(); + assert_eq!(tx_builder.outputs.len(), 1); + assert_eq!( + tx_builder + .get_explicit_input() + .unwrap() + .checked_add(&tx_builder.get_implicit_input().unwrap()) + .unwrap(), + tx_builder + .get_explicit_output() + .unwrap() + .checked_add(&Value::new(&tx_builder.get_fee_if_set().unwrap())) + .unwrap() + .checked_add(&Value::new(&tx_builder.get_deposit().unwrap())) + .unwrap() + ); + let final_tx = tx_builder.build_tx().unwrap(); + let final_tx_body = final_tx.body(); + let final_tx_wits = final_tx.witness_set(); + + assert_eq!(final_tx_body.reference_inputs().unwrap().len(), 2); + assert_eq!(final_tx_body.withdrawals().unwrap().len(), 3); + assert_eq!(final_tx_body.certs().unwrap().len(), 5); + assert_eq!(final_tx_wits.plutus_scripts().unwrap().len(), 3); + assert_eq!(final_tx_wits.redeemers().unwrap().len(), 5); + + let certs = final_tx_body.certs().unwrap().0; + let withdraws = final_tx_body.withdrawals().unwrap().0 + .iter() + .map(|(k, _)| k.clone()) + .collect::>(); + let redeemers = final_tx_wits.redeemers().unwrap(); + let mut indexes = HashMap::new(); + indexes.insert(RedeemerTag::new_cert(), HashSet::new()); + indexes.insert(RedeemerTag::new_reward(), HashSet::new()); + for redeemer in &redeemers.0 { + let tag_set = indexes.get_mut(&redeemer.tag()).unwrap(); + assert_ne!(tag_set.contains(&redeemer.index()), true); + tag_set.insert(redeemer.index()); + let index: usize = redeemer.index().into(); + if redeemer.tag().kind() == RedeemerTagKind::Cert { + let cert = &certs[index]; + assert!(cert.has_required_script_witness()); + } else if redeemer.tag().kind() == RedeemerTagKind::Reward { + let withdraw = &withdraws[index]; + assert!(withdraw.payment_cred().has_script_hash()); + } + } + } } diff --git a/rust/src/tx_builder/certificates_builder.rs b/rust/src/tx_builder/certificates_builder.rs new file mode 100644 index 00000000..30c0cd8a --- /dev/null +++ b/rust/src/tx_builder/certificates_builder.rs @@ -0,0 +1,227 @@ +use crate::tx_builder::script_structs::*; +use crate::*; + +#[wasm_bindgen] +#[derive(Clone, Debug)] +pub struct CertificatesBuilder { + certs: Vec<(Certificate, Option)>, +} + +#[wasm_bindgen] +impl CertificatesBuilder { + pub fn new() -> Self { + Self { certs: Vec::new() } + } + + pub fn add(&mut self, cert: &Certificate) -> Result<(), JsError> { + if cert.has_required_script_witness() { + return Err(JsError::from_str( + "Your certificate has a required script witness.\ + Please use .add_with_plutus_witness .add_with_native_script instead.", + )); + } + + self.certs.push((cert.clone(), None)); + Ok(()) + } + + pub fn add_with_plutus_witness( + &mut self, + cert: &Certificate, + witness: &PlutusWitness, + ) -> Result<(), JsError> { + if !cert.has_required_script_witness() { + return Err(JsError::from_str( + "Your certificate does not have a required script witness.\ + Please use .add instead.", + )); + } + + self.certs.push(( + cert.clone(), + Some(ScriptWitnessType::PlutusScriptWitness(witness.clone())), + )); + Ok(()) + } + + pub fn add_with_native_script( + &mut self, + cert: &Certificate, + native_script_source: &NativeScriptSource, + ) -> Result<(), JsError> { + if !cert.has_required_script_witness() { + return Err(JsError::from_str( + "Your certificate does not have a required script witness.\ + Please use .add instead.", + )); + } + + self.certs.push(( + cert.clone(), + Some(ScriptWitnessType::NativeScriptWitness( + native_script_source.0.clone(), + )), + )); + Ok(()) + } + + pub(crate) fn get_required_signers(&self) -> RequiredSignersSet { + let mut set = RequiredSignersSet::new(); + for (cert, script_wit) in &self.certs { + let cert_req_signers = witness_keys_for_cert(&cert); + set.extend(cert_req_signers); + if let Some(ScriptWitnessType::NativeScriptWitness(script_source)) = script_wit { + set.extend(script_source.required_signers()); + } + } + set + } + + pub fn get_plutus_witnesses(&self) -> PlutusWitnesses { + let tag = RedeemerTag::new_cert(); + let mut scripts = PlutusWitnesses::new(); + for (i, (_, script_wit)) in self.certs.iter().enumerate() { + if let Some(ScriptWitnessType::PlutusScriptWitness(s)) = script_wit { + let index = BigNum::from(i); + scripts.add(&s.clone_with_redeemer_index_and_tag(&index, &tag)); + } + } + scripts + } + + pub fn get_ref_inputs(&self) -> TransactionInputs { + let mut inputs = Vec::new(); + for (_, script_wit) in self.certs.iter() { + match script_wit { + Some(ScriptWitnessType::NativeScriptWitness(script_source)) => { + if let NativeScriptSourceEnum::RefInput(input, _, _) = script_source { + inputs.push(input.clone()); + } + } + Some(ScriptWitnessType::PlutusScriptWitness(plutus_witness)) => { + if let Some(DatumSourceEnum::RefInput(input)) = &plutus_witness.datum { + inputs.push(input.clone()); + } + if let PlutusScriptSourceEnum::RefInput(input, _, _) = &plutus_witness.script { + inputs.push(input.clone()); + } + } + None => {} + } + } + TransactionInputs(inputs) + } + + pub fn get_native_scripts(&self) -> NativeScripts { + let mut scripts = NativeScripts::new(); + for (_, script_wit) in self.certs.iter() { + if let Some(ScriptWitnessType::NativeScriptWitness( + NativeScriptSourceEnum::NativeScript(script), + )) = script_wit + { + scripts.add(script); + } + } + scripts + } + + pub(crate) fn get_used_plutus_lang_versions(&self) -> BTreeSet { + let mut used_langs = BTreeSet::new(); + for (_, script_wit) in &self.certs { + if let Some(ScriptWitnessType::PlutusScriptWitness(s)) = script_wit { + if let Some(lang) = s.script.language() { + used_langs.insert(lang.clone()); + } + } + } + used_langs + } + + pub fn get_certificates_refund( + &self, + pool_deposit: &BigNum, + key_deposit: &BigNum, + ) -> Result { + let mut refund = Coin::zero(); + for (cert, _) in &self.certs { + match &cert.0 { + CertificateEnum::StakeDeregistration(_cert) => { + refund = refund.checked_add(&key_deposit)?; + } + CertificateEnum::PoolRetirement(_cert) => { + refund = refund.checked_add(&pool_deposit)?; + } + _ => {} + } + } + Ok(Value::new(&refund)) + } + + pub fn get_certificates_deposit( + &self, + pool_deposit: &BigNum, + key_deposit: &BigNum, + ) -> Result { + let mut deposit = Coin::zero(); + for (cert, _) in &self.certs { + match &cert.0 { + CertificateEnum::PoolRegistration(_cert) => { + deposit = deposit.checked_add(&pool_deposit)?; + } + CertificateEnum::StakeRegistration(_cert) => { + deposit = deposit.checked_add(&key_deposit)?; + } + _ => {} + } + } + Ok(deposit) + } + + pub fn has_plutus_scripts(&self) -> bool { + for (_, script_wit) in &self.certs { + if let Some(ScriptWitnessType::PlutusScriptWitness(_)) = script_wit { + return true; + } + } + false + } + + pub fn build(&self) -> Certificates { + let certs = self.certs.iter().map(|(c, _)| c.clone()).collect(); + Certificates(certs) + } +} + +// comes from witsVKeyNeeded in the Ledger spec +fn witness_keys_for_cert(cert_enum: &Certificate) -> RequiredSigners { + let mut set = RequiredSigners::new(); + match &cert_enum.0 { + // stake key registrations do not require a witness + CertificateEnum::StakeRegistration(_cert) => {} + CertificateEnum::StakeDeregistration(cert) => { + if let Some(key) = cert.stake_credential().to_keyhash() { + set.add(&key); + } + } + CertificateEnum::StakeDelegation(cert) => { + if let Some(key) = cert.stake_credential().to_keyhash() { + set.add(&key); + } + } + CertificateEnum::PoolRegistration(cert) => { + for owner in &cert.pool_params().pool_owners().0 { + set.add(&owner.clone()); + } + set.add(&Ed25519KeyHash::from_bytes(cert.pool_params().operator().to_bytes()).unwrap()); + } + CertificateEnum::PoolRetirement(cert) => { + set.add(&Ed25519KeyHash::from_bytes(cert.pool_keyhash().to_bytes()).unwrap()); + } + CertificateEnum::GenesisKeyDelegation(cert) => { + set.add(&Ed25519KeyHash::from_bytes(cert.genesis_delegate_hash().to_bytes()).unwrap()); + } + // not witness as there is no single core node or genesis key that posts the certificate + CertificateEnum::MoveInstantaneousRewardsCert(_cert) => {} + } + set +} diff --git a/rust/src/tx_builder/mint_builder.rs b/rust/src/tx_builder/mint_builder.rs index e91c9b3a..f8fdaa11 100644 --- a/rust/src/tx_builder/mint_builder.rs +++ b/rust/src/tx_builder/mint_builder.rs @@ -1,6 +1,7 @@ use super::*; use crate::plutus::Redeemer; -use crate::tx_builder::tx_inputs_builder::{PlutusScriptSource, PlutusScriptSourceEnum}; +use crate::tx_builder::script_structs::PlutusScriptSourceEnum; +use crate::tx_builder::script_structs::PlutusScriptSource; #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] enum MintWitnessEnum { @@ -176,14 +177,15 @@ impl MintBuilder { TransactionInputs(reference_inputs) } - pub fn get_redeeemers(&self) -> Result { + pub fn get_redeemers(&self) -> Result { + let tag = RedeemerTag::new_mint(); let mut redeeemers = Vec::new(); let mut index = BigNum::zero(); for (_, script_mint) in &self.mints { match script_mint { ScriptMint::Plutus(plutus_mints) => { for (redeemer, _) in &plutus_mints.redeemer_mints { - redeeemers.push(redeemer.clone_with_index(&index)); + redeeemers.push(redeemer.clone_with_index_and_tag(&index, &tag)); index = index.checked_add(&BigNum::one())?; } }, diff --git a/rust/src/tx_builder/script_structs.rs b/rust/src/tx_builder/script_structs.rs new file mode 100644 index 00000000..88f7e88d --- /dev/null +++ b/rust/src/tx_builder/script_structs.rs @@ -0,0 +1,291 @@ +use crate::*; + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub enum PlutusScriptSourceEnum { + Script(PlutusScript), + RefInput(TransactionInput, ScriptHash, Option), +} + +impl PlutusScriptSourceEnum { + pub fn script_hash(&self) -> ScriptHash { + match self { + PlutusScriptSourceEnum::Script(script) => script.hash(), + PlutusScriptSourceEnum::RefInput(_, script_hash, _) => script_hash.clone(), + } + } + + pub fn language(&self) -> Option { + match self { + PlutusScriptSourceEnum::Script(script) => Some(script.language_version()), + PlutusScriptSourceEnum::RefInput(_, _, language) => language.clone(), + } + } +} + +#[wasm_bindgen] +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct PlutusScriptSource(pub(crate) PlutusScriptSourceEnum); + +#[wasm_bindgen] +impl PlutusScriptSource { + pub fn new(script: &PlutusScript) -> Self { + Self(PlutusScriptSourceEnum::Script(script.clone())) + } + + /// !!! DEPRECATED !!! + /// This constructor has missed information about plutus script language vesrion. That can affect + /// the script data hash calculation. + /// Use `.new_ref_input_with_lang_ver` instead + #[deprecated( + since = "11.3.0", + note = "This constructor has missed information about plutus script language vesrion. That can affect the script data hash calculation. Use `.new_ref_input_with_lang_ver` instead." + )] + pub fn new_ref_input(script_hash: &ScriptHash, input: &TransactionInput) -> Self { + Self(PlutusScriptSourceEnum::RefInput( + input.clone(), + script_hash.clone(), + None, + )) + } + + pub fn new_ref_input_with_lang_ver( + script_hash: &ScriptHash, + input: &TransactionInput, + lang_ver: &Language, + ) -> Self { + Self(PlutusScriptSourceEnum::RefInput( + input.clone(), + script_hash.clone(), + Some(lang_ver.clone()), + )) + } +} + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub enum DatumSourceEnum { + Datum(PlutusData), + RefInput(TransactionInput), +} + +#[wasm_bindgen] +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct DatumSource(DatumSourceEnum); + +#[wasm_bindgen] +impl DatumSource { + pub fn new(datum: &PlutusData) -> Self { + Self(DatumSourceEnum::Datum(datum.clone())) + } + + pub fn new_ref_input(input: &TransactionInput) -> Self { + Self(DatumSourceEnum::RefInput(input.clone())) + } +} + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub(crate) enum NativeScriptSourceEnum { + NativeScript(NativeScript), + RefInput(TransactionInput, ScriptHash, RequiredSigners), +} + +impl NativeScriptSourceEnum { + pub fn script_hash(&self) -> ScriptHash { + match self { + NativeScriptSourceEnum::NativeScript(script) => script.hash(), + NativeScriptSourceEnum::RefInput(_, script_hash, _) => script_hash.clone(), + } + } + + pub fn required_signers(&self) -> RequiredSignersSet { + match self { + NativeScriptSourceEnum::NativeScript(script) => RequiredSignersSet::from(script), + NativeScriptSourceEnum::RefInput(_, _, required_signers) => required_signers.into(), + } + } +} + +#[wasm_bindgen] +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct NativeScriptSource(pub(crate) NativeScriptSourceEnum); + +#[wasm_bindgen] +impl NativeScriptSource { + pub fn new(script: &NativeScript) -> Self { + Self(NativeScriptSourceEnum::NativeScript(script.clone())) + } + + pub fn new_ref_input( + script_hash: &ScriptHash, + input: &TransactionInput, + required_signers: &RequiredSigners, + ) -> Self { + Self(NativeScriptSourceEnum::RefInput( + input.clone(), + script_hash.clone(), + required_signers.clone(), + )) + } +} + +#[wasm_bindgen] +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct PlutusWitness { + pub(crate) script: PlutusScriptSourceEnum, + pub(crate) datum: Option, + pub(crate) redeemer: Redeemer, +} + +#[wasm_bindgen] +impl PlutusWitness { + pub fn new(script: &PlutusScript, datum: &PlutusData, redeemer: &Redeemer) -> Self { + Self { + script: PlutusScriptSourceEnum::Script(script.clone()), + datum: Some(DatumSourceEnum::Datum(datum.clone())), + redeemer: redeemer.clone(), + } + } + + pub fn new_with_ref( + script: &PlutusScriptSource, + datum: &DatumSource, + redeemer: &Redeemer, + ) -> Self { + Self { + script: script.0.clone(), + datum: Some(datum.0.clone()), + redeemer: redeemer.clone(), + } + } + + pub fn new_without_datum(script: &PlutusScript, redeemer: &Redeemer) -> Self { + Self { + script: PlutusScriptSourceEnum::Script(script.clone()), + datum: None, + redeemer: redeemer.clone(), + } + } + + pub fn new_with_ref_without_datum(script: &PlutusScriptSource, redeemer: &Redeemer) -> Self { + Self { + script: script.0.clone(), + datum: None, + redeemer: redeemer.clone(), + } + } + + pub fn script(&self) -> Option { + match &self.script { + PlutusScriptSourceEnum::Script(script) => Some(script.clone()), + _ => None, + } + } + + pub fn datum(&self) -> Option { + match &self.datum { + Some(DatumSourceEnum::Datum(datum)) => Some(datum.clone()), + _ => None, + } + } + + pub fn redeemer(&self) -> Redeemer { + self.redeemer.clone() + } + + pub(crate) 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), + } + } + + pub(crate) fn clone_with_redeemer_index_and_tag( + &self, + index: &BigNum, + tag: &RedeemerTag, + ) -> Self { + Self { + script: self.script.clone(), + datum: self.datum.clone(), + redeemer: self.redeemer.clone_with_index_and_tag(index, tag), + } + } +} + +#[wasm_bindgen] +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct PlutusWitnesses(pub(crate) 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()); + } + + pub(crate) fn collect(&self) -> (PlutusScripts, Option, Redeemers) { + let mut used_scripts = BTreeSet::new(); + let mut used_datums = BTreeSet::new(); + let mut used_redeemers = BTreeSet::new(); + let mut s = PlutusScripts::new(); + let mut d: Option = None; + let mut r = Redeemers::new(); + self.0.iter().for_each(|w| { + if let PlutusScriptSourceEnum::Script(script) = &w.script { + if used_scripts.insert(script.clone()) { + s.add(script); + } + } + if let Some(DatumSourceEnum::Datum(datum)) = &w.datum { + if used_datums.insert(datum) { + match d { + Some(ref mut d) => d.add(datum), + None => { + d = { + let mut initial_list = PlutusList::new(); + initial_list.add(datum); + Some(initial_list) + } + } + } + } + } + if used_redeemers.insert(w.redeemer.clone()) { + r.add(&w.redeemer); + } + }); + (s, d, r) + } +} + +impl From> for PlutusWitnesses { + fn from(scripts: Vec) -> Self { + Self(scripts) + } +} + +#[derive(Clone, Debug)] +pub(crate) enum ScriptWitnessType { + NativeScriptWitness(NativeScriptSourceEnum), + PlutusScriptWitness(PlutusWitness), +} + +impl ScriptWitnessType { + pub fn script_hash(&self) -> ScriptHash { + match self { + ScriptWitnessType::NativeScriptWitness(script) => script.script_hash(), + ScriptWitnessType::PlutusScriptWitness(script) => script.script.script_hash(), + } + } +} diff --git a/rust/src/tx_builder/tx_inputs_builder.rs b/rust/src/tx_builder/tx_inputs_builder.rs index c3cebbbd..bc3e9dd6 100644 --- a/rust/src/tx_builder/tx_inputs_builder.rs +++ b/rust/src/tx_builder/tx_inputs_builder.rs @@ -1,5 +1,6 @@ use super::*; use std::collections::{BTreeMap, BTreeSet}; +use crate::tx_builder::script_structs::*; #[derive(Clone, Debug)] pub(crate) struct TxBuilderInput { @@ -19,7 +20,8 @@ impl InputWithScriptWitness { pub fn new_with_native_script_witness(input: &TransactionInput, witness: &NativeScript) -> Self { Self { input: input.clone(), - witness: ScriptWitnessType::NativeScriptWitness(witness.clone()) + witness: ScriptWitnessType::NativeScriptWitness( + NativeScriptSourceEnum::NativeScript(witness.clone())) } } @@ -57,225 +59,6 @@ impl InputsWithScriptWitness { } } -#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] -pub enum PlutusScriptSourceEnum { - Script(PlutusScript), - RefInput(TransactionInput, ScriptHash, Option), -} - -impl PlutusScriptSourceEnum { - pub fn script_hash(&self) -> ScriptHash { - match self { - PlutusScriptSourceEnum::Script(script) => script.hash(), - PlutusScriptSourceEnum::RefInput(_, script_hash, _) => script_hash.clone(), - } - } - - pub fn language(&self) -> Option { - match self { - PlutusScriptSourceEnum::Script(script) => Some(script.language_version()), - PlutusScriptSourceEnum::RefInput(_, _, language) => language.clone(), - } - } -} - -#[wasm_bindgen] -#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] -pub struct PlutusScriptSource(pub(crate) PlutusScriptSourceEnum); - -#[wasm_bindgen] -impl PlutusScriptSource { - pub fn new(script: &PlutusScript) -> Self { - Self(PlutusScriptSourceEnum::Script(script.clone())) - } - - /// !!! DEPRECATED !!! - /// This constructor has missed information about plutus script language vesrion. That can affect - /// the script data hash calculation. - /// Use `.new_ref_input_with_lang_ver` instead - #[deprecated( - since = "11.3.0", - note = "This constructor has missed information about plutus script language vesrion. That can affect the script data hash calculation. Use `.new_ref_input_with_lang_ver` instead." - )] - pub fn new_ref_input(script_hash: &ScriptHash, input: &TransactionInput) -> Self { - Self(PlutusScriptSourceEnum::RefInput(input.clone(), - script_hash.clone(), - None)) - } - - pub fn new_ref_input_with_lang_ver(script_hash: &ScriptHash, input: &TransactionInput, lang_ver: &Language) -> Self { - Self(PlutusScriptSourceEnum::RefInput(input.clone(), - script_hash.clone(), - Some(lang_ver.clone()))) - } -} - -#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] -pub enum DatumSourceEnum { - Datum(PlutusData), - RefInput(TransactionInput), -} - -#[wasm_bindgen] -#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] -pub struct DatumSource(DatumSourceEnum); - -#[wasm_bindgen] -impl DatumSource { - pub fn new(datum: &PlutusData) -> Self { - Self(DatumSourceEnum::Datum(datum.clone())) - } - - pub fn new_ref_input(input: &TransactionInput) -> Self { - Self(DatumSourceEnum::RefInput(input.clone())) - } -} - -#[wasm_bindgen] -#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] -pub struct PlutusWitness { - script: PlutusScriptSourceEnum, - datum: Option, - redeemer: Redeemer, -} - -#[wasm_bindgen] -impl PlutusWitness { - pub fn new(script: &PlutusScript, datum: &PlutusData, redeemer: &Redeemer) -> Self { - Self { - script: PlutusScriptSourceEnum::Script(script.clone()), - datum: Some(DatumSourceEnum::Datum(datum.clone())), - redeemer: redeemer.clone(), - } - } - - pub fn new_with_ref(script: &PlutusScriptSource, datum: &DatumSource, redeemer: &Redeemer) -> Self { - Self { - script: script.0.clone(), - datum: Some(datum.0.clone()), - redeemer: redeemer.clone(), - } - } - - pub fn new_without_datum(script: &PlutusScript, redeemer: &Redeemer) -> Self { - Self { - script: PlutusScriptSourceEnum::Script(script.clone()), - datum: None, - redeemer: redeemer.clone(), - } - } - - pub fn new_with_ref_without_datum(script: &PlutusScriptSource, redeemer: &Redeemer) -> Self { - Self { - script: script.0.clone(), - datum: None, - redeemer: redeemer.clone(), - } - } - - pub fn script(&self) -> Option { - match &self.script { - PlutusScriptSourceEnum::Script(script) => Some(script.clone()), - _ => None, - } - } - - pub fn datum(&self) -> Option { - match &self.datum { - Some(DatumSourceEnum::Datum(datum)) => Some(datum.clone()), - _ => None, - } - } - - 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), - } - } -} - -#[wasm_bindgen] -#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] -pub struct PlutusWitnesses(pub(crate) 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()); - } - - pub(crate) fn collect(&self) -> (PlutusScripts, Option, Redeemers) { - let mut used_scripts = BTreeSet::new(); - let mut used_datums = BTreeSet::new(); - let mut used_redeemers = BTreeSet::new(); - let mut s = PlutusScripts::new(); - let mut d : Option = None; - let mut r = Redeemers::new(); - self.0.iter().for_each(|w| { - if let PlutusScriptSourceEnum::Script(script) = &w.script { - if used_scripts.insert(script.clone()) { - s.add(script); - } - } - if let Some(DatumSourceEnum::Datum(datum)) = &w.datum { - if used_datums.insert(datum) { - match d { - Some(ref mut d) => d.add(datum), - None => d = { - let mut initial_list = PlutusList::new(); - initial_list.add(datum); - Some(initial_list) - } - } - } - } - if used_redeemers.insert(w.redeemer.clone()) { - r.add(&w.redeemer); - } - }); - (s, d, r) - } -} - -impl From> for PlutusWitnesses { - fn from(scripts: Vec) -> Self { - Self(scripts) - } -} - -#[derive(Clone, Debug)] -pub enum ScriptWitnessType { - NativeScriptWitness(NativeScript), - PlutusScriptWitness(PlutusWitness), -} - -impl ScriptWitnessType { - pub fn script_hash(&self) -> ScriptHash { - match self { - ScriptWitnessType::NativeScriptWitness(script) => script.hash(), - ScriptWitnessType::PlutusScriptWitness(script) => script.script.script_hash(), - } - } -} - // 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)] pub struct RequiredWitnessSet { @@ -365,7 +148,8 @@ impl TxInputsBuilder { ) { let hash = script.hash(); self.add_script_input(&hash, input, amount); - let witness = ScriptWitnessType::NativeScriptWitness(script.clone()); + let witness = ScriptWitnessType::NativeScriptWitness( + NativeScriptSourceEnum::NativeScript(script.clone())); self.insert_input_with_witness(&hash, input, &witness); } @@ -476,7 +260,8 @@ impl TxInputsBuilder { } if let Some(tx_in) = tx_in { - let witness = ScriptWitnessType::NativeScriptWitness(s.clone()); + let witness = ScriptWitnessType::NativeScriptWitness( + NativeScriptSourceEnum::NativeScript(s.clone())); self.insert_input_with_witness(&hash, &tx_in, &witness); } } @@ -535,16 +320,21 @@ impl TxInputsBuilder { for wintess in self.required_witnesses.scripts.iter() .flat_map(|(_, tx_wits)| tx_wits.values()) .filter_map(|wit| wit.as_ref()) { - if let ScriptWitnessType::PlutusScriptWitness(plutus_witness) = wintess { - if let Some(DatumSourceEnum::RefInput(input)) = &plutus_witness.datum { - inputs.push(input.clone()); - } - if let PlutusScriptSourceEnum::RefInput(input, _, _) = &plutus_witness.script { + match wintess { + ScriptWitnessType::NativeScriptWitness(NativeScriptSourceEnum::RefInput(input, _, _)) => { inputs.push(input.clone()); - } + }, + ScriptWitnessType::PlutusScriptWitness(plutus_witness) => { + if let Some(DatumSourceEnum::RefInput(input)) = &plutus_witness.datum { + inputs.push(input.clone()); + } + if let PlutusScriptSourceEnum::RefInput(input, _, _) = &plutus_witness.script { + inputs.push(input.clone()); + } + }, + _ => () } } - TransactionInputs(inputs) } @@ -555,7 +345,8 @@ impl TxInputsBuilder { .values() .flat_map(|v| v) .for_each(|tx_in_with_wit| { - if let Some(ScriptWitnessType::NativeScriptWitness(s)) = tx_in_with_wit.1 { + if let Some(ScriptWitnessType::NativeScriptWitness( + NativeScriptSourceEnum::NativeScript(s))) = tx_in_with_wit.1 { scripts.add(&s); } }); @@ -593,7 +384,9 @@ impl TxInputsBuilder { * 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. + * To avoid incorrect redeemer tag we also set the `tag` field to `spend`. */ + let tag = RedeemerTag::new_spend(); let script_hash_index_map: BTreeMap<&TransactionInput, BigNum> = self .inputs .values() @@ -611,7 +404,7 @@ impl TxInputsBuilder { .for_each(|(hash, 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)); + scripts.add(&s.clone_with_redeemer_index_and_tag(&idx, &tag)); } } }); @@ -622,6 +415,13 @@ impl TxInputsBuilder { } } + pub(crate) fn has_plutus_scripts(&self) -> bool { + self.required_witnesses.scripts.values() + .any(|x| x.iter() + .any(|(_, w)| + matches!(w, Some(ScriptWitnessType::PlutusScriptWitness(_))))) + } + pub(crate) fn iter(&self) -> impl std::iter::Iterator + '_ { self.inputs.values().map(|(i, _)| i) } @@ -685,10 +485,8 @@ impl From<&TxInputsBuilder> for RequiredSignersSet { .values() .flat_map(|tx_wits| tx_wits.values()) .for_each(|swt: &Option| { - if let Some(ScriptWitnessType::NativeScriptWitness(s)) = swt { - RequiredSignersSet::from(s).iter().for_each(|k| { - set.insert(k.clone()); - }); + if let Some(ScriptWitnessType::NativeScriptWitness(script_source)) = swt { + set.extend(script_source.required_signers()); } }); set diff --git a/rust/src/tx_builder/withdrawals_builder.rs b/rust/src/tx_builder/withdrawals_builder.rs new file mode 100644 index 00000000..4b454ab8 --- /dev/null +++ b/rust/src/tx_builder/withdrawals_builder.rs @@ -0,0 +1,181 @@ +use crate::tx_builder::script_structs::*; +use crate::*; +use linked_hash_map::LinkedHashMap; + +#[wasm_bindgen] +#[derive(Clone, Debug)] +pub struct WithdrawalsBuilder { + withdrawals: LinkedHashMap)>, +} + +#[wasm_bindgen] +impl WithdrawalsBuilder { + pub fn new() -> Self { + Self { + withdrawals: LinkedHashMap::new(), + } + } + + pub fn add(&mut self, address: &RewardAddress, coin: &Coin) -> Result<(), JsError> { + if address.payment_cred().has_script_hash() { + return Err(JsError::from_str( + "Your address has a required script witness.\ + Please use .add_with_plutus_witness .add_with_native_script instead.", + )); + } + + self.withdrawals + .insert(address.clone(), (coin.clone(), None)); + + Ok(()) + } + + pub fn add_with_plutus_witness( + &mut self, + address: &RewardAddress, + coin: &Coin, + witness: &PlutusWitness, + ) -> Result<(), JsError> { + if !address.payment_cred().has_script_hash() { + return Err(JsError::from_str( + "Your address does not have a required script witness.\ + Please use .add instead.", + )); + } + + self.withdrawals.insert( + address.clone(), + ( + coin.clone(), + Some(ScriptWitnessType::PlutusScriptWitness(witness.clone())), + ), + ); + Ok(()) + } + + pub fn add_with_native_script( + &mut self, + address: &RewardAddress, + coin: &Coin, + native_script_source: &NativeScriptSource, + ) -> Result<(), JsError> { + if !address.payment_cred().has_script_hash() { + return Err(JsError::from_str( + "Your address does not have a required script witness.\ + Please use .add instead.", + )); + } + + self.withdrawals.insert( + address.clone(), + ( + coin.clone(), + Some(ScriptWitnessType::NativeScriptWitness( + native_script_source.0.clone(), + )), + ), + ); + Ok(()) + } + + pub(crate) fn get_required_signers(&self) -> RequiredSignersSet { + let mut set = RequiredSignersSet::new(); + for (address, (_, script_wit)) in &self.withdrawals { + let req_signature = address.payment_cred().to_keyhash(); + if let Some(req_signature) = req_signature { + set.insert(req_signature); + } + + if let Some(ScriptWitnessType::NativeScriptWitness(script_source)) = script_wit { + set.extend(script_source.required_signers()); + } + } + set + } + + pub fn get_plutus_witnesses(&self) -> PlutusWitnesses { + let tag = RedeemerTag::new_reward(); + let mut scripts = PlutusWitnesses::new(); + for (i, (_, (_, script_wit))) in self.withdrawals.iter().enumerate() { + if let Some(ScriptWitnessType::PlutusScriptWitness(s)) = script_wit { + let index = BigNum::from(i); + scripts.add(&s.clone_with_redeemer_index_and_tag(&index, &tag)); + } + } + scripts + } + + pub fn get_ref_inputs(&self) -> TransactionInputs { + let mut inputs = Vec::new(); + for (_, (_, script_wit)) in self.withdrawals.iter() { + match script_wit { + Some(ScriptWitnessType::NativeScriptWitness(script_source)) => { + if let NativeScriptSourceEnum::RefInput(input, _, _) = script_source { + inputs.push(input.clone()); + } + } + Some(ScriptWitnessType::PlutusScriptWitness(plutus_witness)) => { + if let Some(DatumSourceEnum::RefInput(input)) = &plutus_witness.datum { + inputs.push(input.clone()); + } + if let PlutusScriptSourceEnum::RefInput(input, _, _) = &plutus_witness.script { + inputs.push(input.clone()); + } + } + None => {} + } + } + TransactionInputs(inputs) + } + + pub fn get_native_scripts(&self) -> NativeScripts { + let mut scripts = NativeScripts::new(); + for (_, (_, script_wit)) in self.withdrawals.iter() { + if let Some(ScriptWitnessType::NativeScriptWitness( + NativeScriptSourceEnum::NativeScript(script), + )) = script_wit + { + scripts.add(script); + } + } + scripts + } + + pub(crate) fn get_used_plutus_lang_versions(&self) -> BTreeSet { + let mut used_langs = BTreeSet::new(); + for (_, (_, script_wit)) in &self.withdrawals { + if let Some(ScriptWitnessType::PlutusScriptWitness(s)) = script_wit { + if let Some(lang) = s.script.language() { + used_langs.insert(lang.clone()); + } + } + } + used_langs + } + + pub fn get_total_withdrawals(&self) -> Result { + let mut total = Coin::zero(); + for (_, (coin, _)) in &self.withdrawals { + total = total.checked_add(coin)?; + } + Ok(Value::new(&total)) + } + + pub fn has_plutus_scripts(&self) -> bool { + for (_, (_, script_wit)) in &self.withdrawals { + if let Some(ScriptWitnessType::PlutusScriptWitness(_)) = script_wit { + return true; + } + } + false + } + + pub fn build(&self) -> Withdrawals { + let map = self + .withdrawals + .iter() + .map(|(k, (v, _))| (k.clone(), v.clone())) + .collect(); + Withdrawals(map) + } +} diff --git a/rust/src/utils.rs b/rust/src/utils.rs index 42fe66be..0d403ec3 100644 --- a/rust/src/utils.rs +++ b/rust/src/utils.rs @@ -271,6 +271,13 @@ impl From for u64 { } } +impl From for usize { + + fn from(value: BigNum) -> Self { + value.0 as usize + } +} + impl From for BigNum { fn from(value: u64) -> Self { return BigNum(value); From b6cbfcfc93002a4d05889b9cdda14c04c6a69d20 Mon Sep 17 00:00:00 2001 From: lisicky Date: Thu, 1 Jun 2023 23:08:22 +0400 Subject: [PATCH 2/4] revert naming fix --- rust/pkg/cardano_serialization_lib.js.flow | 2 +- rust/src/tx_builder/mint_builder.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/pkg/cardano_serialization_lib.js.flow b/rust/pkg/cardano_serialization_lib.js.flow index 2af783b6..715adf33 100644 --- a/rust/pkg/cardano_serialization_lib.js.flow +++ b/rust/pkg/cardano_serialization_lib.js.flow @@ -4044,7 +4044,7 @@ declare export class MintBuilder { /** * @returns {Redeemers} */ - get_redeemers(): Redeemers; + get_redeeemers(): Redeemers; /** * @returns {boolean} diff --git a/rust/src/tx_builder/mint_builder.rs b/rust/src/tx_builder/mint_builder.rs index f8fdaa11..67d11d59 100644 --- a/rust/src/tx_builder/mint_builder.rs +++ b/rust/src/tx_builder/mint_builder.rs @@ -177,7 +177,7 @@ impl MintBuilder { TransactionInputs(reference_inputs) } - pub fn get_redeemers(&self) -> Result { + pub fn get_redeeemers(&self) -> Result { let tag = RedeemerTag::new_mint(); let mut redeeemers = Vec::new(); let mut index = BigNum::zero(); From d95f218e9a710c880ce5cb23b7ba625acf07c446 Mon Sep 17 00:00:00 2001 From: lisicky Date: Fri, 2 Jun 2023 14:52:34 +0400 Subject: [PATCH 3/4] fix warns --- rust/src/plutus.rs | 1 + rust/src/tx_builder.rs | 2 +- rust/src/tx_builder/script_structs.rs | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/rust/src/plutus.rs b/rust/src/plutus.rs index b32b6fae..e1fca2e7 100644 --- a/rust/src/plutus.rs +++ b/rust/src/plutus.rs @@ -940,6 +940,7 @@ impl Redeemer { } } + #[allow(dead_code)] pub(crate) fn clone_with_index(&self, index: &BigNum) -> Self { Self { tag: self.tag.clone(), diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index 03fa1632..b3fcf6a2 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -1752,7 +1752,7 @@ impl TransactionBuilder { used_langs.append(&mut certs_builder.get_used_plutus_lang_versions()); plutus_witnesses.0.append(&mut certs_builder.get_plutus_witnesses().0) } - if let Some(mut withdrawals_builder) = self.withdrawals.clone() { + if let Some(withdrawals_builder) = self.withdrawals.clone() { used_langs.append(&mut withdrawals_builder.get_used_plutus_lang_versions()); plutus_witnesses.0.append(&mut withdrawals_builder.get_plutus_witnesses().0) } diff --git a/rust/src/tx_builder/script_structs.rs b/rust/src/tx_builder/script_structs.rs index 88f7e88d..085c70cd 100644 --- a/rust/src/tx_builder/script_structs.rs +++ b/rust/src/tx_builder/script_structs.rs @@ -191,6 +191,7 @@ impl PlutusWitness { self.redeemer.clone() } + #[allow(dead_code)] pub(crate) fn clone_with_redeemer_index(&self, index: &BigNum) -> Self { Self { script: self.script.clone(), From 4d8a48fbf2228c291b71660adf7cb31298c524e2 Mon Sep 17 00:00:00 2001 From: lisicky Date: Fri, 2 Jun 2023 14:54:33 +0400 Subject: [PATCH 4/4] fix spelling --- rust/src/tx_builder.rs | 4 ++-- rust/src/tx_builder/certificates_builder.rs | 2 +- rust/src/tx_builder/withdrawals_builder.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rust/src/tx_builder.rs b/rust/src/tx_builder.rs index b3fcf6a2..d9c58a7d 100644 --- a/rust/src/tx_builder.rs +++ b/rust/src/tx_builder.rs @@ -959,7 +959,7 @@ impl TransactionBuilder { /// Use set_certs_builder instead. #[deprecated( since = "11.4.1", - note = "Can emit error if add a cert with script credential. Use set_certs_builder instead." + note = "Can emit an error if you add a cert with script credential. Use set_certs_builder instead." )] pub fn set_certs(&mut self, certs: &Certificates) -> Result<(), JsError> { let mut builder = CertificatesBuilder::new(); @@ -981,7 +981,7 @@ impl TransactionBuilder { /// Use set_withdrawals_builder instead. #[deprecated( since = "11.4.1", - note = "Can emit error if add a withdrawal with script credential. Use set_withdrawals_builder instead." + note = "Can emit an error if you add a withdrawal with script credential. Use set_withdrawals_builder instead." )] pub fn set_withdrawals(&mut self, withdrawals: &Withdrawals) -> Result<(), JsError>{ let mut withdrawals_builder = WithdrawalsBuilder::new(); diff --git a/rust/src/tx_builder/certificates_builder.rs b/rust/src/tx_builder/certificates_builder.rs index 30c0cd8a..789d4aad 100644 --- a/rust/src/tx_builder/certificates_builder.rs +++ b/rust/src/tx_builder/certificates_builder.rs @@ -17,7 +17,7 @@ impl CertificatesBuilder { if cert.has_required_script_witness() { return Err(JsError::from_str( "Your certificate has a required script witness.\ - Please use .add_with_plutus_witness .add_with_native_script instead.", + Please use .add_with_plutus_witness or .add_with_native_script instead.", )); } diff --git a/rust/src/tx_builder/withdrawals_builder.rs b/rust/src/tx_builder/withdrawals_builder.rs index 4b454ab8..731d9c26 100644 --- a/rust/src/tx_builder/withdrawals_builder.rs +++ b/rust/src/tx_builder/withdrawals_builder.rs @@ -20,7 +20,7 @@ impl WithdrawalsBuilder { if address.payment_cred().has_script_hash() { return Err(JsError::from_str( "Your address has a required script witness.\ - Please use .add_with_plutus_witness .add_with_native_script instead.", + Please use .add_with_plutus_witness or .add_with_native_script instead.", )); }