Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RVRT instruction implementation #27

Merged
merged 1 commit into from
Oct 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/interpreter/executors/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ where

ProgramState::ReturnData(d) => Ok(ProgramState::ReturnData(d)),

ProgramState::Revert(w) => Ok(ProgramState::Revert(w)),

ProgramState::RunProgram(_) => self.run_program(),

ProgramState::VerifyPredicate(_) => unimplemented!(),
Expand Down
5 changes: 4 additions & 1 deletion src/interpreter/executors/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,10 @@ where
result = Ok(ExecuteState::ReturnData(digest));
}

Opcode::RVRT(ra) if self.gas_charge(&op).is_ok() && self.revert(self.registers[ra]).is_ok() => {
result = Ok(ExecuteState::Revert(self.registers[ra]))
}

Opcode::ALOC(ra) if self.gas_charge(&op).is_ok() && self.malloc(self.registers[ra]).is_ok() => {}

Opcode::CFEI(imm)
Expand Down Expand Up @@ -467,7 +471,6 @@ where

Opcode::LDC(_ra, _rb, _rc) => result = Err(InterpreterError::OpcodeUnimplemented(op)),
Opcode::SLDC(_ra, _rb, _rc) => result = Err(InterpreterError::OpcodeUnimplemented(op)),
Opcode::RVRT(_ra) => result = Err(InterpreterError::OpcodeUnimplemented(op)),
Opcode::TR(_ra, _rb, _rc) => result = Err(InterpreterError::OpcodeUnimplemented(op)),
Opcode::TRO(_ra, _rb, _rc, _rd) => result = Err(InterpreterError::OpcodeUnimplemented(op)),
Opcode::Undefined => result = Err(InterpreterError::OpcodeFailure(op)),
Expand Down
8 changes: 6 additions & 2 deletions src/interpreter/executors/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,16 @@ where
return Ok(ProgramState::ReturnData(d));
}

ExecuteState::Revert(r) => {
return Ok(ProgramState::Revert(r));
}

ExecuteState::Proceed => (),

#[cfg(feature = "debug")]
ExecuteState::DebugEvent(d) => {
return Ok(ProgramState::RunProgram(d));
}

_ => (),
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/interpreter/executors/predicate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,14 @@ where
// A predicate is not expected to return data
ExecuteState::ReturnData(_) => return Err(InterpreterError::PredicateFailure),

ExecuteState::Revert(r) => return Ok(ProgramState::Revert(r)),

ExecuteState::Proceed => (),

#[cfg(feature = "debug")]
ExecuteState::DebugEvent(d) => {
return Ok(ProgramState::VerifyPredicate(d));
}

_ => (),
}

if self.registers[REG_PC] < pc || self.registers[REG_PC] >= end {
Expand Down
23 changes: 23 additions & 0 deletions src/interpreter/flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ where
self.registers[REG_RET] = a;
self.registers[REG_RETL] = 0;

// TODO if ret instruction is in memory boundary, inc_pc shouldn't fail
vlopes11 marked this conversation as resolved.
Show resolved Hide resolved
self.return_from_context(receipt)
}

Expand All @@ -181,4 +182,26 @@ where

self.return_from_context(receipt)
}

