trn_pact/interpreter/mod.rs
1// Copyright 2019 Centrality Investments Limited
2// This file is part of Pact.
3//
4// Licensed under the Apache License v2.0;
5// you may not use this file except in compliance with the License.
6// Unless required by applicable law or agreed to in writing, software
7// distributed under the License is distributed on an "AS IS" BASIS,
8// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9// See the License for the specific language governing permissions and
10// limitations under the License.
11
12// You should have received a copy of the Apache License v2.0
13// along with Pact. If not, see:
14// <https://futureverse.com/licenses/apachev2.txt>
15
16//!
17//! The pact bytecode interpreter
18//!
19use crate::types::PactType;
20
21pub use crate::types::opcode::{
22 Comparator, Conjunction, OpCode, OpComp, OpConj, OpIndices, OpLoad,
23};
24
25/// Interpret some pact byte code (`source`) with input data registers (`input_data`) and
26/// user data registers (`user_data`).
27/// Returns a boolean indicating whether the pact contract was validated or not,
28/// An `InterpErr` is returned on a runtime error e.g. malformed byte code, missing data, invalid OpCode etc.
29pub fn interpret(
30 input_data: &[PactType],
31 user_data: &[PactType],
32 source: &[u8],
33) -> Result<bool, InterpErr> {
34 let mut interpreter = Interpreter::new(input_data, user_data);
35 let mut scanner = source.iter();
36 while let Some(op) = OpCode::parse(&mut scanner)? {
37 match interpreter.interpret(op) {
38 Err(InterpErr::Refused) => break,
39 Err(err) => return Err(err),
40 Ok(_) => {}
41 }
42 }
43
44 match interpreter.state {
45 State::AssertionTrue => Ok(true),
46 State::Failed | State::AssertionFalse => Ok(false),
47 // Any other state is an Unexpected end of input
48 _invalid => Err(InterpErr::UnexpectedEOI("incomplete operation")),
49 }
50}
51
52/// An interpreter error
53#[derive(Debug, PartialEq)]
54pub enum InterpErr {
55 /// A comparison operator failed with incompatible types on LHS and RHS
56 TypeMismatch,
57 /// A comparison operator failed because it is not supported on the type
58 BadTypeOperation,
59 /// Unexpected end of input
60 UnexpectedEOI(&'static str),
61 /// Encountered an unexpected OpCode given the context
62 UnexpectedOpCode(u8),
63 /// Encountered an OpCode the interpreter does not support yet
64 UnsupportedOpCode(&'static str),
65 /// Encountered an invalid OpCode
66 InvalidOpCode(u8),
67 /// A referenced index in the data table does not exist
68 MissingIndex(u8),
69 /// Raised when trying to execute an OpCode from an interpreter which is in a failed state
70 Refused,
71}
72
73/// Evaluate a comparator OpCode returning its result
74fn eval_comparator(
75 comparator: Comparator,
76 lhs: &PactType,
77 rhs: &PactType,
78) -> Result<bool, InterpErr> {
79 let value = match (lhs, rhs) {
80 (PactType::Numeric(l), PactType::Numeric(r)) => match comparator.op {
81 OpComp::EQ => Ok(l == r),
82 OpComp::GT => Ok(l > r),
83 OpComp::GTE => Ok(l >= r),
84 _ => Err(InterpErr::BadTypeOperation),
85 },
86 (PactType::StringLike(l), PactType::StringLike(r)) => match comparator.op {
87 OpComp::EQ => Ok(l == r),
88 _ => Err(InterpErr::BadTypeOperation),
89 },
90 (PactType::List(_), _) => match comparator.op {
91 _ => Err(InterpErr::BadTypeOperation),
92 },
93 (l, PactType::List(r)) => match comparator.op {
94 OpComp::IN => Ok(r.contains(l)),
95 _ => Err(InterpErr::BadTypeOperation),
96 },
97 _ => Err(InterpErr::TypeMismatch),
98 }?;
99
100 // Apply inversion if required
101 if comparator.invert {
102 Ok(!value)
103 } else {
104 Ok(value)
105 }
106}
107
108/// Evaluate a conjunction OpCode given an LHS and RHS boolean
109fn eval_conjunction(conjunction: &Conjunction, lhs: bool, rhs: bool) -> Result<bool, InterpErr> {
110 let value = match conjunction.op {
111 OpConj::AND => lhs & rhs,
112 OpConj::OR => lhs | rhs,
113 OpConj::XOR => lhs ^ rhs,
114 };
115
116 // Apply inversion if required
117 if conjunction.invert {
118 Ok(!value)
119 } else {
120 Ok(value)
121 }
122}
123
124/// The pact interpreter
125/// It evaluates `OpCode`s maintaining the state of the current contract execution
126/// Uses the rust type system to encode state, see: https://hoverbear.org/2016/10/12/rust-state-machine-pattern/
127/// States provide transformations into other valid states and failure cases.
128#[cfg_attr(feature = "std", derive(Debug))]
129pub struct Interpreter<'a> {
130 state: State,
131 input_data: &'a [PactType],
132 user_data: &'a [PactType],
133}
134
135impl<'a> Interpreter<'a> {
136 /// Return a new interpreter, ready for execution
137 pub fn new(input_data: &'a [PactType], user_data: &'a [PactType]) -> Self {
138 Interpreter {
139 state: State::Initial,
140 input_data,
141 user_data,
142 }
143 }
144
145 /// Executes a comparator OpCode
146 /// This belongs to the interpreter state machine and will update state
147 /// based on the outcome
148 fn execute_comparator(&mut self, op: OpCode) -> Result<(), InterpErr> {
149 match op {
150 OpCode::COMP(comparator) => {
151 // Gather left and right hand side values
152 let lhs = self
153 .input_data
154 .get(comparator.indices.lhs as usize)
155 .ok_or(InterpErr::MissingIndex(comparator.indices.lhs))?;
156
157 let rhs = match comparator.load {
158 OpLoad::INPUT_VS_USER => self
159 .user_data
160 .get(comparator.indices.rhs as usize)
161 .ok_or(InterpErr::MissingIndex(comparator.indices.rhs)),
162 OpLoad::INPUT_VS_INPUT => self
163 .input_data
164 .get(comparator.indices.rhs as usize)
165 .ok_or(InterpErr::MissingIndex(comparator.indices.rhs)),
166 }?;
167
168 let mut result = eval_comparator(comparator, &lhs, rhs)?;
169
170 // Evaluate the conjunction if necessary
171 match &self.state {
172 State::Conjunctive {
173 last_assertion,
174 conjunction,
175 } => {
176 result = eval_conjunction(conjunction, *last_assertion, result)?;
177 }
178 _ => {}
179 };
180
181 // The assertions and operations upto this point have all been collapsed into
182 // a single boolean.
183 if result {
184 self.state = State::AssertionTrue;
185 } else {
186 self.state = State::AssertionFalse;
187 };
188 Ok(())
189 }
190 _ => Err(InterpErr::UnexpectedOpCode(op.into())),
191 }
192 }
193
194 /// Interpreter state machine
195 pub fn interpret(&mut self, op: OpCode) -> Result<(), InterpErr> {
196 match &self.state {
197 // First op code must be a comparator
198 State::Initial => self.execute_comparator(op),
199 State::AssertionTrue => match op {
200 OpCode::COMP(_) => self.execute_comparator(op),
201 OpCode::CONJ(conjunction) => {
202 self.state = State::Conjunctive {
203 last_assertion: true,
204 conjunction: conjunction,
205 };
206 Ok(())
207 }
208 },
209 State::AssertionFalse => {
210 match op {
211 // There is no continuation of the last assertion.
212 // This is now considered a failed clause, and hence the contract has failed
213 OpCode::COMP(_) => {
214 self.state = State::Failed;
215 Ok(())
216 }
217 // The conjunction will determine whether the contract has failed or succeeded
218 OpCode::CONJ(conjunction) => {
219 self.state = State::Conjunctive {
220 last_assertion: false,
221 conjunction: conjunction,
222 };
223 Ok(())
224 }
225 }
226 }
227 State::Conjunctive {
228 last_assertion: _,
229 conjunction: _,
230 } => {
231 // A Conjunction must be followed by a comparator
232 match op {
233 OpCode::COMP(_) => self.execute_comparator(op),
234 OpCode::CONJ(_) => {
235 return Err(InterpErr::UnexpectedOpCode(op.into()));
236 }
237 }
238 }
239 State::Failed => Err(InterpErr::Refused),
240 }
241 }
242}
243
244#[cfg_attr(feature = "std", derive(Debug))]
245pub enum State {
246 /// The initial interpreter state
247 Initial,
248 /// The last assertion evaluated as false
249 AssertionFalse,
250 /// The last assertion evaluated as true
251 AssertionTrue,
252 /// The last assertion was followed by a conjunction.
253 /// The interpreter is awaiting the next OpCode as the RHS.
254 Conjunctive {
255 // The last assertion truthiness (LHS of conjunction)
256 last_assertion: bool,
257 // The conjunction to apply. <LHS> <conjunction> <RHS>
258 conjunction: Conjunction,
259 },
260 /// The contract invariants were not maintained
261 /// it has failed.
262 Failed,
263}