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}