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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
- If EVM test fixtures under `target/evm-contracts/*.bin` are missing, run `make build-contracts-evm`.
- If Wasm contract fixtures are missing, run `make build-contracts-rs`.
- Keep `resources/local/chainspec.toml.in` in sync when editing chainspecs; run `./generate-chainspec.sh` when `resources/local/chainspec.toml` is missing or stale.
- Always prefer invoking the appropriate system contract over manually modifying mint-owned data.
- Treat idempotent system contract/predeploy upserts in protocol upgrade handlers as standard activation behavior, not as an alternative to `global_state_update`.
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 node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ casper-executor-wasm = { version = "0.1.3", path = "../executor/wasm" }
casper-executor-wasm-interface = { version = "0.1.3", path = "../executor/wasm_interface" }
fs_extra = "1.3.0"
casper-executor-evm = { version = "0.1.0", path = "../executor/evm" }
parking_lot = "0.12.5"

[dev-dependencies]
casper-binary-port = { version = "1.1.1", path = "../binary_port", features = ["testing"] }
Expand Down
3 changes: 3 additions & 0 deletions node/src/components/contract_runtime/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ pub enum BlockExecutionError {
UnsupportedTransactionKind(u8),
#[error("Error while converting transaction to internal representation: {0}")]
TransactionConversion(String),
/// Failed to reconcile EVM balance rounding with total supply.
#[error("Failed to reconcile EVM dust with total supply: {0}")]
EvmDust(String),
/// Invalid gas limit amount.
#[error("Invalid gas limit amount: {0}")]
InvalidGasLimit(U512),
Expand Down
46 changes: 40 additions & 6 deletions node/src/components/contract_runtime/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ pub(crate) mod wasm_v2_request;

use casper_executor_wasm::ExecutorV2;
use itertools::Itertools;
use std::{collections::BTreeMap, convert::TryInto, sync::Arc, time::Instant};
use parking_lot::RwLock;
use std::{
cell::RefCell, collections::BTreeMap, convert::TryInto, rc::Rc, sync::Arc, time::Instant,
};
use tracing::{debug, error, info, trace, warn};
use wasm_v2_request::{WasmV2Request, WasmV2Result};

Expand Down Expand Up @@ -32,9 +35,12 @@ use casper_storage::{
lmdb::LmdbGlobalState, scratch::ScratchGlobalState, CommitProvider, ScratchProvider,
StateProvider, StateReader,
},
system::runtime_native::Config as NativeRuntimeConfig,
system::{
mint::Mint,
runtime_native::{Config as NativeRuntimeConfig, Id as NativeRuntimeId, RuntimeNative},
},
tracking_copy::{TrackingCopyEntityExt, TrackingCopyError},
TrackingCopy,
AddressGenerator, TrackingCopy,
};
use casper_types::{
account::{Account, AccountHash},
Expand All @@ -45,10 +51,10 @@ use casper_types::{
ReceiptStatus as EvmReceiptStatus,
},
execution::{Effects, ExecutionResult, TransformKindV2, TransformV2},
system::handle_payment::ARG_AMOUNT,
system::{handle_payment::ARG_AMOUNT, MINT},
BlockHash, BlockHeader, BlockTime, BlockV2, CLValue, Chainspec, ChecksumRegistry, Digest,
EntityAddr, EraEndV2, EraId, FeeHandling, Gas, InvalidTransaction, InvalidTransactionV1, Key,
ProtocolVersion, PublicKey, RefundHandling, StoredValue, TimeDiff, Transaction,
Phase, ProtocolVersion, PublicKey, RefundHandling, StoredValue, TimeDiff, Transaction,
TransactionEntryPoint, AUCTION_LANE_ID, MINT_LANE_ID, U512,
};

Expand Down Expand Up @@ -1203,7 +1209,35 @@ pub fn execute_finalized_block(
.map_err(|error| {
BlockExecutionError::TransactionConversion(error.to_string())
})?;
let execution_effects = tracking_copy.effects();
// EVM balances were already rounded down by the executor. Call mint
// without debiting a purse, using the same tracking copy so its supply
// reduction commits with the EVM balance writes.
// TODO: Move this mint runtime setup out of block execution once the
// runtime refactor is complete.
let execution_effects = if outcome.dust_motes.is_zero() {
tracking_copy.effects()
} else {
let tracking_copy = Rc::new(RefCell::new(tracking_copy));
let id = NativeRuntimeId::Transaction(transaction_hash);
let phase = Phase::Session;
let address_generator =
Arc::new(RwLock::new(AddressGenerator::new(&id.seed(), phase)));
let mut runtime = RuntimeNative::new_system_contract_runtime(
native_runtime_config.clone(),
protocol_version,
id,
address_generator,
Rc::clone(&tracking_copy),
phase,
MINT,
)
.map_err(|error| BlockExecutionError::EvmDust(error.to_string()))?;
runtime
.reduce_total_supply(outcome.dust_motes)
.map_err(|error| BlockExecutionError::EvmDust(error.to_string()))?;
let effects = tracking_copy.borrow().effects();
effects
};
state_root_hash =
scratch_state.commit_effects(state_root_hash, execution_effects.clone())?;
let effective_gas_price = evm_transaction.effective_gas_price(base_fee_wei);
Expand Down
99 changes: 99 additions & 0 deletions node/src/reactor/main_reactor/tests/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,32 @@ fn evm_coinbase_transfer_init_code() -> Vec<u8> {
])
}

