fuel_contract_message_predicate/
script_asm.rs

1use fuel_asm::{op, RegId};
2use sha2::{Digest, Sha256};
3
4const PROCESS_MESSAGE_FUNCTION_SIGNATURE: &str = "process_message(u8)";
5const GTF_MSG_DATA: u16 = 0x11D;
6const GTF_REG_MSG_AMOUNT: u16 = 0x117;
7
8const BYTES_PER_INSTR: u16 = 4;
9
10// Gets the bytecode for the message-to-contract script
11pub fn bytecode() -> Vec<u8> {
12    //calculate function selector
13    let mut fn_sel_hasher = Sha256::new();
14    fn_sel_hasher.update(PROCESS_MESSAGE_FUNCTION_SIGNATURE);
15    let fn_sel_hash: [u8; 32] = fn_sel_hasher.finalize().into();
16
17    //register names
18    const REG_MEMORY_START_PTR: u8 = 0x10;
19    const REG_ASSET_PTR: u8 = REG_MEMORY_START_PTR;
20    const REG_DATA_PTR: u8 = 0x11;
21    const REG_DATA_FN_SEL_PTR: u8 = 0x12;
22    const REG_CONTRACT_ADDR_PTR: u8 = 0x13;
23    const REG_FN_SELECTOR_PTR: u8 = 0x14;
24    const REG_MSG_AMOUNT: u8 = 0x15;
25
26    //referenced data start pointer
27    const REF_DATA_START_PTR: u16 = 11 * BYTES_PER_INSTR;
28
29    /* The following assembly code is intended to do the following:
30     *  -Call the function `process_message` on the contract with ID that matches
31     *   the first 32 bytes in the message data field, while forwarding the exact
32     *   amount of base asset specified in the `InputMessage` `amount` field
33     *
34     * note: this code makes the assumption that all memory at VM initialization is set to 0
35     */
36    let mut script: Vec<u8> = vec![
37        //extend stack for contract call data
38        op::move_(REG_MEMORY_START_PTR, RegId::SP), //REG_MEMORY_START_PTR = stack pointer
39        op::cfei(32 + 32 + 8 + 8), //extends current call frame stack by 32+32+8+8 bytes [base asset id, contract id, param1, param2]
40        op::addi(REG_DATA_PTR, REG_MEMORY_START_PTR, 32), //REG_DATA_PTR = REG_MEMORY_START_PTR + 32bytes [memory start pointer + 32]
41        op::addi(REG_DATA_FN_SEL_PTR, REG_DATA_PTR, 32 + 4), //REG_DATA_FN_SEL_PTR = REG_DATA_PTR + 32bytes + 4bytes [call data start pointer + 32 + 4]
42        //prep call parameters
43        op::gtf(REG_MSG_AMOUNT, RegId::ZERO, GTF_REG_MSG_AMOUNT), //REG_MSG_AMOUNT = amount value of message from input[0]
44        op::gtf(REG_CONTRACT_ADDR_PTR, RegId::ZERO, GTF_MSG_DATA), //REG_CONTRACT_ADDR_PTR = memory location of the message data from input[0]
45        op::addi(REG_FN_SELECTOR_PTR, RegId::IS, REF_DATA_START_PTR), //REG_FN_SELECTOR_PTR = function selector at end of program
46        op::mcpi(REG_DATA_PTR, REG_CONTRACT_ADDR_PTR, 32), //32 bytes at REG_DATA_PTR = the 32 bytes at REG_CONTRACT_ADDR_PTR
47        op::mcpi(REG_DATA_FN_SEL_PTR, REG_FN_SELECTOR_PTR, 4), //4 bytes at REG_DATA_FN_SEL_PTR = the 4 bytes at REG_FN_SELECTOR_PTR
48        //make contract call
49        op::call(REG_DATA_PTR, REG_MSG_AMOUNT, REG_ASSET_PTR, RegId::CGAS),
50        op::ret(RegId::ZERO),
51        //referenced data (function selector)
52        //00000000
53    ]
54    .into_iter()
55    .collect();
56
57    //add referenced data (function selector)
58    script.append(&mut fn_sel_hash[0..4].to_vec());
59    script
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    // Ensure the script bytecode doesn't change
67    #[test]
68    fn snapshot_script_bytecode() {
69        let bytecode = bytecode();
70        let serialized = hex::encode(&bytecode);
71        insta::assert_snapshot!(serialized);
72    }
73}