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

EVM: return just the bytecode hash #1015

Merged
merged 3 commits into from
Jan 12, 2023
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
28 changes: 6 additions & 22 deletions actors/evm/src/interpreter/instructions/ext.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,19 @@
use crate::interpreter::instructions::memory::copy_to_memory;
use crate::interpreter::{address::EthAddress, precompiles::Precompiles};
use crate::U256;
use crate::{BytecodeHash, U256};
use cid::Cid;
use fil_actors_runtime::deserialize_block;
use fil_actors_runtime::runtime::builtins::Type;
use fil_actors_runtime::ActorError;
use fvm_ipld_blockstore::Blockstore;
use fvm_shared::crypto::hash::SupportedHashes;
use fvm_shared::sys::SendFlags;
use fvm_shared::{address::Address, econ::TokenAmount};
use multihash::Multihash;
use num_traits::Zero;
use {
crate::interpreter::{ExecutionState, StatusCode, System},
fil_actors_runtime::runtime::Runtime,
};

/// Keccak256 hash of `[0xfe]`, "native bytecode"
pub const NATIVE_BYTECODE_HASH: [u8; 32] =
hex_literal::hex!("bcc90f2d6dada5b18e155c17a1c0a55920aae94f39857d39d0d8ed07ae8f228b");

/// Keccak256 hash of `[]`, empty bytecode
pub const EMPTY_EVM_HASH: [u8; 32] =
hex_literal::hex!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");

