Skip to content

Commit

Permalink
feat(zink): introduce contract constructor (#171)
Browse files Browse the repository at this point in the history
* feat(zink): introduce proc-macro constructor

* chore(compiler): clean the parameters of compiler

* feat(compiler): switch from runtime bytecode

* feat(codegen): compile constructor

* feat(examples): introduce tests for example storage

* feat(codegen): relocate return instructions in constructor
  • Loading branch information
clearloop authored Nov 11, 2023
1 parent ffff362 commit 28b99a2
Show file tree
Hide file tree
Showing 18 changed files with 407 additions and 175 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ jobs:
uses: dtolnay/rust-toolchain@nightly

- name: Release packages to crates.io
run: cargo run --release --bin conta publish
run: cargo run --release --package conta publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
117 changes: 117 additions & 0 deletions codegen/src/constructor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//! Contract constructor.
use crate::{Buffer, CodeGen, Function, JumpTable, MacroAssembler, Result, ToLSBytes};

/// Contract constructor.
///
/// # Bytecode
/// - `CREATE` instruction
/// - `INIT_CODE`
/// - `INIT_LOGIC`
/// - `RETURN RUNTIME_BYTECODE`
/// - `RUNTIME_BYTECODE`
pub struct Constructor {
/// Code buffer.
pub masm: MacroAssembler,

/// Code generator.
pub init_code: Buffer,

/// Runtime bytecode.
pub runtime_bytecode: Buffer,
}

impl Constructor {
/// Create a new constructor.
pub fn new(constructor: Option<Function<'_>>, runtime_bytecode: Buffer) -> Result<Self> {
let mut init_code = Buffer::new();
if let Some(constructor) = constructor {
let codegen = CodeGen::new(
constructor.sig()?,
Default::default(),
Default::default(),
// No `return` instruction in the generated code.
false,
)?;

let mut jump_table = JumpTable::default();
init_code = codegen.finish(&mut jump_table, 0)?;
jump_table.relocate(&mut init_code)?;
};

Ok(Self {
masm: MacroAssembler::default(),
init_code,
runtime_bytecode,
})
}

fn return_instr_length(init_code_length: usize, runtime_bytecode_length: usize) -> usize {
let mut expected_length =
runtime_bytecode_length.to_ls_bytes().len() + init_code_length.to_ls_bytes().len() + 3;

if init_code_length < 0xff && init_code_length + expected_length > 0xff {
expected_length += 1;
}

expected_length
}

/// Concat the constructor code.
///
/// Here we override the memory totally with
/// the runtime bytecode.
pub fn finish(&mut self) -> Result<Buffer> {
let init_code_length = self.init_code.len();
let runtime_bytecode_length = self.runtime_bytecode.len();
let return_instr_length =
Self::return_instr_length(init_code_length, runtime_bytecode_length);

// Copy init code and runtime bytecode to memory from offset 0.
//
// 1. code size ( init_code + instr_return + runtime_bytecode )
// 2. byte offset of code which is fixed to N.
// 3. destination offset which is fixed to 0.
{
self.masm.push(
&(init_code_length + return_instr_length + runtime_bytecode_length).to_ls_bytes(),
)?;
// # SAFETY
//
// The length of the most significiant bytes of
// the bytecode offset is fixed to 1.
self.masm
.push(&((self.masm.pc_offset() as usize + 9).to_ls_bytes()))?;
self.masm._push0()?;
self.masm._codecopy()?;
}

// Process instruction `CREATE`
{
self.masm._push0()?;
self.masm._push0()?;
self.masm._push0()?;
self.masm._calldataload()?;
self.masm._create()?;
}

self.masm.buffer_mut().extend_from_slice(&self.init_code);

// Process `RETURN`.
//
// 1. size of the runtime bytecode
// 2. offset of the runtime bytecode in memory
{
self.masm.push(&runtime_bytecode_length.to_ls_bytes())?;
self.masm
.push(&(init_code_length + return_instr_length).to_ls_bytes())?;
self.masm.asm._return()?;
}

self.masm
.buffer_mut()
.extend_from_slice(&self.runtime_bytecode);

Ok(self.masm.buffer().into())
}
}
95 changes: 3 additions & 92 deletions codegen/src/dispatcher.rs
Original file line number Diff line number Diff line change
@@ -1,98 +1,12 @@
//! Code generator for EVM dispatcher.
use crate::{
code::ExtFunc, DataSet, Error, Exports, Imports, JumpTable, MacroAssembler, Result, ToLSBytes,
};
use std::{
collections::BTreeMap,
ops::{Deref, DerefMut},
};
use wasmparser::{
FuncType, FuncValidator, FunctionBody, Operator, ValidatorResources, WasmModuleResources,
code::ExtFunc, DataSet, Error, Exports, Function, Functions, Imports, JumpTable,
MacroAssembler, Result, ToLSBytes,
};
use wasmparser::{FuncType, Operator};
use zabi::Abi;

/// Function with validator.
pub struct Function<'f> {
/// Function validator.
pub validator: FuncValidator<ValidatorResources>,
/// Function body.
pub body: FunctionBody<'f>,
}

impl Function<'_> {
/// Get function index.
pub fn index(&self) -> u32 {
self.validator.index()
}

