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

Add Utility precompile #215

Merged
merged 2 commits into from
Feb 3, 2025
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pallet-evm-precompile-babe = { path = "precompiles/babe", default-features = fal
pallet-evm-precompile-governance = { path = "precompiles/governance", default-features = false }
pallet-evm-precompile-treasury = { path = "precompiles/treasury", default-features = false }
pallet-evm-precompile-preimage = { path = "precompiles/preimage", default-features = false }
pallet-evm-precompile-utility = { path = "precompiles/utility", default-features = false }

async-trait = "0.1"
bn = { package = "substrate-bn", version = "0.6", default-features = false }
Expand Down
44 changes: 44 additions & 0 deletions precompiles/utility/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
[package]
name = "pallet-evm-precompile-utility"
authors = { workspace = true }
edition = "2021"
version = "0.0.1"

[dependencies]
precompile-utils = { workspace = true }
pallet-utility = { workspace = true }

evm = { workspace = true }
fp-evm = { workspace = true }
pallet-evm = { workspace = true }

frame-support = { workspace = true }
frame-system = { workspace = true }

sp-std = { workspace = true }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
sp-staking = { workspace = true }


environmental = { workspace = true }

[features]
default = ["std"]
std = [
"precompile-utils/std",
"pallet-utility/std",

"evm/std",
"fp-evm/std",
"pallet-evm/std",

"frame-support/std",
"frame-system/std",

"sp-std/std",
"sp-core/std",
"sp-io/std",
]

200 changes: 200 additions & 0 deletions precompiles/utility/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![allow(missing_docs)]

use evm::{ExitError, ExitReason};
use fp_evm::{Context, Log, PrecompileFailure, PrecompileHandle, Transfer};
use frame_support::traits::ConstU32;
use precompile_utils::{evm::costs::call_cost, prelude::*};
use sp_core::{H160, U256};
use sp_std::{iter::repeat, marker::PhantomData, vec, vec::Vec};

pub struct UtilityPrecompile<Runtime>(PhantomData<Runtime>);

pub fn log_subcall_succeeded(address: impl Into<H160>, index: usize) -> Log {
log1(address, LOG_SUBCALL_SUCCEEDED, solidity::encode_event_data(U256::from(index)))
}

pub fn log_subcall_failed(address: impl Into<H160>, index: usize) -> Log {
log1(address, LOG_SUBCALL_FAILED, solidity::encode_event_data(U256::from(index)))
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Mode {
BatchSome, // = "batchSome(address[],uint256[],bytes[],uint64[])",
BatchSomeUntilFailure, // = "batchSomeUntilFailure(address[],uint256[],bytes[],uint64[])",
BatchAll, // = "batchAll(address[],uint256[],bytes[],uint64[])",
}

pub const LOG_SUBCALL_SUCCEEDED: [u8; 32] = keccak256!("SubcallSucceeded(uint256)");
pub const LOG_SUBCALL_FAILED: [u8; 32] = keccak256!("SubcallFailed(uint256)");
pub const CALL_DATA_LIMIT: u32 = 2u32.pow(16);
pub const ARRAY_LIMIT: u32 = 2u32.pow(9);

type GetCallDataLimit = ConstU32<CALL_DATA_LIMIT>;
type GetArrayLimit = ConstU32<ARRAY_LIMIT>;

#[precompile_utils::precompile]
impl<Runtime> UtilityPrecompile<Runtime>
where
Runtime: pallet_evm::Config,
{
#[precompile::public("batchSome(address[],uint256[],bytes[],uint64[])")]
fn batch_some(
handle: &mut impl PrecompileHandle,
to: BoundedVec<Address, GetArrayLimit>,
value: BoundedVec<U256, GetArrayLimit>,
call_data: BoundedVec<BoundedBytes<GetCallDataLimit>, GetArrayLimit>,
gas_limit: BoundedVec<u64, GetArrayLimit>,
) -> EvmResult {
Self::inner_batch(Mode::BatchSome, handle, to, value, call_data, gas_limit)
}

fn inner_batch(
mode: Mode,
handle: &mut impl PrecompileHandle,
to: BoundedVec<Address, GetArrayLimit>,
value: BoundedVec<U256, GetArrayLimit>,
call_data: BoundedVec<BoundedBytes<GetCallDataLimit>, GetArrayLimit>,
gas_limit: BoundedVec<u64, GetArrayLimit>,
) -> EvmResult {
let addresses = Vec::from(to).into_iter().enumerate();
let values = Vec::from(value).into_iter().map(Some).chain(repeat(None));
let calls_data =
Vec::from(call_data).into_iter().map(|x| Some(x.into())).chain(repeat(None));
let gas_limits = Vec::from(gas_limit).into_iter().map(|x|
// x = 0 => forward all remaining gas
if x == 0 {
None
} else {
Some(x)
}
).chain(repeat(None));

// Cost of batch log. (doesn't change when index changes)
let log_cost = log_subcall_failed(handle.code_address(), 0)
.compute_cost()
.map_err(|_| revert("Failed to compute log cost"))?;

for ((i, address), (value, (call_data, gas_limit))) in
addresses.zip(values.zip(calls_data.zip(gas_limits)))
{
let address = address.0;
let value = value.unwrap_or(U256::zero());
let call_data = call_data.unwrap_or(vec![]);

let sub_context =
Context { caller: handle.context().caller, address, apparent_value: value };

let transfer = if value.is_zero() {
None
} else {
Some(Transfer { source: handle.context().caller, target: address, value })
};

// We reserve enough gas to emit a final log and perform the subcall itself.
// If not enough gas we stop there according to Mode strategy.
let remaining_gas = handle.remaining_gas();

let forwarded_gas = match (remaining_gas.checked_sub(log_cost), mode) {
(Some(remaining), _) => remaining,
(None, Mode::BatchAll) => {
return Err(PrecompileFailure::Error { exit_status: ExitError::OutOfGas })
},
(None, _) => {
return Ok(());
},
};

// Cost of the call itself that the batch precompile must pay.
let call_cost = call_cost(value, <Runtime as pallet_evm::Config>::config());

let forwarded_gas = match forwarded_gas.checked_sub(call_cost) {
Some(remaining) => remaining,
None => {
let log = log_subcall_failed(handle.code_address(), i);
handle.record_log_costs(&[&log])?;
log.record(handle)?;

match mode {
Mode::BatchAll => {
return Err(PrecompileFailure::Error {
exit_status: ExitError::OutOfGas,
})
},
Mode::BatchSomeUntilFailure => return Ok(()),
Mode::BatchSome => continue,
}
},
};

// If there is a provided gas limit we ensure there is enough gas remaining.
let forwarded_gas = match gas_limit {
None => forwarded_gas, // provide all gas if no gas limit,
Some(limit) => {
if limit > forwarded_gas {
let log = log_subcall_failed(handle.code_address(), i);
handle.record_log_costs(&[&log])?;
log.record(handle)?;

match mode {
Mode::BatchAll => {
return Err(PrecompileFailure::Error {
exit_status: ExitError::OutOfGas,
})
},
Mode::BatchSomeUntilFailure => return Ok(()),
Mode::BatchSome => continue,
}
}
limit
},
};

let (reason, output) =
handle.call(address, transfer, call_data, Some(forwarded_gas), false, &sub_context);

// Logs
// We reserved enough gas so this should not OOG.
match reason {
ExitReason::Revert(_) | ExitReason::Error(_) => {
let log = log_subcall_failed(handle.code_address(), i);
handle.record_log_costs(&[&log])?;
log.record(handle)?
},
ExitReason::Succeed(_) => {
let log = log_subcall_succeeded(handle.code_address(), i);
handle.record_log_costs(&[&log])?;
log.record(handle)?
},
_ => (),
}

// How to proceed
match (mode, reason) {
// _: Fatal is always fatal
(_, ExitReason::Fatal(exit_status)) => {
return Err(PrecompileFailure::Fatal { exit_status })
},

// BatchAll : Reverts and errors are immediatly forwarded.
(Mode::BatchAll, ExitReason::Revert(exit_status)) => {
return Err(PrecompileFailure::Revert { exit_status, output })
},
(Mode::BatchAll, ExitReason::Error(exit_status)) => {
return Err(PrecompileFailure::Error { exit_status })
},

// BatchSomeUntilFailure : Reverts and errors prevent subsequent subcalls to
// be executed but the precompile still succeed.
(Mode::BatchSomeUntilFailure, ExitReason::Revert(_) | ExitReason::Error(_)) => {
return Ok(())
},

// Success or ignored revert/error.
(_, _) => (),
}
}

Ok(())
}
}
2 changes: 2 additions & 0 deletions runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ pallet-evm-precompile-babe = { workspace = true }
pallet-evm-precompile-governance = { workspace = true }
pallet-evm-precompile-treasury = { workspace = true }
pallet-evm-precompile-preimage = { workspace = true }
pallet-evm-precompile-utility = { workspace = true }

# Parachains
runtime-parachains = { workspace = true }
Expand Down Expand Up @@ -286,6 +287,7 @@ std = [
"pallet-evm-precompile-governance/std",
"pallet-evm-precompile-treasury/std",
"pallet-evm-precompile-preimage/std",
"pallet-evm-precompile-utility/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
Expand Down
2 changes: 1 addition & 1 deletion runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("atleta"),
impl_name: create_runtime_str!("atleta"),
authoring_version: 1,
spec_version: 10,
spec_version: 11,
impl_version: 1,
apis: RUNTIME_API_VERSIONS,
transaction_version: 3,
Expand Down
2 changes: 2 additions & 0 deletions runtime/src/precompiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use pallet_evm_precompile_preimage::PreimagePrecompile;
use pallet_evm_precompile_staking::StakingPrecompile;
use pallet_evm_precompile_treasury::TreasuryPrecompile;
use pallet_evm_precompile_utility::UtilityPrecompile;

use frame_support::traits::Contains;
use precompile_utils::precompile_set::*;
Expand Down Expand Up @@ -85,9 +86,10 @@
PrecompileAt<
AddressU64<2006>,
NominationPoolsPrecompile<R>,
(CallableByContract, CallableByPrecompile),

Check warning on line 89 in runtime/src/precompiles.rs

View workflow job for this annotation

GitHub Actions / cargo check

Diff in /tmp/github-runner-moneyfactory-2/atleta/atleta/runtime/src/precompiles.rs
>,
PrecompileAt<AddressU64<2007>, BabePrecompile<R>, (CallableByContract, CallableByPrecompile)>,
PrecompileAt<AddressU64<2008>, UtilityPrecompile<R>, (CallableByContract, CallableByPrecompile)>,
);

/// The PrecompileSet installed in the Atleta runtime.
Expand Down
Loading