radix_transactions/model/execution/
executable_common.rs

1use crate::internal_prelude::*;
2use decompiler::*;
3
4#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor, Default)]
5pub struct AuthZoneInit {
6    pub initial_non_fungible_id_proofs: BTreeSet<NonFungibleGlobalId>,
7    /// For use by the "assume_all_signature_proofs" flag
8    pub simulate_every_proof_under_resources: BTreeSet<ResourceAddress>,
9}
10
11impl AuthZoneInit {
12    pub fn proofs(proofs: BTreeSet<NonFungibleGlobalId>) -> Self {
13        Self::new(proofs, btreeset!())
14    }
15
16    pub fn new(
17        proofs: BTreeSet<NonFungibleGlobalId>,
18        resources: BTreeSet<ResourceAddress>,
19    ) -> Self {
20        Self {
21            initial_non_fungible_id_proofs: proofs,
22            simulate_every_proof_under_resources: resources,
23        }
24    }
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct EpochRange {
29    pub start_epoch_inclusive: Epoch,
30    pub end_epoch_exclusive: Epoch,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
34pub struct ProposerTimestampRange {
35    pub start_timestamp_inclusive: Option<Instant>,
36    pub end_timestamp_exclusive: Option<Instant>,
37}
38
39#[derive(Debug, Clone, Eq, PartialEq, ManifestSbor, ScryptoSbor)]
40pub struct PreAllocatedAddress {
41    pub blueprint_id: BlueprintId,
42    pub address: GlobalAddress,
43}
44
45impl PreAllocatedAddress {
46    pub fn decompile_as_pseudo_instruction(
47        &self,
48        context: &mut DecompilationContext,
49    ) -> Result<DecompiledInstruction, DecompileError> {
50        // This aligns with AllocateGlobalAddress
51        let instruction = DecompiledInstruction::new("USE_PREALLOCATED_ADDRESS")
52            .add_argument(self.blueprint_id.package_address)
53            .add_argument(&self.blueprint_id.blueprint_name)
54            .add_argument(context.new_address_reservation())
55            .add_argument(self.address);
56        Ok(instruction)
57    }
58}
59
60impl From<(BlueprintId, GlobalAddress)> for PreAllocatedAddress {
61    fn from((blueprint_id, address): (BlueprintId, GlobalAddress)) -> Self {
62        PreAllocatedAddress {
63            blueprint_id,
64            address,
65        }
66    }
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub enum IntentHashNullification {
71    /// Should be checked with transaction tracker.
72    /// Assuming the transaction gets committed, this will be persisted/nullified regardless of success
73    TransactionIntent {
74        intent_hash: TransactionIntentHash,
75        expiry_epoch: Epoch,
76    },
77    /// Used in preview. For realistic preview, should be billed as if it were a real transaction intent nullification.
78    /// But it shouldn't error or prevent the preview from running.
79    SimulatedTransactionIntent {
80        simulated: SimulatedTransactionIntentNullification,
81    },
82    /// Subintent - should only be written on failure
83    Subintent {
84        intent_hash: SubintentHash,
85        expiry_epoch: Epoch,
86    },
87    /// Used in preview. For realistic preview, should be billed as if it were a real subintent nullification.
88    /// But it shouldn't error or prevent the preview from running.
89    SimulatedSubintent {
90        simulated: SimulatedSubintentNullification,
91    },
92}
93
94impl IntentHashNullification {
95    pub fn intent_hash(&self) -> IntentHash {
96        match self {
97            IntentHashNullification::TransactionIntent { intent_hash, .. } => {
98                IntentHash::Transaction(*intent_hash)
99            }
100            IntentHashNullification::SimulatedTransactionIntent { simulated } => {
101                IntentHash::Transaction(simulated.transaction_intent_hash())
102            }
103            IntentHashNullification::Subintent { intent_hash, .. } => {
104                IntentHash::Subintent(*intent_hash)
105            }
106            IntentHashNullification::SimulatedSubintent { simulated } => {
107                IntentHash::Subintent(simulated.subintent_hash())
108            }
109        }
110    }
111}
112
113#[derive(Debug, Clone, PartialEq, Eq)]
114pub struct SimulatedTransactionIntentNullification;
115
116impl SimulatedTransactionIntentNullification {
117    pub fn transaction_intent_hash(&self) -> TransactionIntentHash {
118        TransactionIntentHash::from_hash(Hash([0; Hash::LENGTH]))
119    }
120
121    pub fn expiry_epoch(&self, current_epoch: Epoch) -> Epoch {
122        current_epoch.next().unwrap_or(Epoch::of(u64::MAX))
123    }
124}
125
126#[derive(Debug, Clone, PartialEq, Eq)]
127pub struct SimulatedSubintentNullification {
128    index: SubintentIndex,
129}
130
131impl SimulatedSubintentNullification {
132    pub fn subintent_hash(&self) -> SubintentHash {
133        let mut simulated_hash = [0u8; Hash::LENGTH];
134        simulated_hash[0] = 1; // To differentiate it from the simulated transaction intent hash
135        let index_bytes = self.index.0.to_be_bytes();
136        let target = &mut simulated_hash[1..(index_bytes.len() + 1)];
137        target.copy_from_slice(&index_bytes);
138        SubintentHash::from_hash(Hash(simulated_hash))
139    }
140
141    pub fn expiry_epoch(&self, current_epoch: Epoch) -> Epoch {
142        current_epoch.next().unwrap_or(Epoch::of(u64::MAX))
143    }
144}
145
146#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor, ManifestSbor, Default)]
147pub struct TransactionCostingParameters {
148    pub tip: TipSpecifier,
149
150    /// Free credit for execution, for preview only!
151    pub free_credit_in_xrd: Decimal,
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq, ScryptoSbor, ManifestSbor, Default)]
155pub enum TipSpecifier {
156    #[default]
157    None,
158    Percentage(u16),
159    BasisPoints(u32),
160}
161
162impl TipSpecifier {
163    pub fn basis_points(&self) -> u32 {
164        match self {
165            TipSpecifier::None => 0,
166            TipSpecifier::Percentage(percentage) => (*percentage as u32) * 100,
167            TipSpecifier::BasisPoints(basis_points) => *basis_points,
168        }
169    }
170
171    pub fn proportion(&self) -> Decimal {
172        // Notes:
173        // * We don't use checked math because it can't overfow
174        // * In order to make this math a bit cheaper, we multiply in I192 space to save a division
175        match self {
176            TipSpecifier::None => Decimal::ZERO,
177            TipSpecifier::Percentage(percentage) => {
178                Decimal::from_attos(I192::from(*percentage) * dec!(0.01).attos())
179            }
180            TipSpecifier::BasisPoints(basis_points) => {
181                Decimal::from_attos(I192::from(*basis_points) * dec!(0.0001).attos())
182            }
183        }
184    }
185
186    pub fn fee_multiplier(&self) -> Decimal {
187        Decimal::ONE + self.proportion()
188    }
189
190    pub fn truncate_to_percentage_u32(&self) -> u32 {
191        match self {
192            TipSpecifier::None => 0,
193            TipSpecifier::Percentage(percentage) => *percentage as u32,
194            TipSpecifier::BasisPoints(basis_points) => *basis_points / 100,
195        }
196    }
197}
198
199#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor, ManifestSbor, Default)]
200pub struct TransactionCostingParametersReceiptV1 {
201    pub tip_percentage: u16,
202    pub free_credit_in_xrd: Decimal,
203}
204
205#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor, ManifestSbor, Default)]
206pub struct TransactionCostingParametersReceiptV2 {
207    pub tip_proportion: Decimal,
208    pub free_credit_in_xrd: Decimal,
209}
210
211impl From<TransactionCostingParametersReceiptV1> for TransactionCostingParametersReceiptV2 {
212    fn from(value: TransactionCostingParametersReceiptV1) -> Self {
213        Self {
214            tip_proportion: TipSpecifier::Percentage(value.tip_percentage).proportion(),
215            free_credit_in_xrd: value.free_credit_in_xrd,
216        }
217    }
218}
219
220#[cfg(test)]
221mod tests {
222    use crate::internal_prelude::*;
223
224    #[test]
225    fn test_uniqueness_of_simulated_hashes_inside_single_transaction() {
226        let mut hashes: IndexSet<IntentHash> = Default::default();
227
228        let num_subintents = 10;
229
230        hashes.insert(
231            SimulatedTransactionIntentNullification
232                .transaction_intent_hash()
233                .into(),
234        );
235        for i in 0..num_subintents {
236            let nullification = SimulatedSubintentNullification {
237                index: SubintentIndex(i),
238            };
239            hashes.insert(nullification.subintent_hash().into());
240        }
241
242        assert_eq!(hashes.len(), num_subintents + 1);
243    }
244
245    #[test]
246    fn tip_specifier_conversion_tests() {
247        // Some simple conversions
248        {
249            let tip = TipSpecifier::BasisPoints(1500);
250            assert_eq!(tip.basis_points(), 1500);
251            assert_eq!(tip.proportion(), dec!(0.15));
252            assert_eq!(tip.fee_multiplier(), dec!(1.15));
253            assert_eq!(tip.truncate_to_percentage_u32(), 15);
254        }
255
256        {
257            let tip = TipSpecifier::Percentage(50);
258            assert_eq!(tip.basis_points(), 5000);
259            assert_eq!(tip.proportion(), dec!(0.5));
260            assert_eq!(tip.fee_multiplier(), dec!(1.5));
261            assert_eq!(tip.truncate_to_percentage_u32(), 50);
262        }
263
264        {
265            let tip = TipSpecifier::None;
266            assert_eq!(tip.basis_points(), 0);
267            assert_eq!(tip.proportion(), dec!(0));
268            assert_eq!(tip.fee_multiplier(), dec!(1));
269            assert_eq!(tip.truncate_to_percentage_u32(), 0);
270        }
271
272        // Edge-case conversions
273        {
274            let tip = TipSpecifier::BasisPoints(7);
275            assert_eq!(tip.basis_points(), 7);
276            assert_eq!(tip.proportion(), dec!(0.0007));
277            assert_eq!(tip.fee_multiplier(), dec!(1.0007));
278            assert_eq!(tip.truncate_to_percentage_u32(), 0); // Rounds down to 0
279        }
280
281        {
282            let tip = TipSpecifier::Percentage(u16::MAX);
283            assert_eq!(tip.basis_points(), 6553500);
284            assert_eq!(tip.proportion(), dec!(655.35));
285            assert_eq!(tip.fee_multiplier(), dec!(656.35));
286            assert_eq!(tip.truncate_to_percentage_u32(), 65535);
287        }
288
289        {
290            let tip = TipSpecifier::BasisPoints(u32::MAX);
291            assert_eq!(tip.basis_points(), 4294967295);
292            assert_eq!(tip.proportion(), dec!(429496.7295));
293            assert_eq!(tip.fee_multiplier(), dec!(429497.7295));
294            assert_eq!(tip.truncate_to_percentage_u32(), 42949672); // Rounds down
295        }
296    }
297}