Skip to content
53 changes: 53 additions & 0 deletions crates/forge/tests/cli/revive_vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,59 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)
"#]]);
});

forgetest!(fee_revive, |prj, cmd| {
prj.insert_ds_test();
prj.insert_vm();
prj.insert_console();
prj.add_source(
"Fee.t.sol",
r#"
import "./test.sol";
import "./Vm.sol";
import {console} from "./console.sol";

contract Fee is DSTest {
Copy link

Choose a reason for hiding this comment

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

try calling block.basefee from a contract that's deployed on pvm side

Copy link
Author

Choose a reason for hiding this comment

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

Added some more tests

Copy link

Choose a reason for hiding this comment

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

forgetest!(fee_revive, |prj, cmd| {
    prj.insert_ds_test();
    prj.insert_vm();
    prj.insert_console();
    prj.add_source("C.sol",
    r#"
    contract C {
      function example() public returns (uint256) {
        return block.basefee;
      }
    }
    "#
    ).unwrap();
    prj.add_source(
        "Fee.t.sol",
        r#"
import "./test.sol";
import "./Vm.sol";
import "./C.sol";
import {console} from "./console.sol";

contract Fee is DSTest {
  Vm constant vm = Vm(HEVM_ADDRESS);

  function test_Fee() public {
      C target = new C();
      uint256 original = target.example();
      vm.fee(25 gwei);
      uint256 newValue = target.example();
      assertEq(newValue, 25 gwei);
      assert(original != newValue);
  }
}
"#,
    )
    .unwrap();

    let res = cmd.args(["test", "--resolc", "--resolc-startup", "-vvv"]).assert_success();
    res.stderr_eq(str![""]).stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
[COMPILING_FILES] with [RESOLC_VERSION]
[RESOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for src/Fee.t.sol:Fee
[PASS] test_Fee() ([GAS])
Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

result:

Traces:
  [83471631] Fee::test_Fee()
    ├─ [16788608] → new <unknown>@0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC
    │   └─ ← [Return] 2366 bytes of code
    ├─ [33317875] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::example()
    │   └─ ← [Return] 1000000000 [1e9]
    ├─ [0] VM::fee(25000000000 [2.5e10])
    │   └─ ← [Return]
    ├─ [33317875] 0x7D8CB8F412B3ee9AC79558791333F41d2b1ccDAC::example()
    │   └─ ← [Return] 1000000000 [1e9]
    ├─ emit log(val: "Error: a == b not satisfied [uint]")
    ├─ emit log_named_uint(key: "  Expected", val: 25000000000 [2.5e10])
    ├─ emit log_named_uint(key: "    Actual", val: 1000000000 [1e9])

Copy link
Author

Choose a reason for hiding this comment

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

Seems like the pallet-revive's basefee opcode implementation doesn't read from the effective_gas_price, but has a hardcoded default value of 1 gwei.

Copy link

Choose a reason for hiding this comment

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

can you fill out an issue on foundry-polkadot to implement the functionality for pallet-revive/foundry-polkadot

Copy link
Author

@filip-parity filip-parity Oct 29, 2025

Choose a reason for hiding this comment

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

I updated the ticket with a comment and created this issue related to the default value of BASEFEE opcode

Vm constant vm = Vm(HEVM_ADDRESS);

function test_Fee() public {
vm.pvm(true);
uint256 original = block.basefee;
vm.fee(25 gwei);
uint256 newValue = block.basefee;
assert(original != newValue);
assertEq(newValue, 25 gwei);
}
}
"#,
)
.unwrap();

let res = cmd.args(["test", "--resolc", "-vvv"]).assert_success();
res.stderr_eq(str![""]).stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
[COMPILING_FILES] with [RESOLC_VERSION]
[RESOLC_VERSION] [ELAPSED]
Compiler run successful with warnings:
Warning: Warning: Your code or one of its dependencies uses the 'extcodesize' instruction, which is
usually needed in the following cases:
1. To detect whether an address belongs to a smart contract.
2. To detect whether the deploy code execution has finished.
Polkadot comes with native account abstraction support (so smart contracts are just accounts
coverned by code), and you should avoid differentiating between contracts and non-contract
addresses.
[FILE]

Ran 1 test for src/Fee.t.sol:Fee
[PASS] test_Fee() ([GAS])
Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

forgetest!(deal, |prj, cmd| {
prj.insert_ds_test();
prj.insert_vm();
Expand Down
18 changes: 17 additions & 1 deletion crates/revive-strategy/src/cheatcodes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ use foundry_cheatcodes::{
Broadcast, BroadcastableTransactions, CheatcodeInspectorStrategy,
CheatcodeInspectorStrategyContext, CheatcodeInspectorStrategyRunner, CheatsConfig, CheatsCtxt,
CommonCreateInput, DealRecord, Ecx, EvmCheatcodeInspectorStrategyRunner, Result,
Vm::{dealCall, getNonce_0Call, pvmCall, rollCall, setNonceCall, setNonceUnsafeCall, warpCall},
Vm::{
dealCall, feeCall, getNonce_0Call, pvmCall, rollCall, setNonceCall, setNonceUnsafeCall,
warpCall,
},
};
use foundry_common::sh_err;
use foundry_compilers::resolc::dual_compiled_contracts::DualCompiledContracts;
Expand Down Expand Up @@ -164,6 +167,11 @@ fn set_timestamp(new_timestamp: U256, ecx: Ecx<'_, '_, '_>) {
});
}

fn set_basefee(new_basefee: U256, ecx: Ecx<'_, '_, '_>) {
Copy link

Choose a reason for hiding this comment

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

okay, so i assume it's not possible to modify the value of Weights::seal_base_fee on pallet-revive?

Copy link

Choose a reason for hiding this comment

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

and do we have anyone who's up to date on gas mapping?

Copy link
Author

Choose a reason for hiding this comment

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

Not really

// Set basefee in EVM context.
ecx.block.basefee = new_basefee.try_into().expect("Basefee exceeds u64");
Copy link
Collaborator

@smiasojed smiasojed Sep 30, 2025

Choose a reason for hiding this comment

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

did you check how zksync is handling it?
I think they keep it consistent between vm calls.

  1. Single Source of Truth

// vm.fee() cheatcode sets this:
ccx.ecx.block.basefee =
newBasefee.saturating_to(); // Line 459 in
evm.rs

  1. Shared Through EthEvmContext

When zkVM is called, it receives the same
EthEvmContext that EVM uses:

// In zksync_try_call (strategy/zksync):
foundry_zksync_core::vm::call(call,
factory_deps, ecx, ccx)

  1. zkVM Extracts Basefee from Shared Context

// In runner.rs:303 - zkVM reads basefee from
the shared ecx.block:
block_basefee: min(max_fee_per_gas.to_ru256(),
rU256::from(ecx.block.basefee)),

Why do we use different approach?

Copy link
Author

Choose a reason for hiding this comment

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

Added shared source of truth ecx.block.basefee, then clamping the value with maximum gas price.

}

/// Implements [CheatcodeInspectorStrategyRunner] for PVM.
#[derive(Debug, Default, Clone)]
pub struct PvmCheatcodeInspectorStrategyRunner;
Expand Down Expand Up @@ -246,6 +254,14 @@ impl CheatcodeInspectorStrategyRunner for PvmCheatcodeInspectorStrategyRunner {

Ok(Default::default())
}
t if using_pvm && is::<feeCall>(t) => {
let &feeCall { newBasefee } = cheatcode.as_any().downcast_ref().unwrap();

tracing::info!(cheatcode = ?cheatcode.as_debug() , using_pvm = ?using_pvm);
set_basefee(newBasefee, ccx.ecx);

Ok(Default::default())
}
// Not custom, just invoke the default behavior
_ => cheatcode.dyn_apply(ccx, executor),
}
Expand Down
Loading