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