From 1430d4bd200ac43c044f76026e6a43d1c362dd65 Mon Sep 17 00:00:00 2001 From: Michael Birch Date: Tue, 3 Aug 2021 15:45:40 +0000 Subject: [PATCH 1/3] Define blockhash using block_height, chain_id, and account_id --- src/engine.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index fa2f2e7b3..5bd5d53d6 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -20,6 +20,10 @@ use crate::state::AuroraStackState; use crate::storage::{address_to_key, bytes_to_key, storage_to_key, KeyPrefix, KeyPrefixU8}; use crate::types::{u256_to_arr, AccountId, Wei, ERC20_MINT_SELECTOR}; +/// Used as the first byte in the concatenation of data used to compute the blockhash. +/// Could be useful in the future as a version byte, or to distinguish different types of blocks. +const BLOCK_HASH_PREFIX: u8 = 0; + #[cfg(not(feature = "contract"))] pub fn current_address() -> Address { crate::types::near_account_to_evm_address("engine".as_bytes()) @@ -191,7 +195,7 @@ impl AsRef<[u8]> for EngineStateError { /// Should not contain anything large or enumerable. #[derive(BorshSerialize, BorshDeserialize, Default)] pub struct EngineState { - /// Chain id, according to the EIP-115 / ethereum-lists spec. + /// Chain id, according to the EIP-155 / ethereum-lists spec. pub chain_id: [u8; 32], /// Account which can upgrade this contract. /// Use empty to disable updatability. @@ -245,6 +249,36 @@ impl Engine { ); } + /// There is one Aurora block per NEAR block height (note: when heights in NEAR are skipped + /// they are interpreted as empty blocks on Aurora). The blockhash is derived from the height + /// according to + /// ```text + /// block_hash = sha256(concat( + /// BLOCK_HASH_PREFIX, + /// block_height as u64, + /// chain_id, + /// engine_account_id, + /// )) + /// ``` + pub fn compute_block_hash(&self, block_height: u64, account_id: &[u8]) -> H256 { + let mut data = Vec::with_capacity(1 + 8 + 32 + account_id.len()); + data.push(BLOCK_HASH_PREFIX); + data.extend_from_slice(&block_height.to_be_bytes()); + data.extend_from_slice(&self.state.chain_id); + data.extend_from_slice(account_id); + + #[cfg(not(feature = "contract"))] + { + use sha2::Digest; + + let output = sha2::Sha256::digest(&data); + H256(output.into()) + } + + #[cfg(feature = "contract")] + sdk::sha256(&data) + } + /// Fails if state is not found. pub fn get_state() -> Result { match sdk::read_storage(&bytes_to_key(KeyPrefix::Config, STATE_KEY)) { @@ -705,7 +739,15 @@ impl evm::backend::Backend for Engine { fn block_hash(&self, number: U256) -> H256 { let idx = U256::from(sdk::block_index()); if idx.saturating_sub(U256::from(256)) <= number && number < idx { - H256::from([255u8; 32]) + // since `idx` comes from `u64` it is always safe to downcast `number` from `U256` + #[cfg(feature = "contract")] + { + let account_id = sdk::current_account_id(); + self.compute_block_hash(number.low_u64(), &account_id) + } + + #[cfg(not(feature = "contract"))] + self.compute_block_hash(number.low_u64(), b"aurora") } else { H256::zero() } From 8ec6f4a862a50f01ed6f58b1522bfd5cf466089d Mon Sep 17 00:00:00 2001 From: Michael Birch Date: Tue, 3 Aug 2021 17:13:11 +0000 Subject: [PATCH 2/3] Tests for blockhash --- .gitignore | 2 +- src/engine.rs | 8 ++--- src/test_utils/solidity.rs | 39 ++++++++++++++++++++++- src/test_utils/standard_precompiles.rs | 32 ++----------------- src/tests/res/blockhash.sol | 13 ++++++++ src/tests/sanity.rs | 44 ++++++++++++++++++++++++++ 6 files changed, 102 insertions(+), 36 deletions(-) create mode 100644 src/tests/res/blockhash.sol diff --git a/.gitignore b/.gitignore index eb342215e..f0b0d9eb7 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,7 @@ artifacts/ cache/ node_modules/ -res/ +etc/eth-contracts/res/ # Other etc/state-migration-test/target/ diff --git a/src/engine.rs b/src/engine.rs index 5bd5d53d6..f75d74190 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -260,11 +260,11 @@ impl Engine { /// engine_account_id, /// )) /// ``` - pub fn compute_block_hash(&self, block_height: u64, account_id: &[u8]) -> H256 { + pub fn compute_block_hash(chain_id: [u8; 32], block_height: u64, account_id: &[u8]) -> H256 { let mut data = Vec::with_capacity(1 + 8 + 32 + account_id.len()); data.push(BLOCK_HASH_PREFIX); data.extend_from_slice(&block_height.to_be_bytes()); - data.extend_from_slice(&self.state.chain_id); + data.extend_from_slice(&chain_id); data.extend_from_slice(account_id); #[cfg(not(feature = "contract"))] @@ -743,11 +743,11 @@ impl evm::backend::Backend for Engine { #[cfg(feature = "contract")] { let account_id = sdk::current_account_id(); - self.compute_block_hash(number.low_u64(), &account_id) + Self::compute_block_hash(self.state.chain_id, number.low_u64(), &account_id) } #[cfg(not(feature = "contract"))] - self.compute_block_hash(number.low_u64(), b"aurora") + Self::compute_block_hash(self.state.chain_id, number.low_u64(), b"aurora") } else { H256::zero() } diff --git a/src/test_utils/solidity.rs b/src/test_utils/solidity.rs index 37f4c7a7b..6a9a2bc69 100644 --- a/src/test_utils/solidity.rs +++ b/src/test_utils/solidity.rs @@ -1,4 +1,5 @@ -use crate::prelude::Address; +use crate::prelude::{Address, U256}; +use crate::transaction::LegacyEthTransaction; use near_sdk::serde_json; use serde::Deserialize; use std::fs; @@ -73,6 +74,42 @@ impl ContractConstructor { address, } } + + pub fn deploy_without_args(&self, nonce: U256) -> LegacyEthTransaction { + let data = self + .abi + .constructor() + .unwrap() + .encode_input(self.code.clone(), &[]) + .unwrap(); + LegacyEthTransaction { + nonce, + gas_price: Default::default(), + gas: u64::MAX.into(), + to: None, + value: Default::default(), + data, + } + } +} + +impl DeployedContract { + pub fn call_method_without_args(&self, method_name: &str, nonce: U256) -> LegacyEthTransaction { + let data = self + .abi + .function(method_name) + .unwrap() + .encode_input(&[]) + .unwrap(); + LegacyEthTransaction { + nonce, + gas_price: Default::default(), + gas: u64::MAX.into(), + to: Some(self.address), + value: Default::default(), + data, + } + } } /// Compiles a solidity contract. `source_path` gives the directory containing all solidity diff --git a/src/test_utils/standard_precompiles.rs b/src/test_utils/standard_precompiles.rs index 362d285d5..56db06814 100644 --- a/src/test_utils/standard_precompiles.rs +++ b/src/test_utils/standard_precompiles.rs @@ -24,21 +24,7 @@ impl PrecompilesConstructor { } pub fn deploy(&self, nonce: U256) -> LegacyEthTransaction { - let data = self - .0 - .abi - .constructor() - .unwrap() - .encode_input(self.0.code.clone(), &[]) - .unwrap(); - LegacyEthTransaction { - nonce, - gas_price: Default::default(), - gas: u64::MAX.into(), - to: None, - value: Default::default(), - data, - } + self.0.deploy_without_args(nonce) } fn solidity_artifacts_path() -> PathBuf { @@ -52,21 +38,7 @@ impl PrecompilesConstructor { impl PrecompilesContract { pub fn call_method(&self, method_name: &str, nonce: U256) -> LegacyEthTransaction { - let data = self - .0 - .abi - .function(method_name) - .unwrap() - .encode_input(&[]) - .unwrap(); - LegacyEthTransaction { - nonce, - gas_price: Default::default(), - gas: u64::MAX.into(), - to: Some(self.0.address), - value: Default::default(), - data, - } + self.0.call_method_without_args(method_name, nonce) } pub fn all_method_names() -> &'static [&'static str] { diff --git a/src/tests/res/blockhash.sol b/src/tests/res/blockhash.sol new file mode 100644 index 000000000..ae9dca997 --- /dev/null +++ b/src/tests/res/blockhash.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; + +contract BlockHash { + constructor() payable {} + + function test() public view { + require( + blockhash(0) == hex"a7ac0e4bd5ad1654392b64ecd40a69f983e8ce7c315639a339d19a880902457a", + "Bad block hash" + ); + } +} diff --git a/src/tests/sanity.rs b/src/tests/sanity.rs index b0faa2e74..66ff69ec6 100644 --- a/src/tests/sanity.rs +++ b/src/tests/sanity.rs @@ -2,6 +2,7 @@ use crate::prelude::Address; use crate::test_utils; use crate::types::{Wei, ERC20_MINT_SELECTOR}; use secp256k1::SecretKey; +use std::path::{Path, PathBuf}; const INITIAL_BALANCE: Wei = Wei::new_u64(1000); const INITIAL_NONCE: u64 = 0; @@ -178,3 +179,46 @@ fn check_selector() { hasher.update(b"mint(address,uint256)"); assert_eq!(hasher.finalize()[..4].to_vec(), ERC20_MINT_SELECTOR); } + +#[test] +fn test_block_hash() { + let runner = test_utils::AuroraRunner::default(); + let chain_id = { + let number = crate::prelude::U256::from(runner.chain_id); + crate::types::u256_to_arr(&number) + }; + let account_id = runner.aurora_account_id; + let block_hash = crate::engine::Engine::compute_block_hash(chain_id, 10, account_id.as_bytes()); + + assert_eq!( + hex::encode(block_hash.0).as_str(), + "4c8a60b32b74f184438a5e450951570bc1bda37caa7b6a3f178b80395845fb80" + ); +} + +#[test] +fn test_block_hash_contract() { + let (mut runner, mut source_account, _) = initialize_transfer(); + let test_constructor = test_utils::solidity::ContractConstructor::compile_from_source( + ["src", "tests", "res"].iter().collect::(), + Path::new("target").join("solidity_build"), + "blockhash.sol", + "BlockHash", + ); + let nonce = source_account.use_nonce(); + let test_contract = runner.deploy_contract( + &source_account.secret_key, + |c| c.deploy_without_args(nonce.into()), + test_constructor, + ); + + let result = runner + .submit_with_signer(&mut source_account, |nonce| { + test_contract.call_method_without_args("test", nonce) + }) + .unwrap(); + + if !result.status { + panic!("{}", String::from_utf8_lossy(&result.result)); + } +} From 3334f18a944cf97048cde9f3b38a67db7706fc63 Mon Sep 17 00:00:00 2001 From: Michael Birch Date: Tue, 3 Aug 2021 19:32:28 +0000 Subject: [PATCH 3/3] Named size constants --- src/engine.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/engine.rs b/src/engine.rs index f75d74190..6ac4471bf 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,4 +1,5 @@ use borsh::{BorshDeserialize, BorshSerialize}; +use core::mem; use evm::backend::{Apply, ApplyBackend, Backend, Basic, Log}; use evm::executor::{StackExecutor, StackSubstateMetadata}; use evm::ExitFatal; @@ -23,6 +24,9 @@ use crate::types::{u256_to_arr, AccountId, Wei, ERC20_MINT_SELECTOR}; /// Used as the first byte in the concatenation of data used to compute the blockhash. /// Could be useful in the future as a version byte, or to distinguish different types of blocks. const BLOCK_HASH_PREFIX: u8 = 0; +const BLOCK_HASH_PREFIX_SIZE: usize = 1; +const BLOCK_HEIGHT_SIZE: usize = 8; +const CHAIN_ID_SIZE: usize = 32; #[cfg(not(feature = "contract"))] pub fn current_address() -> Address { @@ -261,7 +265,12 @@ impl Engine { /// )) /// ``` pub fn compute_block_hash(chain_id: [u8; 32], block_height: u64, account_id: &[u8]) -> H256 { - let mut data = Vec::with_capacity(1 + 8 + 32 + account_id.len()); + debug_assert_eq!(BLOCK_HASH_PREFIX_SIZE, mem::size_of_val(&BLOCK_HASH_PREFIX)); + debug_assert_eq!(BLOCK_HEIGHT_SIZE, mem::size_of_val(&block_height)); + debug_assert_eq!(CHAIN_ID_SIZE, mem::size_of_val(&chain_id)); + let mut data = Vec::with_capacity( + BLOCK_HASH_PREFIX_SIZE + BLOCK_HEIGHT_SIZE + CHAIN_ID_SIZE + account_id.len(), + ); data.push(BLOCK_HASH_PREFIX); data.extend_from_slice(&block_height.to_be_bytes()); data.extend_from_slice(&chain_id);