use std::collections::HashMap;
use std::convert::TryFrom;
use std::future::ready;
use std::sync::{Arc, Mutex};
use ethcontract::common::abi::{Function, StateMutability, Token};
use ethcontract::common::hash::H32;
use ethcontract::common::{Abi, FunctionExt};
use ethcontract::jsonrpc::serde::Serialize;
use ethcontract::jsonrpc::serde_json::to_value;
use ethcontract::jsonrpc::{Call, MethodCall, Params, Value};
use ethcontract::tokens::Tokenize;
use ethcontract::web3::types::{
Bytes, CallRequest, TransactionReceipt, TransactionRequest, U256, U64,
};
use ethcontract::web3::{helpers, BatchTransport, Error, RequestId, Transport};
use ethcontract::{Address, BlockNumber, H160, H256};
use parse::Parser;
use sign::verify;
use crate::details::transaction::TransactionResult;
use crate::range::TimesRange;
use crate::CallContext;
use std::any::Any;
mod default;
mod parse;
mod sign;
mod transaction;
#[derive(Clone)]
pub struct MockTransport {
state: Arc<Mutex<MockTransportState>>,
}
struct MockTransportState {
chain_id: u64,
gas_price: u64,
request_id: RequestId,
block: u64,
address: u64,
nonce: HashMap<Address, u64>,
contracts: HashMap<Address, Contract>,
receipts: HashMap<H256, TransactionReceipt>,
}
#[allow(clippy::type_complexity)]
impl MockTransport {
pub fn new(chain_id: u64) -> Self {
MockTransport {
state: Arc::new(Mutex::new(MockTransportState {
chain_id,
gas_price: 1,
request_id: 0,
block: 0,
address: 0,
nonce: HashMap::new(),
contracts: HashMap::new(),
receipts: HashMap::new(),
})),
}
}
pub fn deploy(&self, abi: &Abi) -> Address {
let mut state = self.state.lock().unwrap();
state.address += 1;
let address = H160::from_low_u64_be(state.address);
state.contracts.insert(address, Contract::new(address, abi));
address
}
pub fn deploy_with_address(&self, abi: &Abi, address: Address) {
let mut state = self.state.lock().unwrap();
assert!(
state
.contracts
.insert(address, Contract::new(address, abi))
.is_none(),
"replacing contract at address {:?}",
address
);
}
pub fn update_gas_price(&self, gas_price: u64) {
let mut state = self.state.lock().unwrap();
state.gas_price = gas_price;
}
pub fn checkpoint(&self) {
let mut state = self.state.lock().unwrap();
let contracts = state.contracts.values_mut();
for contract in contracts {
contract.checkpoint();
}
}
pub fn expect<P: Tokenize + Send + 'static, R: Tokenize + Send + 'static>(
&self,
address: Address,
signature: H32,
) -> (usize, usize) {
let mut state = self.state.lock().unwrap();
let method = state.method(address, signature);
method.expect::<P, R>()
}
pub fn contract_checkpoint(&self, address: Address) {
let mut state = self.state.lock().unwrap();
let contract = state.contract(address);
contract.checkpoint();
}
pub fn times<P: Tokenize + Send + 'static, R: Tokenize + Send + 'static>(
&self,
address: Address,
signature: H32,
index: usize,
generation: usize,
times: TimesRange,
) {
let mut state = self.state.lock().unwrap();
let expectation = state.expectation::<P, R>(address, signature, index, generation);
if expectation.sequence.is_some() && !times.is_exact() {
panic!("only expectations with an exact call count can be in a sequences")
}
if expectation.sequence.is_some() && times.lower_bound() == 0 {
panic!("expectation in a sequences should be called at least once")
}
expectation.times = times;
}
pub fn in_sequence<P: Tokenize + Send + 'static, R: Tokenize + Send + 'static>(
&self,
address: Address,
signature: H32,
index: usize,
generation: usize,
sequence: &mut mockall::Sequence,
) {
let mut state = self.state.lock().unwrap();
let expectation = state.expectation::<P, R>(address, signature, index, generation);
if !expectation.times.is_exact() {
panic!("only expectations with an exact call count can be in a sequences")
}
if expectation.times.lower_bound() == 0 {
panic!("expectation in a sequences should be called at least once")
}
if expectation.sequence.is_some() {
panic!("expectation can't be in multiple sequences")
}
expectation.sequence = Some(sequence.next_handle());
}
pub fn confirmations<P: Tokenize + Send + 'static, R: Tokenize + Send + 'static>(
&self,
address: Address,
signature: H32,
index: usize,
generation: usize,
confirmations: u64,
) {
let mut state = self.state.lock().unwrap();
let expectation = state.expectation::<P, R>(address, signature, index, generation);
expectation.confirmations = confirmations;
}
pub fn predicate<P: Tokenize + Send + 'static, R: Tokenize + Send + 'static>(
&self,
address: Address,
signature: H32,
index: usize,
generation: usize,
pred: Box<dyn predicates::Predicate<P> + Send>,
) {
let mut state = self.state.lock().unwrap();
let expectation = state.expectation::<P, R>(address, signature, index, generation);
expectation.predicate = Predicate::Predicate(pred);
}
pub fn predicate_fn<P: Tokenize + Send + 'static, R: Tokenize + Send + 'static>(
&self,
address: Address,
signature: H32,
index: usize,
generation: usize,
pred: Box<dyn Fn(&P) -> bool + Send>,
) {
let mut state = self.state.lock().unwrap();
let expectation = state.expectation::<P, R>(address, signature, index, generation);
expectation.predicate = Predicate::Function(pred);
}
pub fn predicate_fn_ctx<P: Tokenize + Send + 'static, R: Tokenize + Send + 'static>(
&self,
address: Address,
signature: H32,
index: usize,
generation: usize,
pred: Box<dyn Fn(&CallContext, &P) -> bool + Send>,
) {
let mut state = self.state.lock().unwrap();
let expectation = state.expectation::<P, R>(address, signature, index, generation);
expectation.predicate = Predicate::TxFunction(pred);
}
pub fn allow_calls<P: Tokenize + Send + 'static, R: Tokenize + Send + 'static>(
&self,
address: Address,
signature: H32,
index: usize,
generation: usize,
allow_calls: bool,
) {
let mut state = self.state.lock().unwrap();
let expectation = state.expectation::<P, R>(address, signature, index, generation);
expectation.allow_calls = allow_calls;
}
pub fn allow_transactions<P: Tokenize + Send + 'static, R: Tokenize + Send + 'static>(
&self,
address: Address,
signature: H32,
index: usize,
generation: usize,
allow_transactions: bool,
) {
let mut state = self.state.lock().unwrap();
let expectation = state.expectation::<P, R>(address, signature, index, generation);
expectation.allow_transactions = allow_transactions;
}
pub fn returns<P: Tokenize + Send + 'static, R: Tokenize + Send + 'static>(
&self,
address: Address,
signature: H32,
index: usize,
generation: usize,
returns: R,
) {
let token = returns.into_token();
let mut state = self.state.lock().unwrap();
let expectation = state.expectation::<P, R>(address, signature, index, generation);
expectation.returns = Returns::Const(token);
}
pub fn returns_fn<P: Tokenize + Send + 'static, R: Tokenize + Send + 'static>(
&self,
address: Address,
signature: H32,
index: usize,
generation: usize,
returns: Box<dyn Fn(P) -> Result<R, String> + Send>,
) {
let mut state = self.state.lock().unwrap();
let expectation = state.expectation::<P, R>(address, signature, index, generation);
expectation.returns = Returns::Function(returns);
}
pub fn returns_fn_ctx<P: Tokenize + Send + 'static, R: Tokenize + Send + 'static>(
&self,
address: Address,
signature: H32,
index: usize,
generation: usize,
returns: Box<dyn Fn(&CallContext, P) -> Result<R, String> + Send>,
) {
let mut state = self.state.lock().unwrap();
let expectation = state.expectation::<P, R>(address, signature, index, generation);
expectation.returns = Returns::TxFunction(returns);
}
pub fn returns_error<P: Tokenize + Send + 'static, R: Tokenize + Send + 'static>(
&self,
address: Address,
signature: H32,
index: usize,
generation: usize,
error: String,
) {
let mut state = self.state.lock().unwrap();
let expectation = state.expectation::<P, R>(address, signature, index, generation);
expectation.returns = Returns::Error(error);
}
pub fn returns_default<P: Tokenize + Send + 'static, R: Tokenize + Send + 'static>(
&self,
address: Address,
signature: H32,
index: usize,
generation: usize,
) {
let mut state = self.state.lock().unwrap();
let expectation = state.expectation::<P, R>(address, signature, index, generation);
expectation.returns = Returns::Default;
}
}
impl MockTransportState {
fn contract(&mut self, address: Address) -> &mut Contract {
match self.contracts.get_mut(&address) {
Some(contract) => contract,
None => panic!("there is no mocked contract with address {:#x}", address),
}
}
fn method(&mut self, address: Address, signature: H32) -> &mut Method {
self.contract(address).method(signature)
}
fn expectation<P: Tokenize + Send + 'static, R: Tokenize + Send + 'static>(
&mut self,
address: Address,
signature: H32,
index: usize,
generation: usize,
) -> &mut Expectation<P, R> {
self.contract(address)
.method(signature)
.expectation(index, generation)
}
}
impl Transport for MockTransport {
type Out = std::future::Ready<Result<Value, Error>>;
fn prepare(&self, method: &str, params: Vec<Value>) -> (RequestId, Call) {
let mut state = self.state.lock().unwrap();
let id = state.request_id;
state.request_id += 1;
let request = helpers::build_request(id, method, params);
(id, request)
}
fn send(&self, _: RequestId, request: Call) -> Self::Out {
ready(self.process_call(request))
}
}
impl BatchTransport for MockTransport {
type Batch = std::future::Ready<Result<Vec<Result<Value, Error>>, Error>>;
fn send_batch<T>(&self, requests: T) -> Self::Batch
where
T: IntoIterator<Item = (RequestId, Call)>,
{
let mut results = Vec::new();
for (_, call) in requests.into_iter() {
results.push(self.process_call(call));
}
ready(Ok(results))
}
}
impl MockTransport {
fn process_call(&self, request: Call) -> Result<Value, Error> {
let MethodCall { method, params, .. } = match request {
Call::MethodCall(method_call) => method_call,
Call::Notification(_) => panic!("rpc notifications are not supported"),
_ => panic!("unknown or invalid rpc call type"),
};
let params = match params {
Params::None => Vec::new(),
Params::Array(array) => array,
Params::Map(_) => panic!("passing arguments by map is not supported"),
};
let result = match method.as_str() {
"eth_blockNumber" => {
let name = "eth_blockNumber";
self.eth_block_number(Parser::new(name, params))
}
"eth_chainId" => {
let name = "eth_chainId";
self.eth_chain_id(Parser::new(name, params))
}
"eth_getTransactionCount" => {
let name = "eth_getTransactionCount";
self.eth_transaction_count(Parser::new(name, params))
}
"eth_gasPrice" => {
let name = "eth_gasPrice";
self.eth_gas_price(Parser::new(name, params))
}
"eth_estimateGas" => {
let name = "eth_estimateGas";
self.eth_estimate_gas(Parser::new(name, params))
}
"eth_call" => {
let name = "eth_call";
self.eth_call(Parser::new(name, params))
}
"eth_sendTransaction" => {
let name = "eth_sendTransaction";
self.eth_send_transaction(Parser::new(name, params))
}
"eth_sendRawTransaction" => {
let name = "eth_sendRawTransaction";
self.eth_send_raw_transaction(Parser::new(name, params))
}
"eth_getTransactionReceipt" => {
let name = "eth_getTransactionReceipt";
self.eth_get_transaction_receipt(Parser::new(name, params))
}
"net_version" => {
let name = "net_version";
self.net_version(Parser::new(name, params))
}
unsupported => panic!("mock node does not support rpc method {:?}", unsupported),
};
result
}
fn eth_block_number(&self, args: Parser) -> Result<Value, Error> {
args.done();
let state = self.state.lock().unwrap();
Self::ok(U64::from(state.block))
}
fn eth_chain_id(&self, args: Parser) -> Result<Value, Error> {
args.done();
let state = self.state.lock().unwrap();
Self::ok(U256::from(state.chain_id))
}
fn eth_transaction_count(&self, mut args: Parser) -> Result<Value, Error> {
let address: Address = args.arg();
let block: Option<BlockNumber> = args.block_number_opt();
args.done();
let block = block.unwrap_or(BlockNumber::Pending);
let state = self.state.lock().unwrap();
let transaction_count = match block {
BlockNumber::Earliest => 0,
BlockNumber::Number(n) if n == 0.into() => 0,
BlockNumber::Number(n) if n != state.block.into() => {
panic!("mock node does not support returning transaction count for specific block number");
}
_ => state.nonce.get(&address).copied().unwrap_or(0),
};
Self::ok(U256::from(transaction_count))
}
fn eth_gas_price(&self, args: Parser) -> Result<Value, Error> {
args.done();
let state = self.state.lock().unwrap();
Self::ok(U256::from(state.gas_price))
}
fn eth_estimate_gas(&self, mut args: Parser) -> Result<Value, Error> {
let request: CallRequest = args.arg();
let block: Option<BlockNumber> = args.block_number_opt();
args.done();
let state = self.state.lock().unwrap();
let block = block.unwrap_or(BlockNumber::Pending);
match block {
BlockNumber::Earliest => {
panic!("mock node does not support executing methods on earliest block");
}
BlockNumber::Number(n) if n != state.block.into() => {
panic!("mock node does not support executing methods on non-last block");
}
_ => (),
}
match request.to {
None => panic!("call's 'to' field is empty"),
Some(to) => to,
};
Self::ok(U256::from(1))
}
fn eth_call(&self, mut args: Parser) -> Result<Value, Error> {
let request: CallRequest = args.arg();
let block: Option<BlockNumber> = args.block_number_opt();
let mut state = self.state.lock().unwrap();
let block = block.unwrap_or(BlockNumber::Pending);
match block {
BlockNumber::Earliest => {
panic!("mock node does not support executing methods on earliest block");
}
BlockNumber::Number(n) if n != state.block.into() => {
panic!("mock node does not support executing methods on non-last block");
}
_ => (),
}
let from = request.from.unwrap_or_default();
let to = match request.to {
None => panic!("call's 'to' field is empty"),
Some(to) => to,
};
let nonce = state.nonce.get(&from).copied().unwrap_or(0);
let gas_price = state.gas_price;
let contract = state.contract(to);
let context = CallContext {
is_view_call: true,
from: request.from.unwrap_or_default(),
to,
nonce: U256::from(nonce),
gas: request.gas.unwrap_or_else(|| U256::from(1)),
gas_price: request.gas.unwrap_or_else(|| U256::from(gas_price)),
value: request.value.unwrap_or_default(),
};
let data = request.data.unwrap_or_default();
let result = contract.process_tx(context, &data.0);
match result.result {
Ok(data) => Self::ok(Bytes(data)),
Err(err) => Err(Error::Rpc(ethcontract::jsonrpc::Error {
code: ethcontract::jsonrpc::ErrorCode::ServerError(0),
message: format!("execution reverted: {}", err),
data: None,
})),
}
}
fn eth_send_transaction(&self, mut args: Parser) -> Result<Value, Error> {
let _request: TransactionRequest = args.arg();
args.done();
panic!("mock node can't sign transactions, use offline signing with private key");
}
fn eth_send_raw_transaction(&self, mut args: Parser) -> Result<Value, Error> {
let raw_tx: Bytes = args.arg();
args.done();
let mut state = self.state.lock().unwrap();
let tx = verify(&raw_tx.0, state.chain_id);
let nonce = state.nonce.entry(tx.from).or_insert(0);
assert!(
*nonce == tx.nonce.as_u64(),
"nonce mismatch for account {:#x}: expected {}, actual {}",
tx.from,
tx.nonce.as_u64(),
nonce
);
*nonce += 1;
let contract = state.contract(tx.to);
let context = CallContext {
is_view_call: false,
from: tx.from,
to: tx.to,
nonce: tx.nonce,
gas: tx.gas,
gas_price: tx.gas_price,
value: tx.value,
};
let result = contract.process_tx(context, &tx.data);
state.block += 1;
let receipt = TransactionReceipt {
transaction_hash: tx.hash,
transaction_index: U64::from(0),
block_hash: None,
block_number: Some(U64::from(state.block)),
from: tx.from,
to: Some(tx.to),
cumulative_gas_used: U256::from(1),
gas_used: None,
contract_address: None,
logs: vec![],
status: Some(U64::from(result.result.is_ok() as u64)),
root: None,
logs_bloom: Default::default(),
transaction_type: None,
effective_gas_price: Some(tx.gas_price),
};
state.receipts.insert(tx.hash, receipt);
state.block += result.confirmations;
Self::ok(tx.hash)
}
fn eth_get_transaction_receipt(&self, mut args: Parser) -> Result<Value, Error> {
let transaction: H256 = args.arg();
args.done();
let state = self.state.lock().unwrap();
Self::ok(state.receipts.get(&transaction).unwrap_or_else(|| {
panic!("there is no transaction with hash {:#x}", transaction);
}))
}
fn net_version(&self, args: Parser) -> Result<Value, Error> {
args.done();
let state = self.state.lock().unwrap();
Self::ok(state.chain_id.to_string())
}
fn ok<T: Serialize>(t: T) -> Result<Value, Error> {
Ok(to_value(t).unwrap())
}
}
impl std::fmt::Debug for MockTransport {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("MockTransport")
}
}
struct Contract {
address: Address,
methods: HashMap<H32, Method>,
}
impl Contract {
fn new(address: Address, abi: &Abi) -> Self {
let mut methods = HashMap::new();
for functions in abi.functions.values() {
for function in functions {
methods.insert(function.selector(), Method::new(address, function.clone()));
}
}
Contract { address, methods }
}
fn method(&mut self, signature: H32) -> &mut Method {
match self.methods.get_mut(&signature) {
Some(method) => method,
None => panic!(
"contract {:#x} doesn't have method with signature 0x{}",
self.address,
hex::encode(signature)
),
}
}
fn process_tx(&mut self, tx: CallContext, data: &[u8]) -> TransactionResult {
assert!(data.len() >= 4, "transaction has invalid call data");
let signature = H32::try_from(&data[0..4]).unwrap();
let method = self.method(signature);
method.process_tx(tx, data)
}
fn checkpoint(&mut self) {
for method in self.methods.values_mut() {
method.checkpoint();
}
}
}
impl Drop for Contract {
fn drop(&mut self) {
if !std::thread::panicking() {
self.checkpoint();
}
}
}
struct Method {
description: String,
function: Function,
generation: usize,
expectations: Vec<Box<dyn ExpectationApi>>,
}
impl Method {
fn new(address: Address, function: Function) -> Self {
let description = format!("{:?} on contract {:#x}", function.abi_signature(), address);
Method {
description,
function,
generation: 0,
expectations: Vec::new(),
}
}
fn expect<P: Tokenize + Send + 'static, R: Tokenize + Send + 'static>(
&mut self,
) -> (usize, usize) {
let index = self.expectations.len();
self.expectations.push(Box::new(Expectation::<P, R>::new()));
(index, self.generation)
}
fn expectation<P: Tokenize + Send + 'static, R: Tokenize + Send + 'static>(
&mut self,
index: usize,
generation: usize,
) -> &mut Expectation<P, R> {
assert!(
generation == self.generation,
"old expectations are not valid after checkpoint"
);
let expectation: &mut Expectation<P, R> = self
.expectations
.get_mut(index)
.unwrap()
.as_any()
.downcast_mut()
.unwrap();
if expectation.checked {
panic!(
"can't modify expectation for {} because it was already in use",
self.description
)
}
expectation
}
fn process_tx(&mut self, tx: CallContext, data: &[u8]) -> TransactionResult {
if !tx.value.is_zero() && self.function.state_mutability != StateMutability::Payable {
panic!(
"call to non-payable {} with non-zero value {}",
self.description, tx.value,
)
}
let params = self
.function
.decode_input(&data[4..])
.unwrap_or_else(|e| panic!("unable to decode input for {}: {:?}", self.description, e));
for expectation in self.expectations.iter_mut() {
if expectation.is_active() {
if let Some(result) =
expectation.process_tx(&tx, &self.description, &self.function, params.clone())
{
return result;
}
}
}
panic!("unexpected call to {}", self.description)
}
fn checkpoint(&mut self) {
for expectation in self.expectations.iter_mut() {
expectation.verify(&self.description);
}
self.generation += 1;
self.expectations.clear();
}
}
trait ExpectationApi: Send {
fn as_any(&mut self) -> &mut dyn Any;
fn is_active(&self) -> bool;
fn process_tx(
&mut self,
tx: &CallContext,
description: &str,
function: &Function,
params: Vec<Token>,
) -> Option<TransactionResult>;
fn verify(&self, description: &str);
}
struct Expectation<P: Tokenize + Send + 'static, R: Tokenize + Send + 'static> {
times: TimesRange,
used: usize,
checked: bool,
confirmations: u64,
predicate: Predicate<P>,
allow_calls: bool,
allow_transactions: bool,
returns: Returns<P, R>,
sequence: Option<mockall::SeqHandle>,
}
impl<P: Tokenize + Send + 'static, R: Tokenize + Send + 'static> Expectation<P, R> {
fn new() -> Self {
Expectation {
times: TimesRange::default(),
used: 0,
checked: false,
confirmations: 0,
predicate: Predicate::None,
allow_calls: true,
allow_transactions: true,
returns: Returns::Default,
sequence: None,
}
}
}
impl<P: Tokenize + Send + 'static, R: Tokenize + Send + 'static> ExpectationApi
for Expectation<P, R>
{
fn as_any(&mut self) -> &mut dyn Any {
self
}
fn is_active(&self) -> bool {
self.times.can_call(self.used)
}
fn process_tx(
&mut self,
tx: &CallContext,
description: &str,
function: &Function,
params: Vec<Token>,
) -> Option<TransactionResult> {
self.checked = true;
if tx.is_view_call && !self.allow_calls || !tx.is_view_call && !self.allow_transactions {
return None;
}
if !self.times.can_call(self.used) {
return None;
}
let param = P::from_token(Token::Tuple(params))
.unwrap_or_else(|e| panic!("unable to decode input for {}: {:?}", description, e));
if !self.predicate.can_call(tx, ¶m) {
return None;
}
self.used += 1;
if let Some(sequence) = &self.sequence {
sequence.verify(description);
if self.used == self.times.lower_bound() {
sequence.satisfy();
}
}
let result = self
.returns
.process_tx(function, tx, param)
.map(|result| ethcontract::common::abi::encode(&result));
Some(TransactionResult {
result,
confirmations: self.confirmations,
})
}
fn verify(&self, description: &str) {
if !self.times.contains(self.used) {
panic!(
"{} was called {} {}, but it was expected to be called {} {} {}",
description,
self.used,
if self.used == 1 { "time" } else { "times" },
if self.times.is_exact() {
"exactly"
} else {
"at least"
},
self.times.lower_bound(),
if self.times.lower_bound() == 1 {
"time"
} else {
"times"
}
)
}
}
}
#[allow(clippy::enum_variant_names, clippy::type_complexity)]
enum Predicate<P: Tokenize + Send + 'static> {
None,
Predicate(Box<dyn predicates::Predicate<P> + Send>),
Function(Box<dyn Fn(&P) -> bool + Send>),
TxFunction(Box<dyn Fn(&CallContext, &P) -> bool + Send>),
}
impl<P: Tokenize + Send + 'static> Predicate<P> {
fn can_call(&self, tx: &CallContext, param: &P) -> bool {
match self {
Predicate::None => true,
Predicate::Predicate(p) => p.eval(param),
Predicate::Function(f) => f(param),
Predicate::TxFunction(f) => f(tx, param),
}
}
}
#[allow(clippy::type_complexity)]
enum Returns<P: Tokenize + Send + 'static, R: Tokenize + Send + 'static> {
Default,
Error(String),
Const(Token),
Function(Box<dyn Fn(P) -> Result<R, String> + Send>),
TxFunction(Box<dyn Fn(&CallContext, P) -> Result<R, String> + Send>),
}
impl<P: Tokenize + Send + 'static, R: Tokenize + Send + 'static> Returns<P, R> {
fn process_tx(
&self,
function: &Function,
tx: &CallContext,
param: P,
) -> Result<Vec<Token>, String> {
match self {
Returns::Default => Ok(function
.outputs
.iter()
.map(|i| default::default(&i.kind))
.collect()),
Returns::Error(error) => Err(error.clone()),
Returns::Const(token) => Ok(Self::convert_result(token.clone(), function)),
Returns::Function(f) => {
f(param).map(|x| Self::convert_result(x.into_token(), function))
}
Returns::TxFunction(f) => {
f(tx, param).map(|x| Self::convert_result(x.into_token(), function))
}
}
}
fn convert_result(token: Token, function: &Function) -> Vec<Token> {
if function.outputs.len() == 1 {
vec![token]
} else {
match token {
Token::Tuple(tuple) => tuple,
_ => unreachable!(),
}
}
}
}