Skip to content
Open
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
38 changes: 11 additions & 27 deletions .github/workflows/tests-evm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,46 +43,30 @@ jobs:
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
repository: paritytech/evm-test-suite
ref: 79144d85626f97e481b84a16d2b8f0813d03548b
ref: 84e536af80513f87bc16f6c7b7dbf796fe9010ab
path: evm-test-suite

- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
- uses: denoland/setup-deno@v1
with:
node-version: 22
deno-version: v2.x

- name: script
env:
# EVM tests don't work with batchSize 300 on self-hosted runners in docker container
BATCH_SIZE: 100
run: |
echo "Change to the evm-test-suite directory"
cd evm-test-suite
echo "Download the resolc binary"
wget -O resolc https://github.com/paritytech/revive/releases/download/v0.3.0/resolc-x86_64-unknown-linux-musl -q
chmod +x resolc
mv resolc /usr/local/bin
resolc --version

deno --version

echo "Check that binaries are in place"
export NODE_BIN_PATH=$(readlink -f ../target/release/revive-dev-node)
export REVIVE_DEV_NODE_PATH=$(readlink -f ../target/release/revive-dev-node)
export ETH_RPC_PATH=$(readlink -f ../target/release/eth-rpc)
export RESOLC_PATH=/usr/local/bin/resolc
echo $NODE_BIN_PATH $ETH_RPC_PATH $RESOLC_PATH
echo $REVIVE_DEV_NODE_PATH $ETH_RPC_PATH

echo "Install npm dependencies"
npm install
# cat matter-labs-tests/hardhat.config.ts | grep batchSize
echo "== Running pvm tests ==j"
START_REVIVE_DEV_NODE=true START_ETH_RPC=true deno task test:pvm

echo "Installing solc"
wget https://github.com/ethereum/solidity/releases/download/v0.8.30/solc-static-linux -q
chmod +x solc-static-linux
mv solc-static-linux /usr/local/bin/solc
# TODO restore once tests can be run against the revive-dev-node
# echo "Run the tests"
# echo "bash init.sh --kitchensink -- --matter-labs -- $NODE_BIN_PATH $ETH_RPC_PATH $RESOLC_PATH"
# bash init.sh --kitchensink -- --matter-labs -- $NODE_BIN_PATH $ETH_RPC_PATH $RESOLC_PATH
echo "Run eth-rpc tests"
bash init.sh --kitchensink http://localhost:9944 --eth-rpc -- $NODE_BIN_PATH $ETH_RPC_PATH $RESOLC_PATH
echo "== Running evm tests =="
START_REVIVE_DEV_NODE=true START_ETH_RPC=true deno task test:evm

- name: Collect tests results
if: always()
Expand Down
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.

23 changes: 23 additions & 0 deletions prdoc/pr_10018.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
title: 'pallet_revive: Change EVM call opcodes to respect the gas limit passed'
doc:
- audience: Runtime Dev
description: |-
So far the EVM family of call opcodes did ignore the `gas` argument passed to them. The consequence was that we were not able to limit the resource usage of sub contract calls. This PR changes that. **Gas is now fully functional on the EVM backend.**

The resources of any sub contract call are now effectively limited. This is both true for `Weight` and storage deposit. The algorithm works in a way that if you pass `x%` of the current `GAS` the the `CALL` opcode the sub call will have `x%` of currently available `Weight` and storage deposit available. This allows the caller to always make sure to execute code after retuning from a sub call.

### Changes to the gas meter

I needed to change the gas meter to track `gas_consumed` instead of `gas_left`. Otherwise it is not possible to know the total amount of gas spent for a call stack that is not unwinded, yet.

### Followup
- Implement a new PVM syscall that takes the new unified gas instead of `Weight` and storage deposit limit
- Change resolc to use this new syscall
- Enable the test added here to run on resolc
crates:
- name: pallet-revive
bump: major
- name: pallet-revive-eth-rpc
bump: major
- name: pallet-revive-fixtures
bump: major
1 change: 1 addition & 0 deletions substrate/frame/revive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ subxt-signer = { workspace = true, optional = true, features = ["unstable-eth"]
[dev-dependencies]
array-bytes = { workspace = true, default-features = true }
assert_matches = { workspace = true }
itertools = { workspace = true }
pretty_assertions = { workspace = true }
secp256k1 = { workspace = true, features = ["recovery"] }
serde_json = { workspace = true }
Expand Down
4 changes: 4 additions & 0 deletions substrate/frame/revive/fixtures/contracts/Callee.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@ contract Callee {
stop()
}
}

function consumeAllReftime() external {
while (true) {}
}
}
20 changes: 20 additions & 0 deletions substrate/frame/revive/fixtures/contracts/Caller.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ contract ChildRevert {
}

contract Caller {
uint256 public data;

function normal(address _callee, uint64 _value, bytes memory _data, uint64 _gas)
external
returns (bool success, bytes memory output)
Expand Down Expand Up @@ -65,4 +67,22 @@ contract Caller {
}
}
}

