Skip to content

Commit

Permalink
Add Opcode::BAL implementation (#53)
Browse files Browse the repository at this point in the history
The opcode implementation brings the ability to inspect the balance of a
given color+contract tuple.

This allows the test case `mint_burn` to be moved to the integrated
scope since before this opcode, there was no way to fetch the balance of
the tuple without inspecting the internals of the interpreter.

Follows FuelLabs/fuel-specs#226
  • Loading branch information
vlopes11 authored Dec 4, 2021
1 parent 0b62361 commit 0aa6786
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 157 deletions.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ name = "test-blockchain"
path = "tests/blockchain.rs"
required-features = ["random"]

[[test]]
name = "test-contract"
path = "tests/contract.rs"
required-features = ["random"]

[[test]]
name = "test-debug"
path = "tests/debug.rs"
Expand Down
184 changes: 27 additions & 157 deletions src/interpreter/contract.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use super::Interpreter;
use crate::consts::*;
use crate::contract::Contract;
use crate::error::InterpreterError;
use crate::storage::InterpreterStorage;

use fuel_types::{Color, ContractId, Word};
use fuel_asm::{RegisterId, Word};
use fuel_types::{Color, ContractId};

use std::borrow::Cow;

Expand All @@ -18,170 +20,38 @@ where
.ok_or(InterpreterError::ContractNotFound)?
}

pub(crate) fn check_contract_exists(&self, contract: &ContractId) -> Result<bool, InterpreterError> {
self.storage.storage_contract_exists(contract)
}

pub(crate) fn balance(&self, contract: &ContractId, color: &Color) -> Result<Word, InterpreterError> {
Ok(self
.storage
.merkle_contract_color_balance(contract, color)?
.unwrap_or_default())
}
}

#[cfg(all(test, feature = "random"))]
mod tests {
use crate::consts::*;
use crate::prelude::*;
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};

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

let mut balance = 1000;

let mut vm = Interpreter::in_memory();

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

let salt: Salt = rng.gen();
let program: Witness = [
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),
Opcode::JNEI(0x10, REG_ZERO, 7),
Opcode::MINT(0x11),
Opcode::JI(8),
Opcode::BURN(0x11),
Opcode::RET(REG_ONE),
]
.iter()
.copied()
.collect::<Vec<u8>>()
.into();
pub(crate) fn contract_balance(&mut self, ra: RegisterId, b: Word, c: Word) -> Result<(), InterpreterError> {
if b > VM_MAX_RAM - Color::LEN as Word || c > VM_MAX_RAM - ContractId::LEN as Word {
return Err(InterpreterError::MemoryOverflow);
}

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

let color = Color::from(*contract);
let output = Output::contract_created(contract);
let (b, c) = (b as usize, c as usize);

let bytecode_witness = 0;
let tx = Transaction::create(
gas_price,
gas_limit,
maturity,
bytecode_witness,
salt,
vec![],
vec![],
vec![output],
vec![program],
);
// Safety: memory bounds checked
let color = unsafe { Color::as_ref_unchecked(&self.memory[b..b + Color::LEN]) };
let contract = unsafe { ContractId::as_ref_unchecked(&self.memory[c..c + ContractId::LEN]) };

vm.transact(tx).expect("Failed to transact");
if !self.tx.input_contracts().any(|input| contract == input) {
return Err(InterpreterError::ContractNotInTxInputs);
}

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

let mut script_ops = vec![
Opcode::ADDI(0x10, REG_ZERO, 0),
Opcode::ADDI(0x11, REG_ZERO, gas_limit as Immediate12),
Opcode::CALL(0x10, REG_ZERO, 0x10, 0x11),
Opcode::RET(REG_ONE),
];
self.registers[ra] = balance;

let script: Vec<u8> = script_ops.iter().copied().collect();
let tx = Transaction::script(
gas_price,
gas_limit,
maturity,
script,
vec![],
vec![input.clone()],
vec![output],
vec![],
);

let script_data_offset = VM_TX_MEMORY + tx.script_data_offset().unwrap();
script_ops[0] = Opcode::ADDI(0x10, REG_ZERO, script_data_offset as Immediate12);

let script: Vec<u8> = script_ops.iter().copied().collect();
let script_data = Call::new(contract, 0, balance).to_bytes();
let tx = Transaction::script(
gas_price,
gas_limit,
maturity,
script,
script_data,
vec![input.clone()],
vec![output],
vec![],
);

assert_eq!(0, vm.balance(&contract, &color).unwrap());
vm.transact(tx).expect("Failed to transact");
assert_eq!(balance as Word, vm.balance(&contract, &color).unwrap());

// Try to burn more than the available balance
let script: Vec<u8> = script_ops.iter().copied().collect();
let script_data = Call::new(contract, 1, balance + 1).to_bytes();
let tx = Transaction::script(
gas_price,
gas_limit,
maturity,
script,
script_data,
vec![input.clone()],
vec![output],
vec![],
);

assert!(vm.transact(tx).is_err());
assert_eq!(balance as Word, vm.balance(&contract, &color).unwrap());

// Burn some of the balance
let burn = 100;

let script: Vec<u8> = script_ops.iter().copied().collect();
let script_data = Call::new(contract, 1, burn).to_bytes();
let tx = Transaction::script(
gas_price,
gas_limit,
maturity,
script,
script_data,
vec![input.clone()],
vec![output],
vec![],
);

vm.transact(tx).expect("Failed to transact");
balance -= burn;
assert_eq!(balance as Word, vm.balance(&contract, &color).unwrap());
self.inc_pc()
}

