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}