1use crate::{
5 execution_mode::ExecutionMode,
6 gas_charger::GasPayment,
7 static_programmable_transactions::{
8 env::Env,
9 linkage,
10 loading::ast::{self as L, PackagePayload},
11 metering::{self, translation_meter::TranslationMeter},
12 },
13};
14use move_core_types::{language_storage::StructTag, u256::U256};
15use mysten_common::ZipDebugEqIteratorExt;
16use sui_types::{
17 base_types::TxContext,
18 error::ExecutionErrorTrait,
19 object::{ObjectPermissions, Owner},
20 transaction::{self as P, CallArg, FundsWithdrawalArg, ObjectArg, SharedObjectMutability},
21};
22
23pub fn transaction<Mode: ExecutionMode>(
24 meter: &mut TranslationMeter<'_, '_>,
25 env: &Env<Mode>,
26 tx_context: &TxContext,
27 withdrawal_compatibility_inputs: Option<Vec<bool>>,
30 gas_payment: Option<GasPayment>,
31 pt: P::ProgrammableTransaction,
32) -> Result<L::Transaction, Mode::Error> {
33 if env.protocol_config.validate_ptb_argument_indices()
34 && let Err(err) = pt.validate_argument_indices()
35 {
36 invariant_violation!(
37 "PTB argument indices are checked at signing -- this should be impossible: {err}"
38 );
39 }
40 metering::pre_translation::meter::<Mode::Error>(meter, &pt)?;
41 let P::ProgrammableTransaction { inputs, commands } = pt;
42 assert_invariant!(
44 withdrawal_compatibility_inputs.is_none()
45 || env
46 .protocol_config
47 .convert_withdrawal_compatibility_ptb_arguments(),
48 "if withdrawal compatibility must be specified, then the flag is set in the protocol config"
49 );
50 let withdrawal_compatibility_inputs =
51 withdrawal_compatibility_inputs.unwrap_or_else(|| vec![false; inputs.len()]);
52 assert_invariant!(
53 inputs.len() == withdrawal_compatibility_inputs.len(),
54 "withdrawal compatibility inputs must be the same length as the inputs"
55 );
56 let inputs = withdrawal_compatibility_inputs
57 .into_iter()
58 .zip_debug_eq(inputs)
59 .map(|(is_withdrawal_compatibility_input, arg)| {
60 input::<Mode>(env, tx_context, is_withdrawal_compatibility_input, arg)
61 })
62 .collect::<Result<Vec<_>, _>>()?;
63 let original_command_len = commands.len();
64 let commands = commands
65 .into_iter()
66 .enumerate()
67 .map(|(idx, cmd)| command::<Mode>(env, cmd).map_err(|e| e.with_command_index(idx)))
68 .collect::<Result<Vec<_>, _>>()?;
69 let loaded_tx = L::Transaction {
70 gas_payment,
71 inputs,
72 original_command_len,
73 commands,
74 unified_linkage: None,
76 };
77 metering::loading::meter::<Mode::Error>(meter, &loaded_tx)?;
78 linkage::refine_linkage::<Mode>(
79 loaded_tx,
80 env.linkage_analysis,
81 env.linkable_store,
82 env.protocol_config,
83 )
84}
85
86fn input<Mode: ExecutionMode>(
87 env: &Env<Mode>,
88 tx_context: &TxContext,
89 is_withdrawal_compatibility_input: bool,
91 arg: CallArg,
92) -> Result<(L::InputArg, L::InputType), Mode::Error> {
93 assert_invariant!(
95 !is_withdrawal_compatibility_input || matches!(arg, CallArg::FundsWithdrawal(_)),
96 "withdrawal compatibility inputs must be FundsWithdrawal"
97 );
98 Ok(match arg {
99 CallArg::Pure(bytes) => (L::InputArg::Pure(bytes), L::InputType::Bytes),
100 CallArg::Object(ObjectArg::Receiving(oref)) => {
101 (L::InputArg::Receiving(oref), L::InputType::Bytes)
102 }
103 CallArg::Object(ObjectArg::ImmOrOwnedObject(oref)) => {
104 let id = &oref.0;
105 let obj = env.read_object(id)?;
106 let Some(ty) = obj.type_() else {
107 invariant_violation!("Object {:?} has does not have a Move type", id);
108 };
109 let tag: StructTag = ty.clone().into();
110 let ty = env.load_type_from_struct(&tag)?;
111 let arg = match &obj.owner {
112 Owner::AddressOwner(_) => L::ObjectArg {
113 kind: L::ObjectArgKind::OwnedObject(oref),
114 refined_permissions: ObjectPermissions::ALL,
115 },
116 Owner::Immutable => L::ObjectArg {
117 kind: L::ObjectArgKind::ImmObject(oref),
118 refined_permissions: ObjectPermissions::IMMUTABLE_USAGE,
119 },
120 Owner::ObjectOwner(_)
121 | Owner::Shared { .. }
122 | Owner::ConsensusAddressOwner { .. } => {
123 assert_invariant!(
124 Mode::allow_arbitrary_values(),
125 "Unexpected owner for ImmOrOwnedObject: {:?}",
126 obj.owner,
127 );
128 let kind = L::ObjectArgKind::OwnedObject(oref);
129 L::ObjectArg {
130 kind,
131 refined_permissions: ObjectPermissions::ALL,
132 }
133 }
134 Owner::Party { permissions, .. } => {
135 assert_invariant!(
136 Mode::allow_arbitrary_values(),
137 "Unexpected owner for ImmOrOwnedObject: {:?}",
138 obj.owner,
139 );
140 let refined_permissions = permissions.permissions_for(&tx_context.sender());
141 L::ObjectArg {
142 kind: L::ObjectArgKind::OwnedObject(oref),
143 refined_permissions,
144 }
145 }
146 };
147 (L::InputArg::Object(arg), L::InputType::Fixed(ty))
148 }
149 CallArg::Object(ObjectArg::SharedObject {
150 id,
151 initial_shared_version,
152 mutability,
153 }) => {
154 let obj = env.read_object(&id)?;
155 let Some(ty) = obj.type_() else {
156 invariant_violation!("Object {:?} does not have a Move type", id);
157 };
158 let tag: StructTag = ty.clone().into();
159 let ty = env.load_type_from_struct(&tag)?;
160 let owner_permissions = match &obj.owner {
161 Owner::AddressOwner(_) | Owner::ObjectOwner(_) | Owner::Immutable => {
162 assert_invariant!(
163 Mode::allow_arbitrary_values(),
164 "Unexpected owner for SharedObject: {:?}",
165 obj.owner
166 );
167 ObjectPermissions::ALL
168 }
169 Owner::Shared { .. } => ObjectPermissions::LEGACY_SHARED_OBJECT,
170 Owner::ConsensusAddressOwner { .. } => ObjectPermissions::ALL,
171 Owner::Party { permissions, .. } => {
172 permissions.permissions_for(&tx_context.sender())
173 }
174 };
175 let refined_permissions = refine_permissions::<Mode>(mutability, owner_permissions)?;
176 let kind = L::ObjectArgKind::ConsensusObject {
177 id,
178 initial_shared_version,
179 };
180 (
181 L::InputArg::Object(L::ObjectArg {
182 kind,
183 refined_permissions,
184 }),
185 L::InputType::Fixed(ty),
186 )
187 }
188 CallArg::FundsWithdrawal(f) => {
189 assert_invariant!(
190 env.protocol_config.enable_accumulators(),
191 "Withdrawals should be rejected at signing if accumulators are not enabled"
192 );
193 let FundsWithdrawalArg {
194 reservation,
195 type_arg,
196 withdraw_from,
197 } = f;
198 let amount = match reservation {
199 P::Reservation::MaxAmountU64(u) => U256::from(u),
200 };
203 let funds_ty = match type_arg {
204 P::WithdrawalTypeArg::Balance(inner) => {
205 let inner = env.load_type_tag(0, &inner)?;
206 env.balance_type(inner)?
207 }
208 };
209 let source = match withdraw_from {
210 P::WithdrawFrom::Sender => L::WithdrawalSource::Direct {
211 owner: tx_context.sender().into(),
212 },
213 P::WithdrawFrom::Sponsor => L::WithdrawalSource::Direct {
214 owner: tx_context
215 .sponsor()
216 .ok_or_else(|| {
217 make_invariant_violation!(
218 "A sponsor withdrawal requires a sponsor and should have been \
219 checked at signing"
220 )
221 })?
222 .into(),
223 },
224 P::WithdrawFrom::SenderAllowance { funder, allowance } => {
225 L::WithdrawalSource::Allowance {
226 funder: funder.into(),
227 id: allowance,
228 }
229 }
230 };
231 let ty = env.withdrawal_type_for_source(&source, funds_ty)?;
232 (
233 L::InputArg::FundsWithdrawal(L::FundsWithdrawalArg {
234 from_compatibility_object: is_withdrawal_compatibility_input,
235 amount,
236 ty: ty.clone(),
237 source,
238 }),
239 L::InputType::Fixed(ty),
240 )
241 }
242 })
243}
244
245fn refine_permissions<Mode: ExecutionMode>(
246 mutability: SharedObjectMutability,
247 permissions: ObjectPermissions,
248) -> Result<ObjectPermissions, Mode::Error> {
249 Ok(match mutability {
250 SharedObjectMutability::Mutable | SharedObjectMutability::NonExclusiveWrite => {
251 assert_invariant!(
252 permissions.can_use_mutably(),
253 "Mutable shared object usage requires mutable usage permission"
254 );
255 permissions
256 }
257 SharedObjectMutability::Immutable => {
258 assert_invariant!(
259 permissions.can_use_immutably(),
260 "Immutable shared object usage requires immutable usage permission"
261 );
262 ObjectPermissions::IMMUTABLE_USAGE
263 }
264 })
265}
266
267fn command<Mode: ExecutionMode>(
268 env: &Env<Mode>,
269 command: P::Command,
270) -> Result<L::Command, Mode::Error> {
271 Ok(match command {
272 P::Command::MoveCall(pmc) => {
273 let P::ProgrammableMoveCall {
274 package,
275 module,
276 function: name,
277 type_arguments: ptype_arguments,
278 arguments,
279 } = *pmc;
280 let type_arguments = ptype_arguments
281 .into_iter()
282 .enumerate()
283 .map(|(idx, ty)| env.load_type_input(idx, ty))
284 .collect::<Result<Vec<_>, _>>()?;
285 let function = env.load_function(package, module, name, type_arguments)?;
286 L::Command::MoveCall(Box::new(L::MoveCall {
287 function,
288 arguments,
289 }))
290 }
291 P::Command::MakeMoveVec(ptype_argument, arguments) => {
292 let type_argument = ptype_argument
293 .map(|ty| env.load_type_input(0, ty))
294 .transpose()?;
295 L::Command::MakeMoveVec(type_argument, arguments)
296 }
297 P::Command::TransferObjects(objects, address) => {
298 L::Command::TransferObjects(objects, address)
299 }
300 P::Command::SplitCoins(coin, amounts) => L::Command::SplitCoins(coin, amounts),
301 P::Command::MergeCoins(target, coins) => L::Command::MergeCoins(target, coins),
302 P::Command::Publish(items, dep_ids) => {
303 let resolved_linkage = env
304 .linkage_analysis
305 .compute_publication_linkage::<Mode::Error>(&dep_ids, env.linkable_store)?;
306 let payload = if env.protocol_config.enable_unified_linkage() {
307 let deserialized_pkg = env.deserialize_package(&items, &dep_ids)?;
308 PackagePayload::Deserialized(deserialized_pkg)
309 } else {
310 PackagePayload::Serialized(items)
311 };
312 L::Command::Publish(payload, dep_ids, resolved_linkage)
313 }
314 P::Command::Upgrade(items, dep_ids, object_id, argument) => {
315 let resolved_linkage = env
316 .linkage_analysis
317 .compute_publication_linkage::<Mode::Error>(&dep_ids, env.linkable_store)?;
318 let payload = if env.protocol_config.enable_unified_linkage() {
319 let deserialized_pkg = env.deserialize_package(&items, &dep_ids)?;
320 PackagePayload::Deserialized(deserialized_pkg)
321 } else {
322 PackagePayload::Serialized(items)
323 };
324 L::Command::Upgrade(payload, dep_ids, object_id, argument, resolved_linkage)
325 }
326 })
327}