fuel_contract_message_predicate/
predicate_asm.rs

1use fuel_asm::{op, RegId};
2
3const GTF_SCRIPT: u16 = 0x00B;
4const GTF_SCRIPT_LEN: u16 = 0x005;
5const GTF_SCRIPT_INPUTS_COUNT: u16 = 0x007;
6const GTF_INPUT_TYPE: u16 = 0x101;
7const GTF_MSG_DATA_LEN: u16 = 0x11A;
8
9const INPUT_MESSAGE_TYPE: u32 = 2;
10const BYTES_PER_INSTR: u16 = 4;
11
12// Gets the bytecode for the message-to-contract predicate
13pub fn bytecode() -> Vec<u8> {
14    //register names
15    const REG_HASH_PTR: u8 = 0x10;
16    const REG_SCRIPT_PTR: u8 = 0x11;
17    const REG_SCRIPT_LEN: u8 = 0x12;
18    const REG_EXPECTED_HASH_PTR: u8 = 0x13;
19    const REG_RESULT: u8 = 0x14;
20    const REG_VAL_32: u8 = 0x16;
21    const REG_INPUT_INDEX: u8 = 0x17;
22    const REG_INPUT_TYPE: u8 = 0x18;
23    const REG_INPUT_MSG_DATA_LEN: u8 = 0x19;
24    const REG_EXPECTED_INPUT_TYPE: u8 = 0x1a;
25
26    //instruction jump points
27    const JMP_LOOP_START: u16 = 11;
28    const JMP_SKIP_DATA_CHECK: u16 = 16;
29    const JMP_PREDICATE_FAILURE: u16 = 18;
30
31    //referenced data start pointer
32    const REF_DATA_START_PTR: u16 = 19 * BYTES_PER_INSTR;
33
34    /* The following assembly code is intended to do the following:
35     *  -Verify that the script bytecode hash for the transaction matches that of
36     *   the expected Message to Contract script
37     *  -Verify there are no other `InputMessages` with data in the transaction
38     *   other than the first input
39     *
40     * If these conditions are met, then the predicate evaluates as true.
41     */
42    let mut predicate: Vec<u8> = vec![
43        //extend stack for storing script hash
44        op::move_(REG_HASH_PTR, RegId::SP), //REG_HASH_PTR = stack pointer
45        op::cfei(32),                       //extends current call frame stack by 32 bytes
46        //compute script hash
47        op::gtf(REG_SCRIPT_PTR, RegId::ZERO, GTF_SCRIPT), //REG_SCRIPT_PTR = script data address
48        op::gtf(REG_SCRIPT_LEN, RegId::ZERO, GTF_SCRIPT_LEN), //REG_SCRIPT_LEN = script data length
49        op::s256(REG_HASH_PTR, REG_SCRIPT_PTR, REG_SCRIPT_LEN), //32bytes at SCRIPT_HASH_PTR = hash of the script
50        //compare hash with expected
51        op::addi(REG_EXPECTED_HASH_PTR, RegId::IS, REF_DATA_START_PTR), //REG_EXPECTED_HASH_PTR = address of reference data at end of program
52        op::movi(REG_VAL_32, 32),                                       //REG_VAL_32 = 32
53        op::meq(REG_RESULT, REG_EXPECTED_HASH_PTR, REG_HASH_PTR, REG_VAL_32), //REG_RESULT = if the 32bytes at REG_HASH_PTR equals the 32bytes at REG_EXPECTED_HASH_PTR
54        op::jnei(REG_RESULT, RegId::ONE, JMP_PREDICATE_FAILURE), //jumps to PREDICATE_FAILURE if REG_RESULT is not 1
55        //confirm that no other messages with data are included
56        op::gtf(REG_INPUT_INDEX, RegId::ZERO, GTF_SCRIPT_INPUTS_COUNT), //REG_INPUT_INDEX = the number of inputs in the script
57        op::movi(REG_EXPECTED_INPUT_TYPE, INPUT_MESSAGE_TYPE), //REG_EXPECTED_INPUT_TYPE = REG_INPUT_MESSAGE_TYPE
58        //LOOP_START:
59        op::subi(REG_INPUT_INDEX, REG_INPUT_INDEX, 1), //REG_INPUT_INDEX = REG_INPUT_INDEX - 1
60        //check if the input is a message input
61        op::gtf(REG_INPUT_TYPE, REG_INPUT_INDEX, GTF_INPUT_TYPE), //REG_INPUT_TYPE = the type of input for input[INPUT_INDEX]
62        op::jnei(REG_INPUT_TYPE, REG_EXPECTED_INPUT_TYPE, JMP_SKIP_DATA_CHECK), //skips to SKIP_DATA_CHECK if REG_INPUT_TYPE does not equal REG_EXPECTED_INPUT_TYPE
63        //check if the input message has data
64        op::gtf(REG_INPUT_MSG_DATA_LEN, REG_INPUT_INDEX, GTF_MSG_DATA_LEN), //REG_INPUT_MSG_DATA_LEN = the data length of input[INPUT_INDEX]
65        op::jnei(REG_INPUT_MSG_DATA_LEN, RegId::ZERO, JMP_PREDICATE_FAILURE), //jumps to PREDICATE_FAILURE if REG_INPUT_MSG_DATA_LEN does not equal 0
66        //SKIP_DATA_CHECK:
67        op::jnei(REG_INPUT_INDEX, RegId::ONE, JMP_LOOP_START), //jumps back to LOOP_START if REG_INPUT_INDEX does not equal 1
68        op::ret(RegId::ONE),
69        //PREDICATE_FAILURE:
70        op::ret(RegId::ZERO),
71        //referenced data (expected script hash)
72        //00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
73    ]
74    .into_iter()
75    .collect();
76
77    //add referenced data (expected script hash)
78    predicate.append(&mut crate::script_hash().to_vec());
79    predicate
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    // Ensure the predicate bytecode doesn't change
87    #[test]
88    fn snapshot_predicate_bytecode() {
89        let bytecode = bytecode();
90        let serialized = hex::encode(&bytecode);
91        insta::assert_snapshot!(serialized);
92    }
93}