fn evm_one_wei_transfer_init_code(recipient: evm::Address) -> Vec<u8> {
let mut runtime = vec![
opcode::PUSH1,
0, // return size
opcode::PUSH1,
0, // return offset
opcode::PUSH1,
0, // calldata size
opcode::PUSH1,
0, // calldata offset
opcode::PUSH1,
1, // value in wei
opcode::PUSH20,
];
runtime.extend_from_slice(recipient.as_bytes());
runtime.extend_from_slice(&[
opcode::PUSH2,
0xff,
0xff, // gas
opcode::CALL,
opcode::POP,
opcode::STOP,
]);
evm_init_code_returning(runtime)
}

fn signed_evm_deploy_transaction(chain_id: u64) -> EvmTransaction {
signed_evm_create_transaction(chain_id, 0, evm_log_emitting_init_code())
}
Expand Down Expand Up @@ -1318,6 +1344,79 @@ async fn should_execute_evm_transaction_and_store_receipt() {
);
}

#[tokio::test]
async fn should_reduce_total_supply_for_evm_rounding_dust_without_debiting_sender_again() {
let evm_config = EvmConfig {
enabled: true,
chain_id: 0x4353_50FF,
spec: EvmSpec::Prague,
block_gas_limit: 30_000_000,
base_fee: 1,
wei_per_mote: DEFAULT_WEI_PER_MOTE,
};
let config = SingleTransactionTestCase::default_test_config()
.with_evm_config(evm_config)
.with_refund_handling(RefundHandling::NoRefund)
.with_fee_handling(FeeHandling::PayToProposer);
let mut test = SingleTransactionTestCase::new(
Arc::clone(&ALICE_SECRET_KEY),
Arc::clone(&BOB_SECRET_KEY),
Arc::clone(&CHARLIE_SECRET_KEY),
Some(config),
)
.await;
test.fixture
.run_until_consensus_in_era(ERA_ONE, ONE_MIN)
.await;

let recipient = evm::Address::new([0x56; 20]);
let deploy = signed_evm_create_transaction(
evm_config.chain_id,
0,
evm_one_wei_transfer_init_code(recipient),
);
let sender = deploy.from();
seed_evm_account(&mut test.fixture, sender, U512::from(EVM_INITIAL_BALANCE));
let (_hash, deploy_height, deploy_result) =
test.send_transaction(Transaction::from(deploy)).await;
let ExecutionResult::Evm(deploy_result) = deploy_result else {
panic!("expected EVM deploy execution result");
};
let contract = deploy_result
.receipt
.contract_address
.expect("EVM deployment should create contract");

let sender_balance_before = evm_balance(&mut test.fixture, sender, deploy_height);
let total_supply_before = test.get_total_supply(Some(deploy_height));
let call = signed_evm_call_transaction(evm_config.chain_id, 1, contract, 1, Vec::new());
let fee = call
.max_fee_amount(&evm_config)
.expect("maximum EVM fee should fit");
let (_hash, call_height, call_result) = test.send_transaction(Transaction::from(call)).await;
let ExecutionResult::Evm(call_result) = call_result else {
panic!("expected EVM call execution result");
};

assert_eq!(call_result.receipt.status, evm::ReceiptStatus::Success);
assert_eq!(
evm_balance(&mut test.fixture, sender, call_height),
sender_balance_before - fee - U512::one(),
);
assert_eq!(
evm_balance(&mut test.fixture, contract, call_height),
U512::zero()
);
assert_eq!(
evm_balance(&mut test.fixture, recipient, call_height),
U512::zero()
);
assert_eq!(
test.get_total_supply(Some(call_height)),
total_supply_before - U512::one(),
);
}

#[tokio::test]
async fn should_prelink_ed25519_proposer_coinbase_for_evm_execution() {
let evm_config = EvmConfig {
Expand Down
Loading