essential_asm/
effects.rs

1use crate::{Access, Op, Stack, StateRead, ToOpcode};
2use bitflags::bitflags;
3
4/// Flags representing the set of effects caused by a given slice of operations.
5#[derive(Debug, Copy, Clone, PartialEq, Eq)]
6pub struct Effects(u8);
7
8bitflags! {
9    impl Effects: u8 {
10        /// Flag for [´StateRead::KeyRange´]
11        const KeyRange = 1 << 0;
12        /// Flag for [´StateRead::KeyRangeExtern´]
13        const KeyRangeExtern = 1 << 1;
14        /// Flag for [´Access::ThisAddress´]
15        const ThisAddress = 1 << 2;
16        /// Flag for [´Access::ThisContractAddress´]
17        const ThisContractAddress = 1 << 3;
18        /// Flag for [´StateRead::PostKeyRange´]
19        const PostKeyRange = 1 << 4;
20        /// Flag for [´StateRead::PostKeyRangeExtern´]
21        const PostKeyRangeExtern = 1 << 5;
22    }
23}
24
25/// Determine effects of the given program.
26pub fn analyze(ops: &[Op]) -> Effects {
27    let mut effects = Effects::empty();
28
29    for op in ops {
30        match op {
31            Op::StateRead(StateRead::KeyRangeExtern) => effects |= Effects::KeyRangeExtern,
32            Op::StateRead(StateRead::KeyRange) => effects |= Effects::KeyRange,
33            Op::Access(Access::ThisAddress) => effects |= Effects::ThisAddress,
34            Op::Access(Access::ThisContractAddress) => effects |= Effects::ThisContractAddress,
35            _ => {}
36        }
37
38        // Short circuit if all flags are found.
39        if effects == Effects::all() {
40            break;
41        }
42    }
43    effects
44}
45
46/// Analyze a slice of bytes to determine if it contains any of the effects.
47///
48/// This is a short-circuiting function that will return true if any of the effects
49/// are found in the byte slice.
50pub fn bytes_contains_any(bytes: &[u8], effects: Effects) -> bool {
51    let krng_byte: u8 = Op::StateRead(StateRead::KeyRange).to_opcode().into();
52    let krng_extern_byte: u8 = Op::StateRead(StateRead::KeyRangeExtern).to_opcode().into();
53    let post_krng_byte: u8 = Op::StateRead(StateRead::PostKeyRange).to_opcode().into();
54    let post_krng_extern_byte: u8 = Op::StateRead(StateRead::PostKeyRangeExtern)
55        .to_opcode()
56        .into();
57    let this_address_byte: u8 = Op::Access(Access::ThisAddress).to_opcode().into();
58    let this_contract_address_byte: u8 = Op::Access(Access::ThisContractAddress).to_opcode().into();
59
60    let push: u8 = Op::Stack(Stack::Push(0)).to_opcode().into();
61
62    let mut iter = bytes.iter();
63    while let Some(byte) = iter.next() {
64        match byte {
65            b if *b == krng_byte && effects.contains(Effects::KeyRange) => return true,
66            b if *b == krng_extern_byte && effects.contains(Effects::KeyRangeExtern) => {
67                return true
68            }
69            b if *b == post_krng_byte && effects.contains(Effects::PostKeyRange) => return true,
70            b if *b == post_krng_extern_byte && effects.contains(Effects::PostKeyRangeExtern) => {
71                return true
72            }
73            b if *b == this_address_byte && effects.contains(Effects::ThisAddress) => return true,
74            b if *b == this_contract_address_byte
75                && effects.contains(Effects::ThisContractAddress) =>
76            {
77                return true
78            }
79            b if *b == push => {
80                // Consume pushes arguments
81                iter.by_ref().take(8).for_each(|_| ());
82            }
83            _ => {}
84        }
85    }
86    false
87}
88
89#[cfg(test)]
90mod test {
91    use essential_types::convert::word_from_bytes;
92
93    use crate::{effects::bytes_contains_any, ToOpcode};
94
95    use super::{analyze, Access, Effects, Op, StateRead};
96
97    #[test]
98    fn none() {
99        let ops = &[];
100        assert_eq!(analyze(ops), Effects::empty());
101    }
102
103    #[test]
104    fn key_range() {
105        let ops = &[Op::StateRead(StateRead::KeyRange)];
106        let effects = analyze(ops);
107        assert!(effects.contains(Effects::KeyRange));
108    }
109
110    #[test]
111    fn key_range_extern() {
112        let ops = &[Op::StateRead(StateRead::KeyRangeExtern)];
113        let effects = analyze(ops);
114        assert!(effects.contains(Effects::KeyRangeExtern));
115    }
116
117    #[test]
118    fn this_address() {
119        let ops = &[Op::Access(Access::ThisAddress)];
120        let effects = analyze(ops);
121        assert!(effects.contains(Effects::ThisAddress));
122    }
123
124    #[test]
125    fn this_contract_address() {
126        let ops = &[Op::Access(Access::ThisContractAddress)];
127        let effects = analyze(ops);
128        assert!(effects.contains(Effects::ThisContractAddress));
129    }
130
131    #[test]
132    fn all_effects() {
133        let ops = &[
134            Op::StateRead(StateRead::KeyRange),
135            Op::StateRead(StateRead::KeyRangeExtern),
136            Op::Access(Access::ThisAddress),
137            Op::Access(Access::ThisContractAddress),
138        ];
139        let effects = analyze(ops);
140        assert!(effects.contains(Effects::KeyRange));
141        assert!(effects.contains(Effects::KeyRangeExtern));
142        assert!(effects.contains(Effects::ThisAddress));
143        assert!(effects.contains(Effects::ThisContractAddress));
144    }
145
146    #[test]
147    fn test_bytes_contains_any() {
148        use crate::short::*;
149        let to_bytes = |ops: &[Op]| crate::to_bytes(ops.iter().copied()).collect::<Vec<u8>>();
150
151        // No effects found
152        let effects = Effects::all();
153        assert!(!bytes_contains_any(&to_bytes(&[POP, POP, POP]), effects));
154
155        // Contains different effects
156        let effects = Effects::KeyRange | Effects::KeyRangeExtern;
157        assert!(!bytes_contains_any(&to_bytes(&[PKRNG, PKREX]), effects));
158
159        // Push contains key opcode
160        let key: u8 = PKRNG.to_opcode().into();
161        let bytes = [key, 0, 0, 0, 0, 0, 0, 0];
162        let word = word_from_bytes(bytes);
163        let effects = Effects::PostKeyRange;
164        assert!(!bytes_contains_any(&to_bytes(&[PUSH(word)]), effects));
165
166        // Push doesn't prevent detection
167        let effects = Effects::KeyRange;
168        assert!(bytes_contains_any(&to_bytes(&[PUSH(word), KRNG]), effects));
169
170        // Key range
171        let effects = Effects::KeyRange;
172        assert!(bytes_contains_any(&to_bytes(&[KRNG]), effects));
173
174        // Key range extern
175        let effects = Effects::KeyRangeExtern;
176        assert!(bytes_contains_any(&to_bytes(&[KREX]), effects));
177
178        // Post key range
179        let effects = Effects::PostKeyRange;
180        assert!(bytes_contains_any(&to_bytes(&[PKRNG]), effects));
181
182        // Post key range extern
183        let effects = Effects::PostKeyRangeExtern;
184        assert!(bytes_contains_any(&to_bytes(&[PKREX]), effects));
185
186        // This address
187        let effects = Effects::ThisAddress;
188        assert!(bytes_contains_any(&to_bytes(&[THIS]), effects));
189
190        // This contract address
191        let effects = Effects::ThisContractAddress;
192        assert!(bytes_contains_any(&to_bytes(&[THISC]), effects));
193
194        // Empty
195        let effects = Effects::empty();
196        assert!(!bytes_contains_any(&[], effects));
197
198        let effects = Effects::KeyRange;
199        assert!(!bytes_contains_any(&[], effects));
200    }
201}