Skip to content
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
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: major
- 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 EvmByteCodeType {
InitCode,
RuntimeCode,
}

impl EvmByteCodeType {
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: EvmByteCodeType,
) -> 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", EvmByteCodeType::InitCode)?;
extract_and_write_bytecode(&json, out_dir, ".sol.runtime.bin", EvmByteCodeType::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", EvmByteCodeType::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
16 changes: 11 additions & 5 deletions substrate/frame/revive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ pub use crate::{
exec::{DelegateInfo, Executable, Key, MomentOf, Origin as ExecOrigin},
pallet::{genesis, *},
storage::{AccountInfo, ContractInfo},
vm::ContractBlob,
vm::{BytecodeType, ContractBlob},
};
pub use codec;
pub use frame_support::{self, dispatch::DispatchInfo, weights::Weight};
Expand Down Expand Up @@ -1556,9 +1556,10 @@ impl<T: Config> Pallet<T> {
let (executable, upload_deposit) = match code {
Code::Upload(code) if code.starts_with(&polkavm_common::program::BLOB_MAGIC) => {
let upload_account = T::UploadOrigin::ensure_origin(origin)?;
let (executable, upload_deposit) = Self::try_upload_pvm_code(
let (executable, upload_deposit) = Self::try_upload_code(
upload_account,
code,
BytecodeType::Pvm,
storage_deposit_limit,
&exec_config,
)?;
Expand Down Expand Up @@ -1984,9 +1985,10 @@ impl<T: Config> Pallet<T> {
storage_deposit_limit: BalanceOf<T>,
) -> CodeUploadResult<BalanceOf<T>> {
let origin = T::UploadOrigin::ensure_origin(origin)?;
let (module, deposit) = Self::try_upload_pvm_code(
let (module, deposit) = Self::try_upload_code(
origin,
code,
BytecodeType::Pvm,
storage_deposit_limit,
&ExecConfig::new_substrate_tx(),
)?;
Expand Down Expand Up @@ -2127,13 +2129,17 @@ impl<T: Config> Pallet<T> {
}

/// Uploads new code and returns the Vm binary contract blob and deposit amount collected.
pub fn try_upload_pvm_code(
pub fn try_upload_code(
origin: T::AccountId,
code: Vec<u8>,
code_type: BytecodeType,
storage_deposit_limit: BalanceOf<T>,
exec_config: &ExecConfig<T>,
) -> Result<(ContractBlob<T>, BalanceOf<T>), DispatchError> {
let mut module = ContractBlob::from_pvm_code(code, origin)?;
let mut module = match code_type {
BytecodeType::Pvm => ContractBlob::from_pvm_code(code, origin)?,
BytecodeType::Evm => 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))
Expand Down
45 changes: 43 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,45 @@ 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;
let deployer_addr = ALICE_ADDR;
let _ = Pallet::<Test>::set_evm_balance(&deployer_addr, 1_000_000_000.into());

let (uploaded_blob, _) = Pallet::<Test>::try_upload_code(
deployer,
runtime_code.clone(),
crate::vm::BytecodeType::Evm,
u64::MAX,
&ExecConfig::new_substrate_tx(),
)
.unwrap();

let contract_address = crate::address::create1(&deployer_addr, 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)");
});
}