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)]
155pub enum TipSpecifier {
156    None,
157    Percentage(u16),
158    BasisPoints(u32),
159}
160
161impl TipSpecifier {
162    pub fn basis_points(&self) -> u32 {
163        match self {
164            TipSpecifier::None => 0,
165            TipSpecifier::Percentage(percentage) => (*percentage as u32) * 100,
166            TipSpecifier::BasisPoints(basis_points) => *basis_points,
167        }
168    }
169
170    pub fn proportion(&self) -> Decimal {
171        // Notes:
172        // * We don't use checked math because it can't overfow
173        // * In order to make this math a bit cheaper, we multiply in I192 space to save a division
174        match self {
175            TipSpecifier::None => Decimal::ZERO,
176            TipSpecifier::Percentage(percentage) => {
177                Decimal::from_attos(I192::from(*percentage) * dec!(0.01).attos())
178            }
179            TipSpecifier::BasisPoints(basis_points) => {
180                Decimal::from_attos(I192::from(*basis_points) * dec!(0.0001).attos())
181            }
182        }
183    }
184
185    pub fn fee_multiplier(&self) -> Decimal {
186        Decimal::ONE + self.proportion()
187    }
188
189    pub fn truncate_to_percentage_u32(&self) -> u32 {
190        match self {
191            TipSpecifier::None => 0,
192            TipSpecifier::Percentage(percentage) => *percentage as u32,
193            TipSpecifier::BasisPoints(basis_points) => {
194                let truncated_percentage = *basis_points / 100;
195                truncated_percentage
196            }
197        }
198    }
199}
200
201impl Default for TipSpecifier {
202    fn default() -> Self {
203        TipSpecifier::None
204    }
205}
206
207#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor, ManifestSbor, Default)]
208pub struct TransactionCostingParametersReceiptV1 {
209    pub tip_percentage: u16,
210    pub free_credit_in_xrd: Decimal,
211}
212
213#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor, ManifestSbor, Default)]
214pub struct TransactionCostingParametersReceiptV2 {
215    pub tip_proportion: Decimal,
216    pub free_credit_in_xrd: Decimal,
217}
218
219impl From<TransactionCostingParametersReceiptV1> for TransactionCostingParametersReceiptV2 {
220    fn from(value: TransactionCostingParametersReceiptV1) -> Self {
221        Self {
222            tip_proportion: TipSpecifier::Percentage(value.tip_percentage).proportion(),
223            free_credit_in_xrd: value.free_credit_in_xrd,
224        }
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use crate::internal_prelude::*;
231
232    #[test]
233    fn test_uniqueness_of_simulated_hashes_inside_single_transaction() {
234        let mut hashes: IndexSet<IntentHash> = Default::default();
235
236        let num_subintents = 10;
237
238        hashes.insert(
239            SimulatedTransactionIntentNullification
240                .transaction_intent_hash()
241                .into(),
242        );
243        for i in 0..num_subintents {
244            let nullification = SimulatedSubintentNullification {
245                index: SubintentIndex(i),
246            };
247            hashes.insert(nullification.subintent_hash().into());
248        }
249
250        assert_eq!(hashes.len(), num_subintents + 1);
251    }
252
253    #[test]
254    fn tip_specifier_conversion_tests() {
255        // Some simple conversions
256        {
257            let tip = TipSpecifier::BasisPoints(1500);
258            assert_eq!(tip.basis_points(), 1500);
259            assert_eq!(tip.proportion(), dec!(0.15));
260            assert_eq!(tip.fee_multiplier(), dec!(1.15));
261            assert_eq!(tip.truncate_to_percentage_u32(), 15);
262        }
263
264        {
265            let tip = TipSpecifier::Percentage(50);
266            assert_eq!(tip.basis_points(), 5000);
267            assert_eq!(tip.proportion(), dec!(0.5));
268            assert_eq!(tip.fee_multiplier(), dec!(1.5));
269            assert_eq!(tip.truncate_to_percentage_u32(), 50);
270        }
271
272        {
273            let tip = TipSpecifier::None;
274            assert_eq!(tip.basis_points(), 0);
275            assert_eq!(tip.proportion(), dec!(0));
276            assert_eq!(tip.fee_multiplier(), dec!(1));
277            assert_eq!(tip.truncate_to_percentage_u32(), 0);
278        }
279
280        // Edge-case conversions
281        {
282            let tip = TipSpecifier::BasisPoints(7);
283            assert_eq!(tip.basis_points(), 7);
284            assert_eq!(tip.proportion(), dec!(0.0007));
285            assert_eq!(tip.fee_multiplier(), dec!(1.0007));
286            assert_eq!(tip.truncate_to_percentage_u32(), 0); // Rounds down to 0
287        }
288
289        {
290            let tip = TipSpecifier::Percentage(u16::MAX);
291            assert_eq!(tip.basis_points(), 6553500);
292            assert_eq!(tip.proportion(), dec!(655.35));
293            assert_eq!(tip.fee_multiplier(), dec!(656.35));
294            assert_eq!(tip.truncate_to_percentage_u32(), 65535);
295        }
296
297        {
298            let tip = TipSpecifier::BasisPoints(u32::MAX);
299            assert_eq!(tip.basis_points(), 4294967295);
300            assert_eq!(tip.proportion(), dec!(429496.7295));
301            assert_eq!(tip.fee_multiplier(), dec!(429497.7295));
302            assert_eq!(tip.truncate_to_percentage_u32(), 42949672); // Rounds down
303        }
304    }
305}