pub(crate) fn revert(&mut self, a: Word) -> Result<(), InterpreterError> {
let receipt = Receipt::revert(
self.internal_contract_or_default(),
a,
self.registers[REG_PC],
self.registers[REG_IS],
);

self.receipts.push(receipt);

// TODO
// All OutputContract outputs will have the same amount and stateRoot as on
// initialization.
//
// All OutputVariable outputs will have to and amount of zero.
//
// All OutputContractConditional outputs will have contractID, amount, and
// stateRoot of zero.

Ok(())
}
}
2 changes: 2 additions & 0 deletions src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub enum ExecuteState {
Proceed,
Return(Word),
ReturnData(Bytes32),
Revert(Word),

#[cfg(feature = "debug")]
DebugEvent(DebugEval),
Expand All @@ -44,6 +45,7 @@ impl From<DebugEval> for ExecuteState {
pub enum ProgramState {
Return(Word),
ReturnData(Bytes32),
Revert(Word),

#[cfg(feature = "debug")]
RunProgram(DebugEval),
Expand Down
185 changes: 185 additions & 0 deletions tests/flow.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use fuel_tx::crypto::Hasher;
use fuel_types::bytes;
use fuel_vm::consts::*;
use fuel_vm::prelude::*;
use rand::rngs::StdRng;
Expand Down Expand Up @@ -285,3 +287,186 @@ fn call_frame_code_offset() {
assert_eq!(ssp, fp + stack);
assert_eq!(ssp, sp_p);
}

#[test]
fn revert() {
let rng = &mut StdRng::seed_from_u64(2322u64);

let mut client = MemoryClient::default();

let gas_price = 0;
let gas_limit = 1_000_000;
let maturity = 0;

let salt: Salt = rng.gen();

#[rustfmt::skip]
let call_arguments_parser: Vec<Opcode> = vec![
Opcode::ADDI(0x10, REG_FP, CallFrame::a_offset() as Immediate12),
Opcode::LW(0x10, 0x10, 0),
Opcode::ADDI(0x11, REG_FP, CallFrame::b_offset() as Immediate12),
Opcode::LW(0x11, 0x11, 0),
];

#[rustfmt::skip]
let routine_add_word_to_state: Vec<Opcode> = vec![
Opcode::JNEI(0x10, 0x30, 13), // (0, b) Add word to state
Opcode::LW(0x20, 0x11, 4), // r[0x20] := m[b+32, 8]
Opcode::SRW(0x21, 0x11), // r[0x21] := s[m[b, 32], 8]
Opcode::ADD(0x20, 0x20, 0x21), // r[0x20] += r[0x21]
Opcode::SWW(0x11, 0x20), // s[m[b,32]] := r[0x20]
Opcode::LOG(0x20, 0x21, 0x00, 0x00),
Opcode::RET(REG_ONE),
];

let program: Witness = call_arguments_parser
.into_iter()
.chain(routine_add_word_to_state.into_iter())
.collect::<Vec<u8>>()
.into();

let contract = Contract::from(program.as_ref());
let contract_root = contract.root();
let contract = contract.id(&salt, &contract_root);

let output = Output::contract_created(contract);

let bytecode_witness = 0;
let tx = Transaction::create(
gas_price,
gas_limit,
maturity,
bytecode_witness,
salt,
vec![],
vec![],
vec![output],
vec![program],
);

// Deploy the contract into the blockchain
client.transition(vec![tx]).expect("Failed to transact");

let input = Input::contract(rng.gen(), rng.gen(), rng.gen(), contract);
let output = Output::contract(0, rng.gen(), rng.gen());

// The script needs to locate the data offset at runtime. Hence, we need to know
// upfront the serialized size of the script so we can set the registers
// accordingly.
//
// This variable is created to assert we have correct script size in the
// instructions.
let script_len = 16;

// Based on the defined script length, we set the appropriate data offset
let script_data_offset = VM_TX_MEMORY + Transaction::script_offset() + script_len;
let script_data_offset = script_data_offset as Immediate12;

let script = vec![
Opcode::ADDI(0x10, REG_ZERO, script_data_offset),
Opcode::ADDI(0x11, REG_ZERO, gas_limit as Immediate12),
Opcode::CALL(0x10, REG_ZERO, 0x10, 0x11),
Opcode::RET(REG_ONE),
]
.iter()
.copied()
.collect::<Vec<u8>>();

// Assert the offsets are set correctnly
let offset = VM_TX_MEMORY + Transaction::script_offset() + bytes::padded_len(script.as_slice());
assert_eq!(script_data_offset, offset as Immediate12);

let mut script_data = vec![];

// Routine to be called: Add word to state
let routine: Word = 0;

// Offset of the script data relative to the call data
let call_data_offset = script_data_offset as usize + ContractId::LEN + 2 * WORD_SIZE;
let call_data_offset = call_data_offset as Word;

// Key and value to be added
let key = Hasher::hash(b"some key");
let val: Word = 150;

// Script data containing the call arguments (contract, a, b) and (key, value)
script_data.extend(contract.as_ref());
script_data.extend(&routine.to_be_bytes());
script_data.extend(&call_data_offset.to_be_bytes());
script_data.extend(key.as_ref());
script_data.extend(&val.to_be_bytes());

let tx = Transaction::script(
gas_price,
gas_limit,
maturity,
script.clone(),
script_data,
vec![input.clone()],
vec![output],
vec![],
);

// Assert the initial state of `key` is empty
let state = client.as_ref().contract_state(&contract, &key);
assert_eq!(Bytes32::default(), state.into_owned());

let receipts = client.transition(vec![tx]).expect("Failed to transact");
let state = client.as_ref().contract_state(&contract, &key);

// Assert the state of `key` is mutated to `val`
assert_eq!(&val.to_be_bytes()[..], &state.as_ref()[..WORD_SIZE]);

// Expect the correct receipt
assert_eq!(receipts[1].ra().expect("Register value expected"), val);
assert_eq!(receipts[1].rb().expect("Register value expected"), 0);

// Create a script with revert instruction
let script = vec![
Opcode::ADDI(0x10, REG_ZERO, script_data_offset),
Opcode::ADDI(0x11, REG_ZERO, gas_limit as Immediate12),
Opcode::CALL(0x10, REG_ZERO, 0x10, 0x11),
Opcode::RVRT(REG_ONE),
]
.iter()
.copied()
.collect::<Vec<u8>>();

let mut script_data = vec![];

// Value to be added
let rev: Word = 250;

// Script data containing the call arguments (contract, a, b) and (key, value)
script_data.extend(contract.as_ref());
script_data.extend(&routine.to_be_bytes());
script_data.extend(&call_data_offset.to_be_bytes());
script_data.extend(key.as_ref());
script_data.extend(&rev.to_be_bytes());

let tx = Transaction::script(
gas_price,
gas_limit,
maturity,
script.clone(),
script_data,
vec![input.clone()],
vec![output],
vec![],
);

let receipts = client.transition(vec![tx]).expect("Failed to transact");
let state = client.as_ref().contract_state(&contract, &key);

// Assert the state of `key` is reverted to `val`
assert_eq!(&val.to_be_bytes()[..], &state.as_ref()[..WORD_SIZE]);

// Expect the correct receipt
assert_eq!(receipts[1].ra().expect("Register value expected"), val + rev);
assert_eq!(receipts[1].rb().expect("Register value expected"), val);

match receipts[3] {
Receipt::Revert { ra, .. } if ra == 1 => (),
_ => panic!("Expected revert receipt: {:?}", receipts[3]),
}
}