/// Get the function signature.
pub fn sig(&self) -> Result<FuncType> {
let func_index = self.validator.index();
let sig = self
.validator
.resources()
.type_of_function(func_index)
.ok_or(Error::InvalidFunctionSignature)?
.clone();

Ok(sig)
}
}

/// Functions with indexes.
#[derive(Default)]
pub struct Functions<'f>(BTreeMap<u32, Function<'f>>);

impl<'f> Deref for Functions<'f> {
type Target = BTreeMap<u32, Function<'f>>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl<'f> DerefMut for Functions<'f> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

impl<'f> Functions<'f> {
/// Add function to the list.
pub fn add(
&mut self,
validator: FuncValidator<ValidatorResources>,
function: FunctionBody<'f>,
) {
self.0.insert(
validator.index(),
Function {
validator,
body: function,
},
);
}

/// Remove all selector functions
pub fn drain_selectors(&mut self, exports: &Exports) -> Self {
let mut functions = Self::default();

for index in exports.selectors() {
if let Some(function) = self.0.remove(&index) {
functions.0.insert(index, function);
}
}

functions
}

/// Get all functions
pub fn into_funcs(self) -> Vec<Function<'f>> {
self.0.into_values().collect()
}
}

/// Code generator for EVM dispatcher.
pub struct Dispatcher<'d> {
/// Code buffer
Expand Down Expand Up @@ -155,12 +69,9 @@ impl<'d> Dispatcher<'d> {
fn load_abi(&mut self, selector: &Function<'_>) -> Result<Abi> {
let mut reader = selector.body.get_operators_reader()?;

// Get data offset.
let Operator::I32Const { value: offset } = reader.read()? else {
return Err(Error::InvalidSelector);
};

// Get data length.
let Operator::I32Const { value: length } = reader.read()? else {
return Err(Error::InvalidSelector);
};
Expand Down
6 changes: 3 additions & 3 deletions codegen/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ use std::{
};

/// WASM export.
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct Export {
/// Name of the export.
pub name: String,
}

/// WASM exports
#[derive(Debug, Default)]
#[derive(Debug, Default, Clone)]
pub struct Exports(BTreeMap<u32, Export>);

impl Deref for Exports {
Expand Down Expand Up @@ -50,7 +50,7 @@ impl Exports {
pub fn selectors(&self) -> Vec<u32> {
let mut selectors = Vec::new();

for (index, export) in self.0.iter() {
for (index, export) in self.iter() {
if export.name.ends_with("_selector") {
selectors.push(*index);
}
Expand Down
99 changes: 99 additions & 0 deletions codegen/src/func.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//! Function handler
use crate::{Error, Exports, Result};
use std::{
collections::BTreeMap,
ops::{Deref, DerefMut},
};
use wasmparser::{FuncType, FuncValidator, FunctionBody, ValidatorResources, WasmModuleResources};

/// Function with validator.
pub struct Function<'f> {
/// Function validator.
pub validator: FuncValidator<ValidatorResources>,
/// Function body.
pub body: FunctionBody<'f>,
}

impl Function<'_> {
/// Get function index.
pub fn index(&self) -> u32 {
self.validator.index()
}

/// Get the function signature.
pub fn sig(&self) -> Result<FuncType> {
let func_index = self.validator.index();
let sig = self
.validator
.resources()
.type_of_function(func_index)
.ok_or(Error::InvalidFunctionSignature)?
.clone();

Ok(sig)
}
}

/// Functions with indexes.
#[derive(Default)]
pub struct Functions<'f>(BTreeMap<u32, Function<'f>>);

impl<'f> Deref for Functions<'f> {
type Target = BTreeMap<u32, Function<'f>>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl<'f> DerefMut for Functions<'f> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

impl<'f> Functions<'f> {
/// Add function to the list.
pub fn add(
&mut self,
validator: FuncValidator<ValidatorResources>,
function: FunctionBody<'f>,
) {
self.0.insert(
validator.index(),
Function {
validator,
body: function,
},
);
}

/// Remove constructor function
pub fn remove_constructor(&mut self, exports: &Exports) -> Option<Function<'f>> {
for (index, export) in exports.iter() {
if &export.name == "constructor" {
return self.remove(index);
}
}

None
}

/// Remove all selector functions
pub fn drain_selectors(&mut self, exports: &Exports) -> Self {
let mut functions = Self::default();

for index in exports.selectors() {
if let Some(function) = self.0.remove(&index) {
functions.0.insert(index, function);
}
}

functions
}

/// Get all functions
pub fn into_funcs(self) -> Vec<Function<'f>> {
self.0.into_values().collect()
}
}
6 changes: 5 additions & 1 deletion codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ pub use crate::{
asm::Assembler,
code::Code,
codegen::CodeGen,
constructor::Constructor,
control::{ControlStack, ControlStackFrame, ControlStackFrameType},
data::DataSet,
dispatcher::{Dispatcher, Function, Functions},
dispatcher::Dispatcher,
export::Exports,
func::{Function, Functions},
import::{Func, Imports},
jump::JumpTable,
local::{LocalSlot, Locals},
Expand All @@ -24,10 +26,12 @@ mod asm;
mod backtrace;
mod code;
mod codegen;
mod constructor;
mod control;
mod data;
mod dispatcher;
mod export;
mod func;
mod import;
mod jump;
mod local;
Expand Down
Loading

0 comments on commit 28b99a2

Please sign in to comment.