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