// Burn the remainder balance
let script: Vec<u8> = script_ops.iter().copied().collect();
let script_data = Call::new(contract, 1, balance).to_bytes();
let tx = Transaction::script(
gas_price,
gas_limit,
maturity,
script,
script_data,
vec![input.clone()],
vec![output],
vec![],
);
pub(crate) fn check_contract_exists(&self, contract: &ContractId) -> Result<bool, InterpreterError> {
self.storage.storage_contract_exists(contract)
}

vm.transact(tx).expect("Failed to transact");
assert_eq!(0, vm.balance(&contract, &color).unwrap());
pub(crate) fn balance(&self, contract: &ContractId, color: &Color) -> Result<Word, InterpreterError> {
Ok(self
.storage
.merkle_contract_color_balance(contract, color)?
.unwrap_or_default())
}
}
5 changes: 5 additions & 0 deletions src/interpreter/executors/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,11 @@ where
self.store_word(a, b, imm)?;
}

OpcodeRepr::BAL => {
self.gas_charge(GAS_BAL)?;
self.contract_balance(ra, b, c)?;
}

OpcodeRepr::BHEI => {
self.gas_charge(GAS_BHEI)?;
self.alu_set(ra, self.block_height() as Word)?;
Expand Down
1 change: 1 addition & 0 deletions src/interpreter/gas/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub const GAS_LW: Word = Interpreter::<()>::gas_cost_const(OpcodeRepr::ADD);
pub const GAS_MEQ: Word = Interpreter::<()>::gas_cost_const(OpcodeRepr::ADD);
pub const GAS_SB: Word = Interpreter::<()>::gas_cost_const(OpcodeRepr::ADD);
pub const GAS_SW: Word = Interpreter::<()>::gas_cost_const(OpcodeRepr::ADD);
pub const GAS_BAL: Word = Interpreter::<()>::gas_cost_const(OpcodeRepr::ADD);
pub const GAS_BHEI: Word = Interpreter::<()>::gas_cost_const(OpcodeRepr::ADD);
pub const GAS_BHSH: Word = Interpreter::<()>::gas_cost_const(OpcodeRepr::ADD);
pub const GAS_BURN: Word = Interpreter::<()>::gas_cost_const(OpcodeRepr::ADD);
Expand Down
Loading

0 comments on commit 0aa6786

Please sign in to comment.