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