Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
11 changes: 11 additions & 0 deletions prdoc/pr_10129.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
title: Add upload evm code function
doc:
- audience: Node Dev
description: |-
EVM Runtime Code Upload for Foundry Integration
This feature enables direct upload of EVM runtime bytecode (deployed contract code without constructor) to the pallet-revive, supporting Foundry's code migration functionality between REVM and pallet-revive execution environments.
crates:
- name: pallet-revive
bump: minor
- name: pallet-revive-fixtures
bump: major
26 changes: 22 additions & 4 deletions substrate/frame/revive/fixtures/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,22 @@ enum ContractType {
Solidity,
}

/// Type of EVM bytecode to extract from Solidity compiler output.
#[derive(Clone, Copy)]
enum BytecodeType {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe EvmByteCodeType ?

InitCode,
RuntimeCode,
}

impl BytecodeType {
fn json_key(&self) -> &'static str {
match self {
Self::InitCode => "bytecode",
Self::RuntimeCode => "deployedBytecode",
}
}
}

impl Entry {
/// Create a new contract entry from the given path.
fn new(path: PathBuf, contract_type: ContractType) -> Self {
Expand Down Expand Up @@ -228,7 +244,7 @@ fn compile_with_standard_json(

serde_json::json!({
"*": {
"*": ["evm.bytecode"]
"*": ["evm.bytecode", "evm.deployedBytecode"]
}
}),

Expand Down Expand Up @@ -302,14 +318,15 @@ fn extract_and_write_bytecode(
compiler_json: &serde_json::Value,
out_dir: &Path,
file_suffix: &str,
bytecode_type: BytecodeType,
) -> Result<()> {
if let Some(contracts) = compiler_json["contracts"].as_object() {
for (_file_key, file_contracts) in contracts {
if let Some(contract_map) = file_contracts.as_object() {
for (contract_name, contract_data) in contract_map {
// Navigate through the JSON path to find the bytecode
let mut current = contract_data;
for path_segment in ["evm", "bytecode", "object"] {
for path_segment in ["evm", bytecode_type.json_key(), "object"] {
if let Some(next) = current.get(path_segment) {
current = next;
} else {
Expand Down Expand Up @@ -360,11 +377,12 @@ fn compile_solidity_contracts(

// Compile with solc for EVM bytecode
let json = compile_with_standard_json("solc", contracts_dir, &solidity_entries)?;
extract_and_write_bytecode(&json, out_dir, ".sol.bin")?;
extract_and_write_bytecode(&json, out_dir, ".sol.bin", BytecodeType::InitCode)?;
extract_and_write_bytecode(&json, out_dir, ".sol.runtime.bin", BytecodeType::RuntimeCode)?;

// Compile with resolc for PVM bytecode
let json = compile_with_standard_json("resolc", contracts_dir, &solidity_entries_pvm)?;
extract_and_write_bytecode(&json, out_dir, ".resolc.polkavm")?;
extract_and_write_bytecode(&json, out_dir, ".resolc.polkavm", BytecodeType::InitCode)?;

Ok(())
}
Expand Down
3 changes: 3 additions & 0 deletions substrate/frame/revive/fixtures/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pub enum FixtureType {
Resolc,
/// Solc (compiled Solidity contracts to EVM bytecode)
Solc,
/// Solc Runtime (compiled Solidity contracts to EVM runtime bytecode)
SolcRuntime,
}

#[cfg(feature = "std")]
Expand All @@ -40,6 +42,7 @@ impl FixtureType {
Self::Rust => ".polkavm",
Self::Resolc => ".resolc.polkavm",
Self::Solc => ".sol.bin",
Self::SolcRuntime => ".sol.runtime.bin",
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions substrate/frame/revive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2095,6 +2095,21 @@ impl<T: Config> Pallet<T> {
.map_err(ContractAccessError::StorageWriteFailed)
}

/// Uploads evm runtime code and returns the Vm binary contract blob and deposit amount
/// collected.
pub fn try_upload_evm_runtime_code(
origin: H160,
code: Vec<u8>,
storage_deposit_limit: BalanceOf<T>,
exec_config: &ExecConfig,
) -> Result<(ContractBlob<T>, BalanceOf<T>), DispatchError> {
let origin = T::AddressMapper::to_account_id(&origin);
let mut module = ContractBlob::from_evm_runtime_code(code, origin)?;
let deposit = module.store_code(exec_config, None)?;
ensure!(storage_deposit_limit >= deposit, <Error<T>>::StorageDepositLimitExhausted);
Ok((module, deposit))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't the exisiting try_upload_pvm_code be renamed try_upload_code and accept both?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here origin is H160 instead of AccountId, but we can use AccountId in both cases and have one method if you want


/// Pallet account, used to hold funds for contracts upload deposit.
pub fn account_id() -> T::AccountId {
use frame_support::PalletId;
Expand Down
43 changes: 41 additions & 2 deletions substrate/frame/revive/src/tests/sol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::{
assert_refcount,
call_builder::VmBinaryModule,
debug::DebugSettings,
test_utils::{builder::Contract, ALICE},
test_utils::{builder::Contract, ALICE, ALICE_ADDR},
tests::{
builder,
test_utils::{contract_base_deposit, ensure_stored, get_contract},
Expand Down Expand Up @@ -104,7 +104,6 @@ fn basic_evm_flow_works() {
fn basic_evm_flow_tracing_works() {
use crate::{
evm::{CallTrace, CallTracer, CallType},
test_utils::ALICE_ADDR,
tracing::trace,
};
let (code, _) = compile_module_with_type("Fibonacci", FixtureType::Solc).unwrap();
Expand Down Expand Up @@ -199,3 +198,43 @@ fn eth_contract_too_large() {
});
}
}

#[test]
fn upload_evm_runtime_code_works() {
use crate::{
exec::Executable,
primitives::ExecConfig,
storage::{AccountInfo, ContractInfo},
Pallet,
};

let (runtime_code, _runtime_hash) =
compile_module_with_type("Fibonacci", FixtureType::SolcRuntime).unwrap();

ExtBuilder::default().build().execute_with(|| {
let deployer = ALICE_ADDR;
let _ = Pallet::<Test>::set_evm_balance(&deployer, 1_000_000_000.into());

let (uploaded_blob, _) = Pallet::<Test>::try_upload_evm_runtime_code(
deployer,
runtime_code.clone(),
u64::MAX,
&ExecConfig::new_substrate_tx(),
)
.unwrap();

let contract_address = crate::address::create1(&deployer, 0u32.into());

let contract_info =
ContractInfo::<Test>::new(&contract_address, 0u32.into(), *uploaded_blob.code_hash())
.unwrap();
AccountInfo::<Test>::insert_contract(&contract_address, contract_info);

// Call the contract and verify it works
let result = builder::bare_call(contract_address)
.data(Fibonacci::FibonacciCalls::fib(Fibonacci::fibCall { n: 10u64 }).abi_encode())
.build_and_unwrap_result();
let decoded = Fibonacci::fibCall::abi_decode_returns(&result.data).unwrap();
assert_eq!(55u64, decoded, "Contract should correctly compute fibonacci(10)");
});
}
Loading