pub fn extcodesize(
_state: &mut ExecutionState,
system: &mut System<impl Runtime>,
Expand Down Expand Up @@ -52,34 +42,28 @@ pub fn extcodehash(
let addr = match get_contract_type(system.rt, addr) {
ContractType::EVM(a) => a,
// _Technically_ since we have native "bytecode" set as 0xfe this is valid, though we cant differentiate between different native actors.
ContractType::Native(_) => return Ok(NATIVE_BYTECODE_HASH.into()),
ContractType::Native(_) => return Ok(BytecodeHash::NATIVE_ACTOR.into()),
// Precompiles "exist" and therefore aren't empty (although spec-wise they can be either 0 or keccak("") ).
ContractType::Precompile => return Ok(EMPTY_EVM_HASH.into()),
ContractType::Precompile => return Ok(BytecodeHash::EMPTY.into()),
// NOTE: There may be accounts that in EVM would be considered "empty" (as defined in EIP-161) and give 0, but we will instead return keccak("").
// The FVM does not have chain state cleanup so contracts will never end up "empty" and be removed, they will either exist (in any state in the contract lifecycle)
// and return keccak(""), or not exist (where nothing has ever been deployed at that address) and return 0.
// TODO: With account abstraction, this may be something other than an empty hash!
ContractType::Account => return Ok(EMPTY_EVM_HASH.into()),
ContractType::Account => return Ok(BytecodeHash::EMPTY.into()),
// Not found
ContractType::NotFound => return Ok(U256::zero()),
};

// multihash { keccak256(bytecode) }
let bytecode_hash: Multihash = deserialize_block(system.send(
let bytecode_hash: BytecodeHash = deserialize_block(system.send(
&addr,
crate::Method::GetBytecodeHash as u64,
Default::default(),
TokenAmount::zero(),
None,
SendFlags::READ_ONLY,
)?)?;

let digest = bytecode_hash.digest();
debug_assert_eq!(SupportedHashes::Keccak256 as u64, bytecode_hash.code());

// Take the first 32 bytes of the Multihash
let digest_len = digest.len().min(32);
Ok(digest[..digest_len].into())
Ok(bytecode_hash.into())
}

pub fn extcodecopy(
Expand Down
15 changes: 7 additions & 8 deletions actors/evm/src/interpreter/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ use fvm_shared::{
sys::SendFlags,
MethodNum, IPLD_RAW,
};
use multihash::{Code, Multihash};
use multihash::Code;
use once_cell::unsync::OnceCell;

use crate::state::{State, Tombstone};
use crate::BytecodeHash;

use super::{address::EthAddress, Bytecode};

Expand Down Expand Up @@ -84,11 +85,11 @@ pub struct EvmBytecode {
/// CID of the contract
pub cid: Cid,
/// Keccak256 hash of the contract
pub evm_hash: Multihash,
pub evm_hash: BytecodeHash,
}

impl EvmBytecode {
fn new(cid: Cid, evm_hash: Multihash) -> Self {
fn new(cid: Cid, evm_hash: BytecodeHash) -> Self {
Self { cid, evm_hash }
}
}
Expand Down Expand Up @@ -318,11 +319,9 @@ impl<'r, RT: Runtime> System<'r, RT> {
));
}

let code_hash = multihash::Multihash::wrap(
SupportedHashes::Keccak256 as u64,
&self.rt.hash(SupportedHashes::Keccak256, bytecode),
)
.context_code(ExitCode::USR_ILLEGAL_STATE, "failed to hash bytecode with keccak")?;
let code_hash = self.rt.hash(SupportedHashes::Keccak256, bytecode)[..]
.try_into()
.context_code(ExitCode::USR_ASSERTION_FAILED, "expected a 32byte digest")?;

let cid = self
.rt
Expand Down
9 changes: 9 additions & 0 deletions actors/evm/src/interpreter/uints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
use serde::{Deserialize, Serialize};
use substrate_bn::arith;

use crate::BytecodeHash;

use {
fvm_shared::bigint::BigInt, fvm_shared::econ::TokenAmount, std::cmp::Ordering, std::fmt,
uint::construct_uint,
Expand Down Expand Up @@ -109,6 +111,13 @@ impl From<&U256> for TokenAmount {
}
}

impl From<BytecodeHash> for U256 {
fn from(bytecode: BytecodeHash) -> Self {
let bytes: [u8; 32] = bytecode.into();
Self::from(bytes)
}
}

impl Serialize for U256 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
Expand Down
11 changes: 4 additions & 7 deletions actors/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ use fil_actors_runtime::{actor_error, AsActorError, EAM_ACTOR_ADDR, INIT_ACTOR_A
use fvm_ipld_encoding::ipld_block::IpldBlock;
use fvm_ipld_encoding::{strict_bytes, BytesDe, BytesSer, DAG_CBOR};
use fvm_shared::address::Address;
use fvm_shared::crypto::hash::SupportedHashes;
use fvm_shared::error::ExitCode;
use interpreter::instructions::ext::EMPTY_EVM_HASH;
use interpreter::{address::EthAddress, system::load_bytecode};
use multihash::Multihash;

use crate::interpreter::output::Outcome;

Expand Down Expand Up @@ -240,14 +237,14 @@ impl EvmContractActor {
}
}

pub fn bytecode_hash(rt: &mut impl Runtime) -> Result<multihash::Multihash, ActorError> {
pub fn bytecode_hash(rt: &mut impl Runtime) -> Result<BytecodeHash, ActorError> {
// Any caller can fetch the bytecode hash of a contract; this is where EXTCODEHASH gets it's value for EVM contracts.
rt.validate_immediate_caller_accept_any()?;

// return value must be either keccak("") or keccak(bytecode)
let state: State = rt.state()?;
if is_dead(rt, &state) {
Ok(Multihash::wrap(SupportedHashes::Keccak256 as u64, &EMPTY_EVM_HASH).unwrap())
Ok(BytecodeHash::EMPTY)
} else {
Ok(state.bytecode_hash)
}
Expand Down Expand Up @@ -340,8 +337,8 @@ impl ActorCode for EvmContractActor {
Ok(IpldBlock::serialize_cbor(&ret)?)
}
Some(Method::GetBytecodeHash) => {
let multihash = Self::bytecode_hash(rt)?;
Ok(IpldBlock::serialize_cbor(&multihash)?)
let hash = Self::bytecode_hash(rt)?;
Ok(IpldBlock::serialize_cbor(&hash)?)
Stebalien marked this conversation as resolved.
Show resolved Hide resolved
}
Some(Method::GetStorageAt) => {
let value = Self::storage_at(
Expand Down
101 changes: 100 additions & 1 deletion actors/evm/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
use std::array::TryFromSliceError;

use fvm_shared::ActorID;

use {
cid::Cid,
fvm_ipld_encoding::strict_bytes,
fvm_ipld_encoding::tuple::*,
serde::{Deserialize, Serialize},
serde_tuple::{Deserialize_tuple, Serialize_tuple},
};

Expand All @@ -15,6 +19,71 @@ pub struct Tombstone {
pub nonce: u64,
}

/// A Keccak256 digest of EVM bytecode.
#[derive(Deserialize, Serialize, Clone, Copy, Eq, PartialEq)]
#[serde(transparent)]
pub struct BytecodeHash(#[serde(with = "strict_bytes")] [u8; 32]);

impl std::fmt::Debug for BytecodeHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("BytecodeHash").field(&format_args!("{}", self)).finish()
}
}

impl std::fmt::Display for BytecodeHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if f.alternate() {
write!(f, "0x")?;
}
for b in self.0 {
write!(f, "{b:02X}")?;
}
Ok(())
}
}

impl BytecodeHash {
Stebalien marked this conversation as resolved.
Show resolved Hide resolved
pub const ZERO: Self = Self([0; 32]);

/// Keccak256 hash of `[0xfe]`, "native bytecode"
pub const NATIVE_ACTOR: Self =
Self(hex_literal::hex!("bcc90f2d6dada5b18e155c17a1c0a55920aae94f39857d39d0d8ed07ae8f228b"));

/// Keccak256 hash of `[]`, empty bytecode
pub const EMPTY: Self =
Self(hex_literal::hex!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"));

pub fn as_slice(&self) -> &[u8] {
&self.0
}
}

impl From<[u8; 32]> for BytecodeHash {
fn from(digest: [u8; 32]) -> Self {
BytecodeHash(digest)
}
}

impl From<BytecodeHash> for [u8; 32] {
fn from(digest: BytecodeHash) -> Self {
digest.0
}
}

impl From<BytecodeHash> for Vec<u8> {
fn from(digest: BytecodeHash) -> Self {
digest.0.into()
}
}

impl TryFrom<&[u8]> for BytecodeHash {
type Error = TryFromSliceError;

fn try_from(value: &[u8]) -> Result<Self, TryFromSliceError> {
Ok(Self(value.try_into()?))
}
}

/// Data stored by an EVM contract.
/// This runs on the fvm-evm-runtime actor code cid.
#[derive(Debug, Serialize_tuple, Deserialize_tuple)]
Expand All @@ -24,7 +93,7 @@ pub struct State {
pub bytecode: Cid,

/// The EVM contract bytecode hash keccak256(bytecode)
pub bytecode_hash: multihash::Multihash,
pub bytecode_hash: BytecodeHash,

/// The EVM contract state dictionary.
/// All eth contract state is a map of U256 -> U256 values.
Expand Down Expand Up @@ -54,3 +123,33 @@ pub struct State {
/// See https://github.com/filecoin-project/ref-fvm/issues/1174 for some context.
pub tombstone: Option<Tombstone>,
}

#[cfg(test)]
mod test {
use fvm_ipld_encoding::{from_slice, to_vec, BytesDe};

use crate::BytecodeHash;
#[test]
fn test_bytecode_hash_serde() {
let encoded = to_vec(&BytecodeHash::EMPTY).unwrap();
let BytesDe(decoded) = from_slice(&encoded).unwrap();
assert_eq!(BytecodeHash::try_from(&decoded[..]).unwrap(), BytecodeHash::EMPTY);
}

#[test]
fn test_bytecode_hash_format() {
assert_eq!(
BytecodeHash::ZERO.to_string(),
"0000000000000000000000000000000000000000000000000000000000000000"
);
assert_eq!(
format!("{:#}", BytecodeHash::ZERO),
"0x0000000000000000000000000000000000000000000000000000000000000000"
);

assert_eq!(
format!("{:?}", BytecodeHash::ZERO),
"BytecodeHash(0000000000000000000000000000000000000000000000000000000000000000)"
);
}
}
Loading