1use fuel_asm::{op, Instruction, RegId, Word};
2use fuel_tx::{AssetId, ContractId};
3use fuels_core::{constants::WORD_SIZE, error, types::errors::Result};
4
5use super::cursor::WasmFriendlyCursor;
6#[derive(Debug)]
7pub struct ContractCallInstructions {
8 instructions: Vec<Instruction>,
9 gas_fwd: bool,
10}
11
12impl IntoIterator for ContractCallInstructions {
13 type Item = Instruction;
14 type IntoIter = std::vec::IntoIter<Instruction>;
15 fn into_iter(self) -> Self::IntoIter {
16 self.instructions.into_iter()
17 }
18}
19
20impl ContractCallInstructions {
21 pub fn new(opcode_params: CallOpcodeParamsOffset) -> Self {
22 Self {
23 gas_fwd: opcode_params.gas_forwarded_offset.is_some(),
24 instructions: Self::generate_instructions(opcode_params),
25 }
26 }
27
28 pub fn into_bytes(self) -> impl Iterator<Item = u8> {
29 self.instructions
30 .into_iter()
31 .flat_map(|instruction| instruction.to_bytes())
32 }
33
34 fn generate_instructions(offsets: CallOpcodeParamsOffset) -> Vec<Instruction> {
46 let call_data_offset = offsets
47 .call_data_offset
48 .try_into()
49 .expect("call_data_offset out of range");
50 let amount_offset = offsets
51 .amount_offset
52 .try_into()
53 .expect("amount_offset out of range");
54 let asset_id_offset = offsets
55 .asset_id_offset
56 .try_into()
57 .expect("asset_id_offset out of range");
58
59 let mut instructions = [
60 op::movi(0x10, call_data_offset),
61 op::movi(0x11, amount_offset),
62 op::lw(0x11, 0x11, 0),
63 op::movi(0x12, asset_id_offset),
64 ]
65 .to_vec();
66
67 match offsets.gas_forwarded_offset {
68 Some(gas_forwarded_offset) => {
69 let gas_forwarded_offset = gas_forwarded_offset
70 .try_into()
71 .expect("gas_forwarded_offset out of range");
72
73 instructions.extend(&[
74 op::movi(0x13, gas_forwarded_offset),
75 op::lw(0x13, 0x13, 0),
76 op::call(0x10, 0x11, 0x12, 0x13),
77 ]);
78 }
79 None => instructions.push(op::call(0x10, 0x11, 0x12, RegId::CGAS)),
81 };
82
83 instructions
84 }
85
86 fn extract_normal_variant(instructions: &[Instruction]) -> Option<&[Instruction]> {
87 let normal_instructions = Self::generate_instructions(CallOpcodeParamsOffset {
88 call_data_offset: 0,
89 amount_offset: 0,
90 asset_id_offset: 0,
91 gas_forwarded_offset: None,
92 });
93 Self::extract_if_match(instructions, &normal_instructions)
94 }
95
96 fn extract_gas_fwd_variant(instructions: &[Instruction]) -> Option<&[Instruction]> {
97 let gas_fwd_instructions = Self::generate_instructions(CallOpcodeParamsOffset {
98 call_data_offset: 0,
99 amount_offset: 0,
100 asset_id_offset: 0,
101 gas_forwarded_offset: Some(0),
102 });
103 Self::extract_if_match(instructions, &gas_fwd_instructions)
104 }
105
106 pub fn extract_from(instructions: &[Instruction]) -> Option<Self> {
107 if let Some(instructions) = Self::extract_normal_variant(instructions) {
108 return Some(Self {
109 instructions: instructions.to_vec(),
110 gas_fwd: false,
111 });
112 }
113
114 Self::extract_gas_fwd_variant(instructions).map(|instructions| Self {
115 instructions: instructions.to_vec(),
116 gas_fwd: true,
117 })
118 }
119
120 pub fn len(&self) -> usize {
121 self.instructions.len()
122 }
123
124 pub fn call_data_offset(&self) -> u32 {
125 let Instruction::MOVI(movi) = self.instructions[0] else {
126 panic!("should have validated the first instruction is a MOVI");
127 };
128
129 movi.imm18().into()
130 }
131
132 pub fn is_gas_fwd_variant(&self) -> bool {
133 self.gas_fwd
134 }
135
136 fn extract_if_match<'a>(
137 unknown: &'a [Instruction],
138 correct: &[Instruction],
139 ) -> Option<&'a [Instruction]> {
140 if unknown.len() < correct.len() {
141 return None;
142 }
143
144 unknown
145 .iter()
146 .zip(correct)
147 .all(|(expected, actual)| expected.opcode() == actual.opcode())
148 .then(|| &unknown[..correct.len()])
149 }
150}
151
152#[derive(Debug, Clone, PartialEq, Eq)]
153pub struct ContractCallData {
154 pub amount: u64,
155 pub asset_id: AssetId,
156 pub contract_id: ContractId,
157 pub fn_selector_encoded: Vec<u8>,
158 pub encoded_args: Vec<u8>,
159 pub gas_forwarded: Option<u64>,
160}
161
162impl ContractCallData {
163 pub fn decode_fn_selector(&self) -> Result<String> {
164 String::from_utf8(self.fn_selector_encoded.clone())
165 .map_err(|e| error!(Codec, "cannot decode function selector: {}", e))
166 }
167
168 pub fn encode(&self, memory_offset: usize, buffer: &mut Vec<u8>) -> CallOpcodeParamsOffset {
178 let amount_offset = memory_offset;
179 let asset_id_offset = amount_offset + WORD_SIZE;
180 let call_data_offset = asset_id_offset + AssetId::LEN;
181 let encoded_selector_offset = call_data_offset + ContractId::LEN + 2 * WORD_SIZE;
182 let encoded_args_offset = encoded_selector_offset + self.fn_selector_encoded.len();
183
184 buffer.extend(self.amount.to_be_bytes()); let asset_id = self.asset_id;
187 buffer.extend(asset_id.iter()); buffer.extend(self.contract_id.as_ref()); buffer.extend((encoded_selector_offset as Word).to_be_bytes()); buffer.extend((encoded_args_offset as Word).to_be_bytes()); buffer.extend(&self.fn_selector_encoded); let encoded_args_len = self.encoded_args.len();
198
199 buffer.extend(&self.encoded_args); let gas_forwarded_offset = self.gas_forwarded.map(|gf| {
202 buffer.extend((gf as Word).to_be_bytes()); encoded_args_offset + encoded_args_len
205 });
206
207 CallOpcodeParamsOffset {
208 amount_offset,
209 asset_id_offset,
210 gas_forwarded_offset,
211 call_data_offset,
212 }
213 }
214
215 pub fn decode(data: &[u8], gas_fwd: bool) -> Result<Self> {
216 let mut data = WasmFriendlyCursor::new(data);
217
218 let amount = u64::from_be_bytes(data.consume_fixed("amount")?);
219
220 let asset_id = AssetId::new(data.consume_fixed("asset id")?);
221
222 let contract_id = ContractId::new(data.consume_fixed("contract id")?);
223
224 let _ = data.consume(8, "function selector offset")?;
225
226 let _ = data.consume(8, "encoded args offset")?;
227
228 let fn_selector = {
229 let fn_selector_len = {
230 let bytes = data.consume_fixed("function selector length")?;
231 u64::from_be_bytes(bytes) as usize
232 };
233 data.consume(fn_selector_len, "function selector")?.to_vec()
234 };
235
236 let (encoded_args, gas_forwarded) = if gas_fwd {
237 let encoded_args = data
238 .consume(data.unconsumed().saturating_sub(WORD_SIZE), "encoded_args")?
239 .to_vec();
240
241 let gas_fwd = { u64::from_be_bytes(data.consume_fixed("forwarded gas")?) };
242
243 (encoded_args, Some(gas_fwd))
244 } else {
245 (data.consume_all().to_vec(), None)
246 };
247
248 Ok(ContractCallData {
249 amount,
250 asset_id,
251 contract_id,
252 fn_selector_encoded: fn_selector,
253 encoded_args,
254 gas_forwarded,
255 })
256 }
257}
258
259#[derive(Default)]
260pub struct CallOpcodeParamsOffset {
263 pub call_data_offset: usize,
264 pub amount_offset: usize,
265 pub asset_id_offset: usize,
266 pub gas_forwarded_offset: Option<usize>,
267}
268
269pub fn loader_contract_asm(blob_ids: &[[u8; 32]]) -> Result<Vec<u8>> {
271 const BLOB_ID_SIZE: u16 = 32;
272 let get_instructions = |num_of_instructions, num_of_blobs| {
273 [
279 op::move_(0x10, RegId::PC),
282 op::addi(0x10, 0x10, num_of_instructions * Instruction::SIZE as u16),
284 op::move_(0x16, RegId::SP),
287 op::movi(0x13, num_of_blobs),
289 op::bsiz(0x11, 0x10),
292 op::ldc(0x10, 0, 0x11, 1),
294 op::addi(0x10, 0x10, BLOB_ID_SIZE),
296 op::subi(0x13, 0x13, 1),
298 op::jnzb(0x13, RegId::ZERO, 3),
300 op::sub(0x16, 0x16, RegId::IS),
304 op::divi(0x16, 0x16, 4),
306 op::jmp(0x16),
308 ]
309 };
310
311 let num_of_instructions = u16::try_from(get_instructions(0, 0).len())
312 .expect("to never have more than u16::MAX instructions");
313
314 let num_of_blobs = u32::try_from(blob_ids.len()).map_err(|_| {
315 error!(
316 Other,
317 "the number of blobs ({}) exceeds the maximum number of blobs supported: {}",
318 blob_ids.len(),
319 u32::MAX
320 )
321 })?;
322
323 let instruction_bytes = get_instructions(num_of_instructions, num_of_blobs)
324 .into_iter()
325 .flat_map(|instruction| instruction.to_bytes());
326
327 let blob_bytes = blob_ids.iter().flatten().copied();
328
329 Ok(instruction_bytes.chain(blob_bytes).collect())
330}