function callPartialGas(address _callee, bytes memory _data, uint64 _gasDivisor, uint8 _callType)
external
returns (bool success)
{
uint256 gas = gasleft() / _gasDivisor;
bytes memory output;
if (_callType == 0) {
(success, output) = _callee.call{gas: gas }(_data);
} else if (_callType == 1) {
(success, output) = _callee.staticcall{gas: gas }(_data);
} else if (_callType == 2) {
(success, output) = _callee.delegatecall{gas: gas }(_data);
} else {
revert("unknown call type");
}
data = 42;
}
}
7 changes: 3 additions & 4 deletions substrate/frame/revive/rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ use jsonrpsee::{
types::{ErrorCode, ErrorObjectOwned},
};
use pallet_revive::evm::*;
use sp_arithmetic::Permill;
use sp_core::{keccak_256, H160, H256, U256};
use thiserror::Error;
use tokio::time::Duration;
Expand Down Expand Up @@ -265,9 +264,9 @@ impl EthRpcServer for EthRpcServerImpl {
}

async fn max_priority_fee_per_gas(&self) -> RpcResult<U256> {
// TODO: Provide better estimation
let gas_price = self.gas_price().await?;
Ok(Permill::from_percent(20).mul_ceil(gas_price))
// We do not support tips. Hence the recommended priority fee is
// always zero. The effective gas price will always be the base price.
Ok(Default::default())

Choose a reason for hiding this comment

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

Do we have any data points on wallets being confused by the fact that this is zero?

Copy link
Contributor

Choose a reason for hiding this comment

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

this (eth_maxPriorityFeePerGas) is not really used in practice, wallets and library like alloy will usually use eth_feeHistory to propose the priority fee

Since we don't support this concept for now anyway we are better off with this change anyway

}

async fn get_code(&self, address: H160, block: BlockNumberOrTagOrHash) -> RpcResult<Bytes> {
Expand Down
38 changes: 10 additions & 28 deletions substrate/frame/revive/src/evm/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ pub trait InfoT<T: Config>: seal::Sealed {
}

/// Convert a weight to an unadjusted fee.
fn weight_to_fee(_weight: &Weight, _combinator: Combinator) -> BalanceOf<T> {
fn weight_to_fee(_weight: &Weight) -> BalanceOf<T> {
Zero::zero()
}

Expand Down Expand Up @@ -158,14 +158,6 @@ pub trait InfoT<T: Config>: seal::Sealed {
}
}

/// Which function to use in order to combine `ref_time` and `proof_size` to a fee.
pub enum Combinator {
/// Minimum function.
Min,
/// Maximum function.
Max,
}

impl<const P: u128, const Q: u128, T: Config> BlockRatioFee<P, Q, T> {
const REF_TIME_TO_FEE: FixedU128 = {
assert!(P > 0 && Q > 0);
Expand All @@ -179,26 +171,17 @@ impl<const P: u128, const Q: u128, T: Config> BlockRatioFee<P, Q, T> {
FixedU128::from_rational(max_weight.ref_time().into(), max_weight.proof_size().into());
Self::REF_TIME_TO_FEE.saturating_mul(ratio)
}

/// Calculate the fee for a weight.
fn weight_to_fee(weight: &Weight, combinator: Combinator) -> BalanceOf<T> {
let ref_time_fee = Self::REF_TIME_TO_FEE
.saturating_mul_int(BalanceOf::<T>::saturated_from(weight.ref_time()));
let proof_size_fee = Self::proof_size_to_fee()
.saturating_mul_int(BalanceOf::<T>::saturated_from(weight.proof_size()));

match combinator {
Combinator::Max => ref_time_fee.max(proof_size_fee),
Combinator::Min => ref_time_fee.min(proof_size_fee),
}
}
}

impl<const P: u128, const Q: u128, T: Config> WeightToFee for BlockRatioFee<P, Q, T> {
type Balance = BalanceOf<T>;

fn weight_to_fee(weight: &Weight) -> Self::Balance {
Self::weight_to_fee(weight, Combinator::Max)
let ref_time_fee = Self::REF_TIME_TO_FEE
.saturating_mul_int(BalanceOf::<T>::saturated_from(weight.ref_time()));
let proof_size_fee = Self::proof_size_to_fee()
.saturating_mul_int(BalanceOf::<T>::saturated_from(weight.proof_size()));
ref_time_fee.max(proof_size_fee)
}
}

Expand Down Expand Up @@ -244,8 +227,8 @@ where
/// Calculate the fee using the weight instead of a dispatch info.
fn tx_fee_from_weight(encoded_len: u32, weight: &Weight) -> BalanceOf<E::Config> {
let fixed_fee = Self::fixed_fee(encoded_len);
let weight_fee = Self::next_fee_multiplier()
.saturating_mul_int(Self::weight_to_fee(weight, Combinator::Max));
let weight_fee =
Self::next_fee_multiplier().saturating_mul_int(Self::weight_to_fee(weight));
fixed_fee.saturating_add(weight_fee)
}

Expand All @@ -254,7 +237,6 @@ where
&<E::Config as frame_system::Config>::BlockWeights::get()
.get(DispatchClass::Normal)
.base_extrinsic,
Combinator::Max,
)
.saturating_add(Self::length_to_fee(encoded_len))
}
Expand Down Expand Up @@ -316,8 +298,8 @@ where
uxt.encoded_size() as u32
}

fn weight_to_fee(weight: &Weight, combinator: Combinator) -> BalanceOf<E::Config> {
<E::Config as TxConfig>::WeightToFee::weight_to_fee(&weight, combinator)
fn weight_to_fee(weight: &Weight) -> BalanceOf<E::Config> {
<E::Config as TxConfig>::WeightToFee::weight_to_fee(weight)
}

/// Convert an unadjusted fee back to a weight.
Expand Down
Loading