Skip to content

Commit

Permalink
Merge pull request #903 from allbridge-io/eof/develop
Browse files Browse the repository at this point in the history
EOF smart contracts support
  • Loading branch information
afalaleev authored Dec 19, 2023
2 parents 6c7b75e + a103896 commit 7bd730c
Show file tree
Hide file tree
Showing 24 changed files with 2,657 additions and 251 deletions.
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,14 @@ RUN apt-get update && \
DEBIAN_FRONTEND=nontineractive apt-get -y install xxd && \
rm -rf /var/lib/apt/lists/* /var/lib/apt/cache/*
COPY tests/contracts/*.sol /opt/
COPY tests/eof-contracts/*.binary /opt/eof-contracts/
COPY solidity/*.sol /opt/
#COPY evm_loader/tests/test_solidity_precompiles.json /opt/
COPY --from=solc /usr/bin/solc /usr/bin/solc
WORKDIR /opt/
RUN solc --optimize --optimize-runs 200 --output-dir . --bin *.sol && \
for file in $(ls *.bin); do xxd -r -p $file >${file}ary; done && \
ls -l
ls -l

# Define solana-image that contains utility
FROM ${SOLANA_IMAGE} AS solana
Expand Down
96 changes: 96 additions & 0 deletions evm_loader/program/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ pub enum Error {
#[error("EVM encountered invalid opcode, contract = {0}, opcode = {1:X}")]
InvalidOpcode(Address, u8),

#[error("EVM encountered deprecated opcode, contract = {0}, opcode = {1:X}")]
DeprecatedOpcode(Address, u8),

#[error("EVM encountered unknown opcode, contract = {0}, opcode = {1:X}")]
UnknownOpcode(Address, u8),

Expand Down Expand Up @@ -145,6 +148,99 @@ pub enum Error {

#[error("Holder Account - invalid transaction hash {}, expected = {}", hex::encode(.0), hex::encode(.1))]
HolderInvalidHash([u8; 32], [u8; 32]),

#[error("Validation: undefined instruction: op {0:X}, pos {1}")]
ValidationUndefinedInstruction(u8, usize),

#[error("Validation: truncated immediate: op {0:X}, pos {1}")]
ValidationTruncatedImmediate(u8, usize),

#[error("Validation: invalid section argument: arg {0}, last {1}, pos {2}")]
ValidationInvalidSectionArgument(u16, usize, usize),

#[error("Validation: invalid jump destination: offset {0}, dest {1}, pos {2}")]
ValidationInvalidJumpDest(i16, usize, usize),

#[error("Validation: conflicting stack height: have {0}, want {1}")]
ValidationConflictingStack(usize, usize),

#[error("Validation: invalid number of branches in jump table: must not be 0, pos {0}")]
ValidationInvalidBranchCount(usize),

#[error("Validation: invalid number of outputs: have {0}, want {1}, at pos {2}")]
ValidationInvalidOutputs(u8, usize, usize),

#[error("Validation: invalid max stack height: {0}: have {1}, want {2}")]
ValidationInvalidMaxStackHeight(usize, usize, u16),

#[error("Validation: invalid code termination: end with {0}, pos {1}")]
ValidationInvalidCodeTermination(u8, usize),

#[error("Validation: unreachable code")]
ValidationUnreachableCode,

#[error("EVM Empty stack")]
EmptyStack,

#[error("EVM container not found")]
ContainerNotFound,

#[error("EVM function metadata not found at index {0}")]
FunctionMetadataNotFound(usize),

#[error("Opcode not supported, opcode {0:X}")]
UnsupportedOpcode(u8),

#[error("Unexpected end of file")]
UnexpectedEndOfFile,

#[error("Invalid magic: want 0xEF00")]
InvalidMagic,

#[error("Invalid version: have {0}, want 1")]
InvalidVersion(u8),

#[error("Missing section header: found section kind {:?} instead of {:?}", .0, .1)]
MissingSectionHeader(u8, u8),

#[error("Invalid type section size: type section size must be divisible by 4, have {0}")]
InvalidTypeSizeMustBeDivisibleBy4(u16),

#[error("Invalid type section size: type section must not exceed 4*1024, have {0}")]
InvalidTypeSizeExceed(u16),

#[error("Invalid code header")]
InvalidCodeHeader,

#[error("Invalid code size: mismatch of code sections cound and type signatures, types {0}, code {1}")]
MismatchCodeSize(usize, usize),

#[error("Invalid code size for section {0}: size must not be 0")]
InvalidCodeSize(usize),

#[error("Missing header terminator: have ({:#x?})", .0)]
MissingTerminator(u8),

#[error("Invalid type content, too many inputs for section {0}: have {1}")]
TooManyInputs(u8, u8),

#[error("Invalid type content, too many outputs for section {0}: have {1}")]
TooManyOutputs(u8, u8),

#[error("Invalid type content, max stack height exceeds limit for section {0}: have {1}")]
TooLargeMaxStackHeight(u8, u8),

#[error("Invalid section 0 type, input and output should be zero: have {0}, {1}")]
InvalidSection0Type(u8, u8),

#[error("invalid code: EOF contract must not deploy legacy code")]
EOFLegacyCode,

#[error("Invalid container size: have {0}, want {1}")]
InvalidContainerSize(usize, usize),

#[error("Unknown section header ({:#x?})", .0)]
UnknownSectionHeader(u8),
}

pub type Result<T> = std::result::Result<T, Error>;
Expand Down
151 changes: 151 additions & 0 deletions evm_loader/program/src/evm/analysis.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#![allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]

use crate::evm::Buffer;

#[allow(clippy::wildcard_imports)]
use crate::evm::opcode_table::opcode::*;

pub struct Bitvec(Vec<u8>);

const BITS_MASK: [u16; 8] = [0, 1, 0b11, 0b111, 0b1111, 0b1_1111, 0b11_1111, 0b111_1111];

impl Bitvec {
pub fn new(capacity: usize) -> Self {
Bitvec(vec![0; capacity])
}

pub fn _set1(&mut self, pos: usize) {
self.0[pos / 8] |= 1 << (pos % 8);
}

pub fn set_n(&mut self, flag: u16, pos: usize) {
let a = flag << (pos % 8);
self.0[pos / 8] |= a as u8;
let b = (a >> 8) as u8;
if b != 0 {
self.0[pos / 8 + 1] = b;
}
}

pub fn set8(&mut self, pos: usize) {
let a = (0xFF << (pos % 8)) as u8;
self.0[pos / 8] |= a;
self.0[pos / 8 + 1] = !a;
}

pub fn set16(&mut self, pos: usize) {
let a = (0xFF << (pos % 8)) as u8;
self.0[pos / 8] |= a;
self.0[pos / 8 + 1] = 0xFF;
self.0[pos / 8 + 2] = !a;
}

pub fn is_code_segment(&self, pos: usize) -> bool {
((self.0[pos / 8] >> (pos % 8)) & 1) == 0
}

#[allow(dead_code)]
pub fn to_vec(&self) -> &Vec<u8> {
&self.0
}

// eofCodeBitmap collects data locations in code.
pub fn eof_code_bitmap(code: &Buffer) -> Bitvec {
// The bitmap is 4 bytes longer than necessary, in case the code
// ends with a PUSH32, the algorithm will push zeroes onto the
// bitvector outside the bounds of the actual code.
let mut bits = Bitvec::new(code.len() / 8 + 1 + 4);
bits.eof_code_bitmap_internal(code);
bits
}

// eofCodeBitmapInternal is the internal implementation of codeBitmap for EOF
// code validation.
pub fn eof_code_bitmap_internal(&mut self, code: &Buffer) {
let mut pc: usize = 0;
while pc < code.len() {
let op = code.get_or_default(pc);
let mut numbits: u8;
pc += 1;

match op {
PUSH1..=PUSH32 => {
numbits = op - PUSH1 + 1;
}

RJUMP | RJUMPI | CALLF => {
numbits = 2;
}

RJUMPV => {
// RJUMPV is unique as it has a variable sized operand.
// The total size is determined by the count byte which
// immediate proceeds RJUMPV. Truncation will be caught
// in other validation steps -- for now, just return a
// valid bitmap for as much of the code as is
// available.
let end = code.len();
if pc >= end {
// Count missing, no more bits to mark.
return;
}
numbits = code.get_or_default(pc) * 2 + 1;
if pc + numbits as usize > end {
// Jump table is truncated, mark as many bits
// as possible.
numbits = (end - pc) as u8;
}
}

_ => continue,
}

if numbits >= 8 {
while numbits >= 16 {
self.set16(pc);
numbits -= 16;
pc += 16;
}
while numbits >= 8 {
self.set8(pc);
numbits -= 8;
pc += 8;
}
}

if (1..=7).contains(&numbits) {
self.set_n(BITS_MASK[numbits as usize], pc);
pc += numbits as usize;
}
}
}
}

#[allow(clippy::enum_glob_use)]
#[cfg(test)]
mod tests {
use crate::evm::analysis::Bitvec;
use crate::evm::opcode_table::opcode::*;
use crate::evm::Buffer;

#[test]
fn eof_code_bitmap_test1() {
let code = Buffer::from_slice(&[RJUMP, 0x01, 0x01, 0x01]);
let bitvec = Bitvec::eof_code_bitmap(&code);
assert_eq!(bitvec.to_vec()[0], 0b0000_0110);
}

#[test]
fn eof_code_bitmap_test2() {
let code = Buffer::from_slice(&[RJUMPI, RJUMP, RJUMP, RJUMPI]);
let bitvec = Bitvec::eof_code_bitmap(&code);
assert_eq!(bitvec.to_vec()[0], 0b0011_0110);
}

#[test]
fn eof_code_bitmap_test3() {
let code = Buffer::from_slice(&[RJUMPV, 0x02, RJUMP, 0x00, RJUMPI, 0x00]);
let bitvec = Bitvec::eof_code_bitmap(&code);
assert_eq!(bitvec.to_vec()[0], 0b0011_1110);
}
}
32 changes: 32 additions & 0 deletions evm_loader/program/src/evm/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,38 @@ impl Buffer {
0
}
}

#[inline]
#[must_use]
pub fn get_unchecked_at(&self, index: usize) -> u8 {
unsafe { self.ptr.add(index).read() }
}

#[inline]
#[must_use]
pub fn get_u16_or_default(&self, index: usize) -> u16 {
if self.len() < index + 2 {
return u16::default();
};

u16::from_be_bytes(*arrayref::array_ref![*self, index, 2])
}

#[inline]
#[must_use]
pub fn get_i16_or_default(&self, index: usize) -> i16 {
if self.len() < index + 2 {
return i16::default();
};

i16::from_be_bytes(*arrayref::array_ref![*self, index, 2])
}
}

impl PartialEq for Buffer {
fn eq(&self, other: &Self) -> bool {
self.deref().eq(&**other)
}
}

impl Drop for Buffer {
Expand Down
Loading

0 comments on commit 7bd730c

Please sign in to comment.