1use alloc::collections::BTreeSet;
4
5use super::{
6 gas::gas_charge,
7 internal::{
8 external_asset_id_balance_sub,
9 inc_pc,
10 internal_contract,
11 set_variable_output,
12 },
13 ExecutableTransaction,
14 Interpreter,
15 Memory,
16 MemoryInstance,
17 PanicContext,
18 RuntimeBalances,
19};
20use crate::{
21 constraints::reg_key::*,
22 consts::*,
23 context::Context,
24 convert,
25 error::{
26 IoResult,
27 RuntimeError,
28 },
29 interpreter::receipts::ReceiptsCtx,
30 storage::{
31 BlobData,
32 ContractsAssetsStorage,
33 ContractsRawCode,
34 InterpreterStorage,
35 },
36 verification::Verifier,
37};
38use fuel_asm::{
39 PanicReason,
40 RegId,
41 Word,
42};
43use fuel_storage::StorageSize;
44use fuel_tx::{
45 Output,
46 Receipt,
47};
48use fuel_types::{
49 Address,
50 AssetId,
51 BlobId,
52 Bytes32,
53 ContractId,
54};
55
56impl<M, S, Tx, Ecal, V> Interpreter<M, S, Tx, Ecal, V>
57where
58 M: Memory,
59 S: InterpreterStorage,
60 Tx: ExecutableTransaction,
61 V: Verifier,
62{
63 pub(crate) fn contract_balance(
64 &mut self,
65 ra: RegId,
66 b: Word,
67 c: Word,
68 ) -> Result<(), RuntimeError<S::DataError>> {
69 let (SystemRegisters { pc, .. }, mut w) = split_registers(&mut self.registers);
70 let result = &mut w[WriteRegKey::try_from(ra)?];
71 let input = ContractBalanceCtx {
72 storage: &self.storage,
73 memory: self.memory.as_mut(),
74 pc,
75 input_contracts: &self.input_contracts,
76 panic_context: &mut self.panic_context,
77 verifier: &mut self.verifier,
78 };
79 input.contract_balance(result, b, c)?;
80 Ok(())
81 }
82
83 pub(crate) fn transfer(
84 &mut self,
85 a: Word,
86 b: Word,
87 c: Word,
88 ) -> IoResult<(), S::DataError> {
89 let new_storage_gas_per_byte = self.gas_costs().new_storage_per_byte();
90 let tx_offset = self.tx_offset();
91 let (
92 SystemRegisters {
93 cgas,
94 ggas,
95 fp,
96 is,
97 pc,
98 ..
99 },
100 _,
101 ) = split_registers(&mut self.registers);
102 let input = TransferCtx {
103 storage: &mut self.storage,
104 memory: self.memory.as_mut(),
105 context: &self.context,
106 balances: &mut self.balances,
107 receipts: &mut self.receipts,
108 new_storage_gas_per_byte,
109 tx: &mut self.tx,
110 input_contracts: &self.input_contracts,
111 panic_context: &mut self.panic_context,
112 tx_offset,
113 cgas,
114 ggas,
115 fp: fp.as_ref(),
116 is: is.as_ref(),
117 pc,
118 verifier: &mut self.verifier,
119 };
120 input.transfer(a, b, c)
121 }
122
123 pub(crate) fn transfer_output(
124 &mut self,
125 a: Word,
126 b: Word,
127 c: Word,
128 d: Word,
129 ) -> IoResult<(), S::DataError> {
130 let tx_offset = self.tx_offset();
131 let new_storage_gas_per_byte = self.gas_costs().new_storage_per_byte();
132 let (
133 SystemRegisters {
134 cgas,
135 ggas,
136 fp,
137 is,
138 pc,
139 ..
140 },
141 _,
142 ) = split_registers(&mut self.registers);
143 let input = TransferCtx {
144 storage: &mut self.storage,
145 memory: self.memory.as_mut(),
146 context: &self.context,
147 balances: &mut self.balances,
148 receipts: &mut self.receipts,
149 new_storage_gas_per_byte,
150 tx: &mut self.tx,
151 input_contracts: &self.input_contracts,
152 panic_context: &mut self.panic_context,
153 tx_offset,
154 cgas,
155 ggas,
156 fp: fp.as_ref(),
157 is: is.as_ref(),
158 pc,
159 verifier: &mut self.verifier,
160 };
161 input.transfer_output(a, b, c, d)
162 }
163
164 pub(crate) fn check_contract_exists(
165 &self,
166 contract: &ContractId,
167 ) -> IoResult<bool, S::DataError> {
168 self.storage
169 .storage_contract_exists(contract)
170 .map_err(RuntimeError::Storage)
171 }
172}
173
174struct ContractBalanceCtx<'vm, S, V> {
175 storage: &'vm S,
176 memory: &'vm mut MemoryInstance,
177 pc: RegMut<'vm, PC>,
178 input_contracts: &'vm BTreeSet<ContractId>,
179 panic_context: &'vm mut PanicContext,
180 verifier: &'vm mut V,
181}
182
183impl<S, V> ContractBalanceCtx<'_, S, V> {
184 pub(crate) fn contract_balance(
185 self,
186 result: &mut Word,
187 b: Word,
188 c: Word,
189 ) -> IoResult<(), S::Error>
190 where
191 S: ContractsAssetsStorage,
192 V: Verifier,
193 {
194 let asset_id = AssetId::new(self.memory.read_bytes(b)?);
195 let contract_id = ContractId::new(self.memory.read_bytes(c)?);
196
197 self.verifier.check_contract_in_inputs(
198 self.panic_context,
199 self.input_contracts,
200 &contract_id,
201 )?;
202
203 let balance = balance(self.storage, &contract_id, &asset_id)?;
204
205 *result = balance;
206
207 Ok(inc_pc(self.pc)?)
208 }
209}
210struct TransferCtx<'vm, S, Tx, V> {
211 storage: &'vm mut S,
212 memory: &'vm mut MemoryInstance,
213 context: &'vm Context,
214 balances: &'vm mut RuntimeBalances,
215 receipts: &'vm mut ReceiptsCtx,
216
217 new_storage_gas_per_byte: Word,
218 tx: &'vm mut Tx,
219 input_contracts: &'vm BTreeSet<ContractId>,
220 panic_context: &'vm mut PanicContext,
221 tx_offset: usize,
222 cgas: RegMut<'vm, CGAS>,
223 ggas: RegMut<'vm, GGAS>,
224 fp: Reg<'vm, FP>,
225 is: Reg<'vm, IS>,
226 pc: RegMut<'vm, PC>,
227 verifier: &'vm mut V,
228}
229
230impl<S, Tx, V> TransferCtx<'_, S, Tx, V> {
231 pub(crate) fn transfer(
237 self,
238 recipient_contract_id_offset: Word,
239 transfer_amount: Word,
240 asset_id_offset: Word,
241 ) -> IoResult<(), S::Error>
242 where
243 Tx: ExecutableTransaction,
244 S: ContractsAssetsStorage,
245 V: Verifier,
246 {
247 let amount = transfer_amount;
248 let destination =
249 ContractId::from(self.memory.read_bytes(recipient_contract_id_offset)?);
250 let asset_id = AssetId::from(self.memory.read_bytes(asset_id_offset)?);
251
252 self.verifier.check_contract_in_inputs(
253 self.panic_context,
254 self.input_contracts,
255 &destination,
256 )?;
257
258 if amount == 0 {
259 return Err(PanicReason::TransferZeroCoins.into())
260 }
261
262 let internal_context = match internal_contract(self.context, self.fp, self.memory)
263 {
264 Ok(source_contract) => Some(source_contract),
266 Err(PanicReason::ExpectedInternalContext) => None,
268 Err(e) => return Err(e.into()),
270 };
271
272 if let Some(source_contract) = internal_context {
273 balance_decrease(self.storage, &source_contract, &asset_id, amount)?;
275 } else {
276 external_asset_id_balance_sub(self.balances, self.memory, &asset_id, amount)?;
278 }
279 let (_, created_new_entry) =
281 balance_increase(self.storage, &destination, &asset_id, amount)?;
282 if created_new_entry {
283 gas_charge(
285 self.cgas,
286 self.ggas,
287 ((Bytes32::LEN + WORD_SIZE) as u64)
288 .saturating_mul(self.new_storage_gas_per_byte),
289 )?;
290 }
291
292 let receipt = Receipt::transfer(
293 internal_context.unwrap_or_default(),
294 destination,
295 amount,
296 asset_id,
297 *self.pc,
298 *self.is,
299 );
300
301 self.receipts.push(receipt)?;
302
303 Ok(inc_pc(self.pc)?)
304 }
305
306 pub(crate) fn transfer_output(
313 self,
314 recipient_offset: Word,
315 output_index: Word,
316 transfer_amount: Word,
317 asset_id_offset: Word,
318 ) -> IoResult<(), S::Error>
319 where
320 Tx: ExecutableTransaction,
321 S: ContractsAssetsStorage,
322 V: Verifier,
323 {
324 let out_idx =
325 convert::to_usize(output_index).ok_or(PanicReason::OutputNotFound)?;
326 let to = Address::from(self.memory.read_bytes(recipient_offset)?);
327 let asset_id = AssetId::from(self.memory.read_bytes(asset_id_offset)?);
328 let amount = transfer_amount;
329
330 if amount == 0 {
331 return Err(PanicReason::TransferZeroCoins.into())
332 }
333
334 let internal_context = match internal_contract(self.context, self.fp, self.memory)
335 {
336 Ok(source_contract) => Some(source_contract),
338 Err(PanicReason::ExpectedInternalContext) => None,
340 Err(e) => return Err(e.into()),
342 };
343
344 if let Some(source_contract) = internal_context {
345 balance_decrease(self.storage, &source_contract, &asset_id, amount)?;
347 } else {
348 external_asset_id_balance_sub(self.balances, self.memory, &asset_id, amount)?;
350 }
351
352 let variable = Output::variable(to, amount, asset_id);
354
355 set_variable_output(self.tx, self.memory, self.tx_offset, out_idx, variable)?;
356
357 let receipt = Receipt::transfer_out(
358 internal_context.unwrap_or_default(),
359 to,
360 amount,
361 asset_id,
362 *self.pc,
363 *self.is,
364 );
365
366 self.receipts.push(receipt)?;
367
368 Ok(inc_pc(self.pc)?)
369 }
370}
371
372pub(crate) fn contract_size<S>(
373 storage: &S,
374 contract: &ContractId,
375) -> IoResult<usize, S::Error>
376where
377 S: StorageSize<ContractsRawCode> + ?Sized,
378{
379 let size = storage
380 .size_of_value(contract)
381 .map_err(RuntimeError::Storage)?
382 .ok_or(PanicReason::ContractNotFound)?;
383 Ok(size)
384}
385
386pub(crate) fn blob_size<S>(storage: &S, blob_id: &BlobId) -> IoResult<usize, S::Error>
387where
388 S: StorageSize<BlobData> + ?Sized,
389{
390 let size = storage
391 .size_of_value(blob_id)
392 .map_err(RuntimeError::Storage)?
393 .ok_or(PanicReason::BlobNotFound)?;
394 Ok(size)
395}
396
397pub(crate) fn balance<S>(
398 storage: &S,
399 contract: &ContractId,
400 asset_id: &AssetId,
401) -> IoResult<Word, S::Error>
402where
403 S: ContractsAssetsStorage + ?Sized,
404{
405 Ok(storage
406 .contract_asset_id_balance(contract, asset_id)
407 .map_err(RuntimeError::Storage)?
408 .unwrap_or_default())
409}
410
411pub fn balance_increase<S>(
414 storage: &mut S,
415 contract: &ContractId,
416 asset_id: &AssetId,
417 amount: Word,
418) -> IoResult<(Word, bool), S::Error>
419where
420 S: ContractsAssetsStorage + ?Sized,
421{
422 let balance = balance(storage, contract, asset_id)?;
423 let balance = balance
424 .checked_add(amount)
425 .ok_or(PanicReason::BalanceOverflow)?;
426
427 let old_value = storage
428 .contract_asset_id_balance_replace(contract, asset_id, balance)
429 .map_err(RuntimeError::Storage)?;
430
431 Ok((balance, old_value.is_none()))
432}
433
434pub fn balance_decrease<S>(
436 storage: &mut S,
437 contract: &ContractId,
438 asset_id: &AssetId,
439 amount: Word,
440) -> IoResult<Word, S::Error>
441where
442 S: ContractsAssetsStorage + ?Sized,
443{
444 let balance = balance(storage, contract, asset_id)?;
445 let balance = balance
446 .checked_sub(amount)
447 .ok_or(PanicReason::NotEnoughBalance)?;
448 storage
449 .contract_asset_id_balance_insert(contract, asset_id, balance)
450 .map_err(RuntimeError::Storage)?;
451 Ok(balance)
452}