diff --git a/.gitignore b/.gitignore index b94d61e746..f25dcec4ac 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ node_modules .DS_Store .idea .vscode +.cursor cmd/node/data cmd/node/node solgen/go/ diff --git a/Makefile b/Makefile index 796391a0e6..815cbc2b1c 100644 --- a/Makefile +++ b/Makefile @@ -243,6 +243,11 @@ test-go-redis: test-go-deps gotestsum --format short-verbose --no-color=false -- -p 1 -run TestRedis ./system_tests/... ./arbnode/... -- --test_redis=redis://localhost:6379/0 @printf $(done) +.PHONY: test-go-gas-dimensions +test-go-gas-dimensions: test-go-deps + gotestsum --format short-verbose --no-color=false -- -timeout 120m ./system_tests/... -run "TestDim(Log|TxOp)" -tags gasdimensionstest + @printf $(done) + .PHONY: test-gen-proofs test-gen-proofs: \ $(arbitrator_test_wasms) \ @@ -596,6 +601,7 @@ contracts/test/prover/proofs/%.json: $(arbitrator_cases)/%.wasm $(prover_bin) yarn --cwd safe-smart-account build yarn --cwd contracts build yarn --cwd contracts build:forge:yul + yarn --cwd contracts build:forge:gas-dimensions yarn --cwd contracts-legacy build yarn --cwd contracts-legacy build:forge:yul @touch $@ diff --git a/contracts b/contracts index 601afc77e5..fb051b20ac 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 601afc77e5b2a2dfd6f7075f2f3a48989c0be740 +Subproject commit fb051b20ac4580475c01be84932ce60f0468846d diff --git a/go-ethereum b/go-ethereum index 25fc5f0842..329f31d96c 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 25fc5f0842584e72455e4d60a61f035623b1aba0 +Subproject commit 329f31d96c80d8cda36241dddbbacc7a98277b03 diff --git a/solgen/gen.go b/solgen/gen.go index 506e229eca..7415800fa9 100644 --- a/solgen/gen.go +++ b/solgen/gen.go @@ -157,6 +157,34 @@ func main() { }) } + gasDimensionsFilePaths, err := filepath.Glob(filepath.Join(parent, "contracts", "out", "gas-dimensions", "*.sol", "*.json")) + if err != nil { + log.Fatal(err) + } + gasDimensionsModInfo := modules["gas_dimensionsgen"] + if gasDimensionsModInfo == nil { + gasDimensionsModInfo = &moduleInfo{} + modules["gas_dimensionsgen"] = gasDimensionsModInfo + } + for _, path := range gasDimensionsFilePaths { + _, file := filepath.Split(path) + name := file[:len(file)-5] + + data, err := os.ReadFile(path) + if err != nil { + log.Fatal("could not read", path, "for contract", name, err) + } + artifact := FoundryArtifact{} + if err := json.Unmarshal(data, &artifact); err != nil { + log.Fatal("failed to parse contract", name, err) + } + gasDimensionsModInfo.addArtifact(HardHatArtifact{ + ContractName: name, + Abi: artifact.Abi, + Bytecode: artifact.Bytecode.Object, + }) + } + // add upgrade executor module which is not compiled locally, but imported from 'nitro-contracts' depedencies upgExecutorPath := filepath.Join(parent, "contracts", "node_modules", "@offchainlabs", "upgrade-executor", "build", "contracts", "src", "UpgradeExecutor.sol", "UpgradeExecutor.json") _, err = os.Stat(upgExecutorPath) diff --git a/system_tests/gas_dim_log_a_common_test.go b/system_tests/gas_dim_log_a_common_test.go new file mode 100644 index 0000000000..03c651cf4d --- /dev/null +++ b/system_tests/gas_dim_log_a_common_test.go @@ -0,0 +1,314 @@ +package arbtest + +import ( + "context" + "encoding/json" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/tracers/native" + "github.com/ethereum/go-ethereum/params" + + "github.com/offchainlabs/nitro/solgen/go/gas_dimensionsgen" +) + +type DimensionLogRes = native.DimensionLogRes +type TraceResult = native.ExecutionResult + +const ( + ColdMinusWarmAccountAccessCost = params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 + ColdMinusWarmSloadCost = params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929 +) + +// ######################################################################################################### +// ######################################################################################################### +// REGULAR COMPUTATION OPCODES (ADD, SWAP, ETC) +// ######################################################################################################### +// ######################################################################################################### + +// Run a test where we set up an L2, then send a transaction +// that only has computation-only opcodes. Then call debug_traceTransaction +// with the txGasDimensionLogger tracer. +// +// we expect in this case to get back a json response, with the gas dimension logs +// containing only the computation-only opcodes and that the gas in the computation +// only opcodes is equal to the OneDimensionalGasCost. +func TestDimLogComputationOnlyOpcodes(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCounter) + _, receipt := callOnContract(t, builder, auth, contract.NoSpecials) + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + + // Validate each log entry + for i, log := range traceResult.DimensionLogs { + // Basic field validation + if log.Op == "" { + t.Errorf("Log entry %d: Expected non-empty opcode", i) + } + if log.Depth < 1 { + t.Errorf("Log entry %d: Expected depth >= 1, got %d", i, log.Depth) + } + + // Check that OneDimensionalGasCost equals Computation for computation-only opcodes + if log.OneDimensionalGasCost != log.Computation { + t.Errorf("Log entry %d: For computation-only opcode %s pc %d, expected OneDimensionalGasCost (%d) to equal Computation (%d): %v", + i, log.Op, log.Pc, log.OneDimensionalGasCost, log.Computation, log) + } + // check that there are only computation-only opcodes + if log.StateAccess != 0 || log.StateGrowth != 0 || log.HistoryGrowth != 0 { + t.Errorf("Log entry %d: For computation-only opcode %s pc %d, expected StateAccess (%d), StateGrowth (%d), HistoryGrowth (%d) to be 0: %v", + i, log.Op, log.Pc, log.StateAccess, log.StateGrowth, log.HistoryGrowth, log) + } + + // Validate error field + if log.Err != nil { + t.Errorf("Log entry %d: Unexpected error: %v", i, log.Err) + } + } +} + +// ######################################################################################################### +// ######################################################################################################### +// HELPER FUNCTIONS +// ######################################################################################################### +// ######################################################################################################### + +// common setup for all gas_dimension_logger tests +func gasDimensionTestSetup(t *testing.T) ( + ctx context.Context, + cancel context.CancelFunc, + builder *NodeBuilder, + auth bind.TransactOpts, + cleanup func(), +) { + t.Helper() + ctx, cancel = context.WithCancel(context.Background()) + builder = NewNodeBuilder(ctx).DefaultConfig(t, true) + builder.execConfig.Caching.Archive = true + // For now Archive node should use HashScheme + builder.execConfig.Caching.StateScheme = rawdb.HashScheme + cleanup = builder.Build(t) + auth = builder.L2Info.GetDefaultTransactOpts("Owner", ctx) + return ctx, cancel, builder, auth, cleanup +} + +// deploy the contract we want to deploy for this test +// wait for it to be included +func deployGasDimensionTestContract[C any]( + t *testing.T, + builder *NodeBuilder, + auth bind.TransactOpts, + deployFunc func(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, C, error), +) ( + address common.Address, + contract C, +) { + t.Helper() + address, tx, contract, err := deployFunc( + &auth, // Transaction options + builder.L2.Client, // Ethereum client + ) + Require(t, err) + + // 3. Wait for deployment to succeed + _, err = builder.L2.EnsureTxSucceeded(tx) + Require(t, err) + + return address, contract +} + +// call whatever test function is required for the test on the contract +func callOnContract[F func(auth *bind.TransactOpts) (*types.Transaction, error)]( + t *testing.T, + builder *NodeBuilder, + auth bind.TransactOpts, + testFunc F, +) (tx *types.Transaction, receipt *types.Receipt) { + t.Helper() + tx, err := testFunc(&auth) // For write operations + Require(t, err) + receipt, err = builder.L2.EnsureTxSucceeded(tx) + Require(t, err) + return tx, receipt +} + +// call whatever test function is required for the test on the contract +// pass in the argument provided to the test function call as its first argument +func callOnContractWithOneArg[A any, F func(auth *bind.TransactOpts, arg1 A) (*types.Transaction, error)]( + t *testing.T, + builder *NodeBuilder, + auth bind.TransactOpts, + testFunc F, + arg1 A, +) (receipt *types.Receipt) { + t.Helper() + tx, err := testFunc(&auth, arg1) + Require(t, err) + receipt, err = builder.L2.EnsureTxSucceeded(tx) + Require(t, err) + return receipt +} + +// call debug_traceTransaction with txGasDimensionLogger tracer +// do very light sanity checks on the result +func callDebugTraceTransactionWithLogger( + t *testing.T, + ctx context.Context, + builder *NodeBuilder, + txHash common.Hash, +) TraceResult { + t.Helper() + // Call debug_traceTransaction with txGasDimensionLogger tracer + rpcClient := builder.L2.ConsensusNode.Stack.Attach() + var result json.RawMessage + err := rpcClient.CallContext(ctx, &result, "debug_traceTransaction", txHash, map[string]interface{}{ + "tracer": "txGasDimensionLogger", + }) + Require(t, err) + + // Parse the result + var traceResult TraceResult + if err := json.Unmarshal(result, &traceResult); err != nil { + Fatal(t, err) + } + + // Validate basic structure + if traceResult.GasUsed == 0 { + Fatal(t, "Expected non-zero gas usage") + } + if traceResult.GasUsedForL1 == 0 { + Fatal(t, "Expected non-zero gas usage for L1") + } + if traceResult.GasUsedForL2 == 0 { + Fatal(t, "Expected non-zero gas usage for L2") + } + if traceResult.IntrinsicGas == 0 { + Fatal(t, "Expected non-zero intrinsic gas") + } + if traceResult.Failed { + Fatal(t, "Transaction should not have failed") + } + txHashHex := txHash.Hex() + if traceResult.TxHash != txHashHex { + Fatal(t, "Expected txHash %s, got %s", txHashHex, traceResult.TxHash) + } + if len(traceResult.DimensionLogs) == 0 { + Fatal(t, "Expected non-empty dimension logs") + } + return traceResult +} + +// get dimension log at position index of that opcode +// desiredIndex is 0-indexed +func getSpecificDimensionLogAtIndex( + t *testing.T, + dimensionLogs []DimensionLogRes, + expectedOpcode string, + expectedCount uint64, + desiredIndex uint64, +) ( + specificDimensionLog *DimensionLogRes, +) { + t.Helper() + specificDimensionLog = nil + var observedOpcodeCount uint64 = 0 + + for i, log := range dimensionLogs { + // Basic field validation + if log.Op == "" { + Fatal(t, "Log entry ", i, " Expected non-empty opcode") + } + if log.Depth < 1 { + Fatal(t, "Log entry ", i, " Expected depth >= 1, got", log.Depth) + } + if log.Err != nil { + Fatal(t, "Log entry ", i, " Unexpected error:", log.Err) + } + if log.Op == expectedOpcode { + if observedOpcodeCount == desiredIndex { + specificDimensionLog = &log + } + observedOpcodeCount++ + } + } + if observedOpcodeCount != expectedCount { + Fatal(t, "Expected ", expectedCount, " ", expectedOpcode, " got ", observedOpcodeCount) + } + if specificDimensionLog == nil { + Fatal(t, "Expected to find log at index ", desiredIndex, " of ", expectedOpcode, " got nil") + } + return specificDimensionLog +} + +// highlight one specific dimension log you want to get out of the +// dimension logs and return it. Make some basic field validation checks on the +// log while you iterate through it. +func getSpecificDimensionLog(t *testing.T, dimensionLogs []DimensionLogRes, expectedOpcode string) ( + specificDimensionLog *DimensionLogRes, +) { + t.Helper() + return getSpecificDimensionLogAtIndex(t, dimensionLogs, expectedOpcode, 1, 0) +} + +// for the sstore multiple tests, we need to get the second sstore in the transaction +// but there should only ever be two sstores in the transaction. +func getLastOfTwoDimensionLogs(t *testing.T, dimensionLogs []DimensionLogRes, expectedOpcode string) ( + specificDimensionLog *DimensionLogRes, +) { + t.Helper() + return getSpecificDimensionLogAtIndex(t, dimensionLogs, expectedOpcode, 2, 1) +} + +// just to reduce visual clutter in parameters +type ExpectedGasCosts struct { + OneDimensionalGasCost uint64 + Computation uint64 + StateAccess uint64 + StateGrowth uint64 + HistoryGrowth uint64 + StateGrowthRefund int64 + ChildExecutionCost uint64 +} + +// checks that all of the fields of the expected and actual dimension logs are equal +func checkGasDimensionsMatch(t *testing.T, expected ExpectedGasCosts, actual *DimensionLogRes) { + t.Helper() + if actual.Computation != expected.Computation { + Fatal(t, "Expected Computation ", expected.Computation, " got ", actual.Computation, " actual: ", actual.DebugString()) + } + if actual.StateAccess != expected.StateAccess { + Fatal(t, "Expected StateAccess ", expected.StateAccess, " got ", actual.StateAccess, " actual: ", actual.DebugString()) + } + if actual.StateGrowth != expected.StateGrowth { + Fatal(t, "Expected StateGrowth ", expected.StateGrowth, " got ", actual.StateGrowth, " actual: ", actual.DebugString()) + } + if actual.HistoryGrowth != expected.HistoryGrowth { + Fatal(t, "Expected HistoryGrowth ", expected.HistoryGrowth, " got ", actual.HistoryGrowth, " actual: ", actual.DebugString()) + } + if actual.StateGrowthRefund != expected.StateGrowthRefund { + Fatal(t, "Expected StateGrowthRefund ", expected.StateGrowthRefund, " got ", actual.StateGrowthRefund, " actual: ", actual.DebugString()) + } + if actual.OneDimensionalGasCost != expected.OneDimensionalGasCost { + Fatal(t, "Expected OneDimensionalGasCost ", expected.OneDimensionalGasCost, " got ", actual.OneDimensionalGasCost, " actual: ", actual.DebugString()) + } + if actual.ChildExecutionCost != expected.ChildExecutionCost { + Fatal(t, "Expected ChildExecutionCost ", expected.ChildExecutionCost, " got ", actual.ChildExecutionCost, " actual: ", actual.DebugString()) + } +} + +// check that the one dimensional gas cost is equal to the sum of the other gas dimensions +func checkGasDimensionsEqualOneDimensionalGas( + t *testing.T, + l *DimensionLogRes, +) { + t.Helper() + if l.OneDimensionalGasCost != l.Computation+l.StateAccess+l.StateGrowth+l.HistoryGrowth { + Fatal(t, "Expected OneDimensionalGasCost to equal sum of gas dimensions", l.DebugString()) + } +} diff --git a/system_tests/gas_dim_log_call_test.go b/system_tests/gas_dim_log_call_test.go new file mode 100644 index 0000000000..5604e104f9 --- /dev/null +++ b/system_tests/gas_dim_log_call_test.go @@ -0,0 +1,1790 @@ +package arbtest + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" + + "github.com/offchainlabs/nitro/solgen/go/gas_dimensionsgen" +) + +// this file tests the opcodes that are able to do write-operation CALLs +// looking for STATICCALL and DELEGATECALL? check out gas_dim_log_read_call_test.go + +// ######################################################################################################### +// ######################################################################################################### +// CALL and CALLCODE +// ######################################################################################################### +// ######################################################################################################### +// +// CALL and CALLCODE have many permutations /axes of variation +// +// Warm vs Cold (in the access list) +// Paying vs NoTransfer (is ether value being sent with this call?) +// Contract vs NoCode (is there code at the target address that we are calling to? or is it an EOA?) +// Virgin vs Funded (has the target address ever been seen before / is the account already in the accounts tree?) +// MemExpansion or MemUnchanged (memory expansion or not) + +// ######################################################################################################### +// ######################################################################################################### +// CALL +// ######################################################################################################### +// ######################################################################################################### + +// Perform a CALL from caller to callee where: +// callee is cold in the access list at the time of the call +// no ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee is virgin (has never been seen before on the network) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallColdNoTransferNoCodeVirginMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemUnchanged, noCodeVirginAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is cold in the access list at the time of the call +// no ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee is virgin (has never been seen before on the network) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallColdNoTransferNoCodeVirginMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemExpansion, noCodeVirginAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + expectedMemExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is cold in the access list at the time of the call +// no ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee has been funded before (someone sent it money in the past) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallColdNoTransferNoCodeFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // prefund callee address + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemUnchanged, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is cold in the access list at the time of the call +// no ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee has been funded before (someone sent it money in the past) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallColdNoTransferNoCodeFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // prefund callee address + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemExpansion, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + expectedMemExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is cold in the access list at the time of the call +// no ether is being sent with the call +// the callee has contract code +// the callee has been funded before (someone sent it money in the past) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallColdNoTransferContractFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallee) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemUnchanged, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + var expectedChildGasExecutionCost uint64 = 22468 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is cold in the access list at the time of the call +// no ether is being sent with the call +// the callee has contract code +// the callee has been funded before (someone sent it money in the past) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallColdNoTransferContractFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallee) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemExpansion, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 22468 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + expectedMemExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is cold in the access list at the time of the call +// ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee is virgin (has never been seen before on the network) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallColdPayingNoCodeVirginMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + // prefund the caller with some funds + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemUnchanged, noCodeVirginAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + params.CallValueTransferGas - params.CallStipend + params.CallNewAccountGas, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: ColdMinusWarmAccountAccessCost + params.CallValueTransferGas - params.CallStipend, + StateGrowth: params.CallNewAccountGas, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is cold in the access list at the time of the call +// ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee is virgin (has never been seen before on the network) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallColdPayingNoCodeVirginMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + // prefund the caller with some funds + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemExpansion, noCodeVirginAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + params.CallValueTransferGas - params.CallStipend + params.CallNewAccountGas + expectedMemExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: ColdMinusWarmAccountAccessCost + params.CallValueTransferGas - params.CallStipend, + StateGrowth: params.CallNewAccountGas, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is cold in the access list at the time of the call +// ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee has been funded before (someone sent it money in the past) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallColdPayingNoCodeFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + // prefund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + noCodeFundedAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, _ = builder.L2.TransferBalanceTo(t, "Owner", noCodeFundedAddress, big.NewInt(1e17), builder.L2Info) + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemUnchanged, noCodeFundedAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + params.CallValueTransferGas - params.CallStipend, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: ColdMinusWarmAccountAccessCost + params.CallValueTransferGas - params.CallStipend, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is cold in the access list at the time of the call +// ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee has been funded before (someone sent it money in the past) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallColdPayingNoCodeFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + // prefund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + noCodeFundedAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, _ = builder.L2.TransferBalanceTo(t, "Owner", noCodeFundedAddress, big.NewInt(1e17), builder.L2Info) + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemExpansion, noCodeFundedAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + params.CallValueTransferGas - params.CallStipend + expectedMemExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: ColdMinusWarmAccountAccessCost + params.CallValueTransferGas - params.CallStipend, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is cold in the access list at the time of the call +// ether is being sent with the call +// the callee has contract code +// the callee has been funded before (someone sent it money in the past) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallColdPayingContractFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallee) + + // fund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // fund the callee address with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemUnchanged, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + var expectedChildGasExecutionCost uint64 = 22468 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + params.CallValueTransferGas - params.CallStipend, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: ColdMinusWarmAccountAccessCost + params.CallValueTransferGas - params.CallStipend, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is cold in the access list at the time of the call +// ether is being sent with the call +// the callee has contract code +// the callee has been funded before (someone sent it money in the past) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallColdPayingContractFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallee) + + // fund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // fund the callee address with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemExpansion, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 22468 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + expectedMemExpansionCost + params.CallValueTransferGas - params.CallStipend, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: ColdMinusWarmAccountAccessCost + params.CallValueTransferGas - params.CallStipend, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is warm in the access list at the time of the call +// no ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee is virgin (has never been seen before on the network) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallWarmNoTransferNoCodeVirginMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemUnchanged, noCodeVirginAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is warm in the access list at the time of the call +// no ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee is virgin (has never been seen before on the network) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallWarmNoTransferNoCodeVirginMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemExpansion, noCodeVirginAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is warm in the access list at the time of the call +// no ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee has been funded before (someone sent it money in the past) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallWarmNoTransferNoCodeFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // prefund callee address + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemUnchanged, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is warm in the access list at the time of the call +// no ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee has been funded before (someone sent it money in the past) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallWarmNoTransferNoCodeFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // prefund callee address + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemExpansion, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is warm in the access list at the time of the call +// no ether is being sent with the call +// the callee has contract code +// the callee has been funded before (someone sent it money in the past) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallWarmNoTransferContractFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallee) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemUnchanged, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + var expectedChildGasExecutionCost uint64 = 22468 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is warm in the access list at the time of the call +// no ether is being sent with the call +// the callee has contract code +// the callee has been funded before (someone sent it money in the past) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallWarmNoTransferContractFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallee) + + // fund the callee address with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemExpansion, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 22468 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is warm in the access list at the time of the call +// ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee is virgin (has never been seen before on the network) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallWarmPayingNoCodeVirginMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + // prefund the caller with some funds + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemUnchanged, noCodeVirginAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + params.CallValueTransferGas - params.CallStipend + params.CallNewAccountGas, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: params.CallValueTransferGas - params.CallStipend, + StateGrowth: params.CallNewAccountGas, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is warm in the access list at the time of the call +// ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee is virgin (has never been seen before on the network) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallWarmPayingNoCodeVirginMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + // prefund the caller with some funds + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemExpansion, noCodeVirginAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + params.CallValueTransferGas - params.CallStipend + params.CallNewAccountGas + expectedMemExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: params.CallValueTransferGas - params.CallStipend, + StateGrowth: params.CallNewAccountGas, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is warm in the access list at the time of the call +// ether value is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee has been funded before (someone sent it money in the past) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallWarmPayingNoCodeFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + + noCodeFundedAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + // prefund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // prefund the callee with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", noCodeFundedAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemUnchanged, noCodeFundedAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + params.CallValueTransferGas - params.CallStipend, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: params.CallValueTransferGas - params.CallStipend, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is warm in the access list at the time of the call +// ether value is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee has been funded before (someone sent it money in the past) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallWarmPayingNoCodeFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + + noCodeFundedAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + // prefund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // prefund the callee with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", noCodeFundedAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemExpansion, noCodeFundedAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + params.CallValueTransferGas - params.CallStipend + expectedMemExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: params.CallValueTransferGas - params.CallStipend, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is warm in the access list at the time of the call +// ether value is being sent with the call +// the callee has contract code +// the callee has been funded before (someone sent it money in the past) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallWarmPayingContractFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallee) + + // prefund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // prefund the callee with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemUnchanged, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + var expectedChildGasExecutionCost uint64 = 22468 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + params.CallValueTransferGas - params.CallStipend, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: params.CallValueTransferGas - params.CallStipend, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALL from caller to callee where: +// callee is warm in the access list at the time of the call +// ether value is being sent with the call +// the callee has contract code +// the callee has been funded before (someone sent it money in the past) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallWarmPayingContractFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallee) + + // fund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // fund the callee address with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemExpansion, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 22468 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost + params.CallValueTransferGas - params.CallStipend, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: params.CallValueTransferGas - params.CallStipend, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// ######################################################################################################### +// ######################################################################################################### +// CALLCODE +// ######################################################################################################### +// ######################################################################################################### +// CALLCODE is deprecated and also weird. +// It operates identically to DELEGATECALL but allows you to send funds with the call +// if you send those funds, it will send them to the library address rather than yourself. +// So it has a positive_value_cost just like CALL, +// but it does NOT have the positive_value_to_new_account cost (params.CallNewAccountGas) +// so all of the test cases for CALLCODE are identical to CALL, EXCEPT FOR +// the NoCodeVirgin test cases, where there is no cost to positive_value_to_new_account +// (which tbh sounds like an EVM mispricing bug) + +// Perform a CALLCODE from caller to callee where: +// callee is cold in the access list at the time of the call +// no ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee is virgin (has never been seen before on the network) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallCodeColdNoTransferNoCodeVirginMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemUnchanged, noCodeVirginAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is cold in the access list at the time of the call +// no ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee is virgin (has never been seen before on the network) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallCodeColdNoTransferNoCodeVirginMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemExpansion, noCodeVirginAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + expectedMemExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is cold in the access list at the time of the call +// no ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee has been funded before (someone sent it money in the past) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallCodeColdNoTransferNoCodeFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // prefund callee address + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemUnchanged, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is cold in the access list at the time of the call +// no ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee has been funded before (someone sent it money in the past) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallCodeColdNoTransferNoCodeFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // prefund callee address + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemExpansion, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + expectedMemExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is cold in the access list at the time of the call +// no ether is being sent with the call +// the callee has contract code +// the callee has been funded before (someone sent it money in the past) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallCodeColdNoTransferContractFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCodee) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemUnchanged, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + var expectedChildGasExecutionCost uint64 = 490 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is cold in the access list at the time of the call +// no ether is being sent with the call +// the callee has contract code +// the callee has been funded before (someone sent it money in the past) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallCodeColdNoTransferContractFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCodee) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemExpansion, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 490 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + expectedMemExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is cold in the access list at the time of the call +// ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee is virgin (has never been seen before on the network) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallCodeColdPayingNoCodeVirginMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + // prefund the caller with some funds + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemUnchanged, noCodeVirginAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + params.CallValueTransferGas - params.CallStipend, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: ColdMinusWarmAccountAccessCost + params.CallValueTransferGas - params.CallStipend, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is cold in the access list at the time of the call +// ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee is virgin (has never been seen before on the network) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallCodeColdPayingNoCodeVirginMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + // prefund the caller with some funds + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemExpansion, noCodeVirginAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + params.CallValueTransferGas - params.CallStipend + expectedMemExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: ColdMinusWarmAccountAccessCost + params.CallValueTransferGas - params.CallStipend, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is cold in the access list at the time of the call +// ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee has been funded before (someone sent it money in the past) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallCodeColdPayingNoCodeFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + // prefund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + noCodeFundedAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, _ = builder.L2.TransferBalanceTo(t, "Owner", noCodeFundedAddress, big.NewInt(1e17), builder.L2Info) + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemUnchanged, noCodeFundedAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + params.CallValueTransferGas - params.CallStipend, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: ColdMinusWarmAccountAccessCost + params.CallValueTransferGas - params.CallStipend, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is cold in the access list at the time of the call +// ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee has been funded before (someone sent it money in the past) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallCodeColdPayingNoCodeFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + // prefund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + noCodeFundedAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, _ = builder.L2.TransferBalanceTo(t, "Owner", noCodeFundedAddress, big.NewInt(1e17), builder.L2Info) + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemExpansion, noCodeFundedAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + params.CallValueTransferGas - params.CallStipend + expectedMemExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: ColdMinusWarmAccountAccessCost + params.CallValueTransferGas - params.CallStipend, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is cold in the access list at the time of the call +// ether is being sent with the call +// the callee has contract code +// the callee has been funded before (someone sent it money in the past) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallCodeColdPayingContractFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCodee) + + // fund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // fund the callee address with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemUnchanged, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + var expectedChildGasExecutionCost uint64 = 490 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + params.CallValueTransferGas - params.CallStipend, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: ColdMinusWarmAccountAccessCost + params.CallValueTransferGas - params.CallStipend, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is cold in the access list at the time of the call +// ether is being sent with the call +// the callee has contract code +// the callee has been funded before (someone sent it money in the past) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallCodeColdPayingContractFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCodee) + + // fund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // fund the callee address with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemExpansion, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 490 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + expectedMemExpansionCost + params.CallValueTransferGas - params.CallStipend, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: ColdMinusWarmAccountAccessCost + params.CallValueTransferGas - params.CallStipend, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is warm in the access list at the time of the call +// no ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee is virgin (has never been seen before on the network) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallCodeWarmNoTransferNoCodeVirginMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemUnchanged, noCodeVirginAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is warm in the access list at the time of the call +// no ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee is virgin (has never been seen before on the network) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallCodeWarmNoTransferNoCodeVirginMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemExpansion, noCodeVirginAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is warm in the access list at the time of the call +// no ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee has been funded before (someone sent it money in the past) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallCodeWarmNoTransferNoCodeFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // prefund callee address + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemUnchanged, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is warm in the access list at the time of the call +// no ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee has been funded before (someone sent it money in the past) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallCodeWarmNoTransferNoCodeFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // prefund callee address + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemExpansion, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is warm in the access list at the time of the call +// no ether is being sent with the call +// the callee has contract code +// the callee has been funded before (someone sent it money in the past) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallCodeWarmNoTransferContractFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCodee) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemUnchanged, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + var expectedChildGasExecutionCost uint64 = 490 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is warm in the access list at the time of the call +// no ether is being sent with the call +// the callee has contract code +// the callee has been funded before (someone sent it money in the past) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallCodeWarmNoTransferContractFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCodee) + + // fund the callee address with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemExpansion, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 490 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is warm in the access list at the time of the call +// ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee is virgin (has never been seen before on the network) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallCodeWarmPayingNoCodeVirginMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + // prefund the caller with some funds + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemUnchanged, noCodeVirginAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + params.CallValueTransferGas - params.CallStipend, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: params.CallValueTransferGas - params.CallStipend, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is warm in the access list at the time of the call +// ether is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee is virgin (has never been seen before on the network) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallCodeWarmPayingNoCodeVirginMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + // prefund the caller with some funds + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemExpansion, noCodeVirginAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + params.CallValueTransferGas - params.CallStipend + expectedMemExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: params.CallValueTransferGas - params.CallStipend, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is warm in the access list at the time of the call +// ether value is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee has been funded before (someone sent it money in the past) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallCodeWarmPayingNoCodeFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + + noCodeFundedAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + // prefund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // prefund the callee with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", noCodeFundedAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemUnchanged, noCodeFundedAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + params.CallValueTransferGas - params.CallStipend, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: params.CallValueTransferGas - params.CallStipend, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is warm in the access list at the time of the call +// ether value is being sent with the call +// the callee has no code (either it is an EOA, or has never been seen on the network) +// the callee has been funded before (someone sent it money in the past) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallCodeWarmPayingNoCodeFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + + noCodeFundedAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + // prefund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // prefund the callee with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", noCodeFundedAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemExpansion, noCodeFundedAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + params.CallValueTransferGas - params.CallStipend + expectedMemExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: params.CallValueTransferGas - params.CallStipend, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is warm in the access list at the time of the call +// ether value is being sent with the call +// the callee has contract code +// the callee has been funded before (someone sent it money in the past) +// there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) +func TestDimLogCallCodeWarmPayingContractFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCodee) + + // prefund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // prefund the callee with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemUnchanged, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + var expectedChildGasExecutionCost uint64 = 490 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + params.CallValueTransferGas - params.CallStipend, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: params.CallValueTransferGas - params.CallStipend, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} + +// Perform a CALLCODE from caller to callee where: +// callee is warm in the access list at the time of the call +// ether value is being sent with the call +// the callee has contract code +// the callee has been funded before (someone sent it money in the past) +// there is memory expansion, the CALL writes to memory outside the bounds of the original memory +func TestDimLogCallCodeWarmPayingContractFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCodee) + + // fund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // fund the callee address with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemExpansion, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALLCODE") + var expectedMemExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 490 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost + params.CallValueTransferGas - params.CallStipend, + Computation: params.WarmStorageReadCostEIP2929 + expectedMemExpansionCost, + StateAccess: params.CallValueTransferGas - params.CallStipend, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} diff --git a/system_tests/gas_dim_log_create_test.go b/system_tests/gas_dim_log_create_test.go new file mode 100644 index 0000000000..6537a77bb2 --- /dev/null +++ b/system_tests/gas_dim_log_create_test.go @@ -0,0 +1,411 @@ +package arbtest + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/params" + + "github.com/offchainlabs/nitro/solgen/go/gas_dimensionsgen" +) + +// ######################################################################################################### +// ######################################################################################################### +// CREATE & CREATE2 +// ######################################################################################################### +// ######################################################################################################### +// CREATE and CREATE2 have permutations: +// Paying vs NoTransfer (is ether value being sent with this call?) +// MemExpansion vs MemUnchanged (does the creation write to new additional memory?) + +// ######################################################################################################### +// CREATE +// ######################################################################################################### + +// in this test, we do a CREATE of a new contract with no transfer of value +// and the creation does not write to new additional memory +// Unfortunately it's really hard to show create without using magic numbers +// from staring at debug traces, so you'll just have to trust the magic values below +// we found that the code execution cost for this particular contract is 22477 +// that the deployed contract code is 181 bytes +// and that the init code is 359 bytes +// +// So we expect the one dimensional gas to be +// 32000 for the static cost +// + ((359+31)/32) * 2 for the init code cost, +// + 0 for the memory expansion +// + 22477 for the deployed code execution cost +// + 200 * 181 for the code deposit cost +// for compute we expect: +// ((359+31)/32) * 2 for the init code cost, +// + 0 for the memory expansion +// + (32000 - 25000) / 2 for static cost to assign to compute +// the state access cost to be 0 +// the state growth cost to be: +// 25000 for the cost of a "new account" (this is taken from the CALL opcode's amount on this) +// + 200 * 181 for the cost of the deployed code size +// + (32000 - 25000) / 2 for static cost to assign to state growth +// the history growth to be 0 +// the state growth refund to be 0 +func TestDimLogCreateNoTransferMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, creator := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCreator) + + _, receipt := callOnContract(t, builder, auth, creator.CreateNoTransferMemUnchanged) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + createLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CREATE") + + var initCodeSize uint64 = 359 + var deployedCodeSize uint64 = 181 + var expectedCallExecutionCost uint64 = 22477 + var expectedMemoryExpansionCost uint64 = 0 + + var minimumWordSize uint64 = (initCodeSize + 31) / 32 + + var expectedInitCodeCost uint64 = 2 * minimumWordSize + var expectedCodeDepositCost uint64 = 200 * deployedCodeSize + + var expectedStaticCostAssignedToCompute uint64 = (params.CreateGas - params.CallNewAccountGas) / 2 + var expectedStaticCostAssignedToStateGrowth uint64 = params.CreateGas - params.CallNewAccountGas - expectedStaticCostAssignedToCompute + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.CreateGas + expectedInitCodeCost + expectedMemoryExpansionCost + expectedCodeDepositCost, + Computation: expectedInitCodeCost + expectedMemoryExpansionCost + expectedStaticCostAssignedToCompute, + StateAccess: 0, + StateGrowth: expectedStaticCostAssignedToStateGrowth + params.CallNewAccountGas + expectedCodeDepositCost, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedCallExecutionCost, + } + checkGasDimensionsMatch(t, expected, createLog) + checkGasDimensionsEqualOneDimensionalGas(t, createLog) +} + +// in this test, we do a CREATE of a new contract with no transfer of value +// and the creation writes to new additional memory, causing memory expansion +func TestDimLogCreateNoTransferMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, creator := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCreator) + + _, receipt := callOnContract(t, builder, auth, creator.CreateNoTransferMemExpansion) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + createLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CREATE") + + var initCodeSize uint64 = 416 + var deployedCodeSize uint64 = 181 + var expectedCallExecutionCost uint64 = 2586 + var expectedMemoryExpansionCost uint64 = 6 + + var minimumWordSize uint64 = (initCodeSize + 31) / 32 + + var expectedInitCodeCost uint64 = 2 * minimumWordSize + var expectedCodeDepositCost uint64 = 200 * deployedCodeSize + + var expectedStaticCostAssignedToCompute uint64 = (params.CreateGas - params.CallNewAccountGas) / 2 + var expectedStaticCostAssignedToStateGrowth uint64 = params.CreateGas - params.CallNewAccountGas - expectedStaticCostAssignedToCompute + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.CreateGas + expectedInitCodeCost + expectedMemoryExpansionCost + expectedCodeDepositCost, + Computation: expectedInitCodeCost + expectedMemoryExpansionCost + expectedStaticCostAssignedToCompute, + StateAccess: 0, + StateGrowth: expectedStaticCostAssignedToStateGrowth + params.CallNewAccountGas + expectedCodeDepositCost, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedCallExecutionCost, + } + checkGasDimensionsMatch(t, expected, createLog) + checkGasDimensionsEqualOneDimensionalGas(t, createLog) +} + +// in this test, we do a CREATE to a new contract with a transfer of ether +// and the creation does not write to new additional memory +// +// The gas costs are identical to the case with NoTransfer, see +// the comments for TestDimLogCreateNoTransferMemUnchanged above +func TestDimLogCreatePayingMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + creatorAddress, creator := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCreator) + // transfer some eth to the creator contract + _, _ = builder.L2.TransferBalanceTo(t, "Owner", creatorAddress, big.NewInt(1e17), builder.L2Info) + + _, receipt := callOnContract(t, builder, auth, creator.CreatePayableMemUnchanged) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + createLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CREATE") + + var initCodeSize uint64 = 359 + var deployedCodeSize uint64 = 181 + var expectedCallExecutionCost uint64 = 22477 + var expectedMemoryExpansionCost uint64 = 0 + + var minimumWordSize uint64 = (initCodeSize + 31) / 32 + + var expectedInitCodeCost uint64 = 2 * minimumWordSize + var expectedCodeDepositCost uint64 = 200 * deployedCodeSize + + var expectedStaticCostAssignedToCompute uint64 = (params.CreateGas - params.CallNewAccountGas) / 2 + var expectedStaticCostAssignedToStateGrowth uint64 = params.CreateGas - params.CallNewAccountGas - expectedStaticCostAssignedToCompute + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.CreateGas + expectedInitCodeCost + expectedMemoryExpansionCost + expectedCodeDepositCost, + Computation: expectedInitCodeCost + expectedMemoryExpansionCost + expectedStaticCostAssignedToCompute, + StateAccess: 0, + StateGrowth: expectedStaticCostAssignedToStateGrowth + params.CallNewAccountGas + expectedCodeDepositCost, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedCallExecutionCost, + } + checkGasDimensionsMatch(t, expected, createLog) + checkGasDimensionsEqualOneDimensionalGas(t, createLog) +} + +// in this test, we do a CREATE of a new contract with transfer of value +// and the creation writes to new additional memory, causing memory expansion +func TestDimLogCreatePayingMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + creatorAddress, creator := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCreator) + // transfer some eth to the creator contract + _, _ = builder.L2.TransferBalanceTo(t, "Owner", creatorAddress, big.NewInt(1e17), builder.L2Info) + + _, receipt := callOnContract(t, builder, auth, creator.CreatePayableMemExpansion) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + createLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CREATE") + + var initCodeSize uint64 = 416 + var deployedCodeSize uint64 = 181 + var expectedCallExecutionCost uint64 = 2586 + var expectedMemoryExpansionCost uint64 = 6 + + var minimumWordSize uint64 = (initCodeSize + 31) / 32 + + var expectedInitCodeCost uint64 = 2 * minimumWordSize + var expectedCodeDepositCost uint64 = 200 * deployedCodeSize + + var expectedStaticCostAssignedToCompute uint64 = (params.CreateGas - params.CallNewAccountGas) / 2 + var expectedStaticCostAssignedToStateGrowth uint64 = params.CreateGas - params.CallNewAccountGas - expectedStaticCostAssignedToCompute + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.CreateGas + expectedInitCodeCost + expectedMemoryExpansionCost + expectedCodeDepositCost, + Computation: expectedInitCodeCost + expectedMemoryExpansionCost + expectedStaticCostAssignedToCompute, + StateAccess: 0, + StateGrowth: expectedStaticCostAssignedToStateGrowth + params.CallNewAccountGas + expectedCodeDepositCost, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedCallExecutionCost, + } + checkGasDimensionsMatch(t, expected, createLog) + checkGasDimensionsEqualOneDimensionalGas(t, createLog) +} + +// ######################################################################################################### +// CREATE2 +// ######################################################################################################### + +// in this test, we do a CREATE2 of a new contract with no transfer of value +// and the creation does not write to new additional memory +// Unfortunately it's really hard to show create without using magic numbers +// from staring at debug traces, so you'll just have to trust the magic values below +// we found that the code execution cost for this particular contract is 22477 +// that the deployed contract code is 181 bytes +// and that the init code is 359 bytes +// +// So we expect the one dimensional gas to be +// 32000 for the static cost +// + ((359+31)/32) * 2 for the init code cost, +// + 0 for the memory expansion +// + 22477 for the deployed code execution cost +// + 200 * 181 for the code deposit cost +// + 6 * ((359+31)/32) for the hash cost +// for compute we expect: +// ((359+31)/32) * 2 for the init code cost, +// + 0 for the memory expansion +// + (32000 - 25000) / 2 for static cost to assign to compute +// + 6 * ((359+31)/32) for the hash cost +// the state access cost to be 0 +// the state growth cost to be: +// 25000 for the cost of a "new account" (this is taken from the CALL opcode's amount on this) +// + 200 * 181 for the cost of the deployed code size +// + (32000 - 25000) / 2 for static cost to assign to state growth +// the history growth to be 0 +// the state growth refund to be 0 +func TestDimLogCreate2NoTransferMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, creator := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCreatorTwo) + + receipt := callOnContractWithOneArg(t, builder, auth, creator.CreateTwoNoTransferMemUnchanged, [32]byte{0x13, 0x37}) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + createLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CREATE2") + + var initCodeSize uint64 = 359 + var deployedCodeSize uint64 = 181 + var expectedCallExecutionCost uint64 = 22477 + var expectedMemoryExpansionCost uint64 = 0 + + var minimumWordSize uint64 = (initCodeSize + 31) / 32 + + var expectedInitCodeCost uint64 = 2 * minimumWordSize + var expectedCodeDepositCost uint64 = 200 * deployedCodeSize + var expectedHashCost uint64 = 6 * minimumWordSize + + var expectedStaticCostAssignedToCompute uint64 = (params.CreateGas - params.CallNewAccountGas) / 2 + var expectedStaticCostAssignedToStateGrowth uint64 = params.CreateGas - params.CallNewAccountGas - expectedStaticCostAssignedToCompute + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.CreateGas + expectedInitCodeCost + expectedMemoryExpansionCost + expectedCodeDepositCost + expectedHashCost, + Computation: expectedInitCodeCost + expectedMemoryExpansionCost + expectedStaticCostAssignedToCompute + expectedHashCost, + StateAccess: 0, + StateGrowth: expectedStaticCostAssignedToStateGrowth + params.CallNewAccountGas + expectedCodeDepositCost, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedCallExecutionCost, + } + checkGasDimensionsMatch(t, expected, createLog) + checkGasDimensionsEqualOneDimensionalGas(t, createLog) +} + +// in this test, we do a CREATE2 of a new contract with no transfer of value +// and the creation writes to new additional memory, causing memory expansion +func TestDimLogCreate2NoTransferMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, creator := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCreatorTwo) + + receipt := callOnContractWithOneArg(t, builder, auth, creator.CreateTwoNoTransferMemExpansion, [32]byte{0x13, 0x37}) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + createLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CREATE2") + + var initCodeSize uint64 = 416 + var deployedCodeSize uint64 = 181 + var expectedCallExecutionCost uint64 = 2586 + var expectedMemoryExpansionCost uint64 = 6 + + var minimumWordSize uint64 = (initCodeSize + 31) / 32 + + var expectedInitCodeCost uint64 = 2 * minimumWordSize + var expectedCodeDepositCost uint64 = 200 * deployedCodeSize + var expectedHashCost uint64 = 6 * minimumWordSize + + var expectedStaticCostAssignedToCompute uint64 = (params.CreateGas - params.CallNewAccountGas) / 2 + var expectedStaticCostAssignedToStateGrowth uint64 = params.CreateGas - params.CallNewAccountGas - expectedStaticCostAssignedToCompute + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.CreateGas + expectedInitCodeCost + expectedMemoryExpansionCost + expectedCodeDepositCost + expectedHashCost, + Computation: expectedInitCodeCost + expectedMemoryExpansionCost + expectedStaticCostAssignedToCompute + expectedHashCost, + StateAccess: 0, + StateGrowth: expectedStaticCostAssignedToStateGrowth + params.CallNewAccountGas + expectedCodeDepositCost, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedCallExecutionCost, + } + checkGasDimensionsMatch(t, expected, createLog) + checkGasDimensionsEqualOneDimensionalGas(t, createLog) +} + +// in this test, we do a CREATE2 of a new contract with transfer of value +// and the creation does not write to new additional memory +// +// The gas costs are identical to the case with NoTransfer, see +// the comments for TestDimLogCreateNoTransferMemUnchanged above +func TestDimLogCreate2PayingMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, creator := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCreatorTwo) + + receipt := callOnContractWithOneArg(t, builder, auth, creator.CreateTwoNoTransferMemUnchanged, [32]byte{0x13, 0x37}) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + createLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CREATE2") + + var initCodeSize uint64 = 359 + var deployedCodeSize uint64 = 181 + var expectedCallExecutionCost uint64 = 22477 + var expectedMemoryExpansionCost uint64 = 0 + + var minimumWordSize uint64 = (initCodeSize + 31) / 32 + + var expectedInitCodeCost uint64 = 2 * minimumWordSize + var expectedCodeDepositCost uint64 = 200 * deployedCodeSize + var expectedHashCost uint64 = 6 * minimumWordSize + + var expectedStaticCostAssignedToCompute uint64 = (params.CreateGas - params.CallNewAccountGas) / 2 + var expectedStaticCostAssignedToStateGrowth uint64 = params.CreateGas - params.CallNewAccountGas - expectedStaticCostAssignedToCompute + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.CreateGas + expectedInitCodeCost + expectedMemoryExpansionCost + expectedCodeDepositCost + expectedHashCost, + Computation: expectedInitCodeCost + expectedMemoryExpansionCost + expectedStaticCostAssignedToCompute + expectedHashCost, + StateAccess: 0, + StateGrowth: expectedStaticCostAssignedToStateGrowth + params.CallNewAccountGas + expectedCodeDepositCost, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedCallExecutionCost, + } + checkGasDimensionsMatch(t, expected, createLog) + checkGasDimensionsEqualOneDimensionalGas(t, createLog) +} + +// in this test, we do a CREATE2 of a new contract with transfer of value +// and the creation writes to new additional memory, causing memory expansion +func TestDimLogCreate2PayingMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + creatorAddress, creator := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCreatorTwo) + // transfer some eth to the creator contract + _, _ = builder.L2.TransferBalanceTo(t, "Owner", creatorAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, creator.CreateTwoPayableMemExpansion, [32]byte{0x13, 0x37}) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + createLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CREATE2") + + var initCodeSize uint64 = 416 + var deployedCodeSize uint64 = 181 + var expectedCallExecutionCost uint64 = 2586 + var expectedMemoryExpansionCost uint64 = 6 + + var minimumWordSize uint64 = (initCodeSize + 31) / 32 + + var expectedInitCodeCost uint64 = 2 * minimumWordSize + var expectedCodeDepositCost uint64 = 200 * deployedCodeSize + var expectedHashCost uint64 = 6 * minimumWordSize + + var expectedStaticCostAssignedToCompute uint64 = (params.CreateGas - params.CallNewAccountGas) / 2 + var expectedStaticCostAssignedToStateGrowth uint64 = params.CreateGas - params.CallNewAccountGas - expectedStaticCostAssignedToCompute + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.CreateGas + expectedInitCodeCost + expectedMemoryExpansionCost + expectedCodeDepositCost + expectedHashCost, + Computation: expectedInitCodeCost + expectedMemoryExpansionCost + expectedStaticCostAssignedToCompute + expectedHashCost, + StateAccess: 0, + StateGrowth: expectedStaticCostAssignedToStateGrowth + params.CallNewAccountGas + expectedCodeDepositCost, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedCallExecutionCost, + } + checkGasDimensionsMatch(t, expected, createLog) + checkGasDimensionsEqualOneDimensionalGas(t, createLog) +} diff --git a/system_tests/gas_dim_log_extcodecopy_test.go b/system_tests/gas_dim_log_extcodecopy_test.go new file mode 100644 index 0000000000..34b76fa98d --- /dev/null +++ b/system_tests/gas_dim_log_extcodecopy_test.go @@ -0,0 +1,229 @@ +package arbtest + +import ( + "testing" + + "github.com/ethereum/go-ethereum/params" + + "github.com/offchainlabs/nitro/solgen/go/gas_dimensionsgen" +) + +// ######################################################################################################### +// ######################################################################################################### +// EXTCODECOPY +// ######################################################################################################### +// ######################################################################################################### + +// EXTCODECOPY reads from state and copies code to memory +// for gas dimensions, we don't care about expanding memory, but +// we do care about the cost being correct +// +// EXTCODECOPY has two axes of variation: +// Warm vs Cold (in the access list) +// MemExpansion or MemUnchanged (memory expansion or not) +// +// EXTCODECOPY has three components to its gas cost: +// 1. minimum_word_size = (size + 31) / 32 +// 2. memory_expansion_cost +// 3. address_access_cost - the access set. +// gas for extcodecopy is 3 * minimum_word_size + memory_expansion_cost + address_access_cost +// 3*minimum_word_size is always state access +// +// Here is the blob of code for the contract that we are copying: +// "608060405234801561000f575f5ffd5b506004361061004a575f3560e01c8063", +// "3fb5c1cb1461004e578063822ec8611461006a5780638381f58a146100745780", +// "63d09de08a14610092575b5f5ffd5b6100686004803603810190610063919061", +// "011b565b61009c565b005b6100726100a5565b005b61007c6100b1565b604051", +// "6100899190610155565b60405180910390f35b61009a6100b6565b005b805f81", +// "90555050565b61696961133701602081f35b5f5481565b5f5f54905060018161", +// "00c8919061019b565b90505f5f54905080826100db919061019b565b5f819055", +// "505050565b5f5ffd5b5f819050919050565b6100fa816100e8565b8114610104", +// "575f5ffd5b50565b5f81359050610115816100f1565b92915050565b5f602082", +// "840312156101305761012f6100e4565b5b5f61013d84828501610107565b9150", +// "5092915050565b61014f816100e8565b82525050565b5f602082019050610168", +// "5f830184610146565b92915050565b7f4e487b71000000000000000000000000", +// "000000000000000000000000000000005f52601160045260245ffd5b5f6101a5", +// "826100e8565b91506101b0836100e8565b92508282019050808211156101c857", +// "6101c761016e565b5b9291505056fea264697066735822122056d73a5a32faf2", +// "0913b0a82eef9159812447f4e5e86362af90bcb20669ddf7bc64736f6c634300", +// "081c003300000000000000000000000000000000000000000000000000000000" +// +// observe that the code size is 516 bytes, and there are 17 256-bit (32 byte) +// long words of data in of this code thus, the minimum word size is 17 +var extCodeCopyWordSize uint64 = 17 + +// the minimum word cost is the minimum word size * 3, and it is always +// read-write state access since this cost is associated with the copying +var extCodeCopyMinimumWordCost uint64 = extCodeCopyWordSize * 3 + +// Above we show the contract code that is copied. +// the memory size at time of copy for all of the test cases +// is 704 bytes (22 words). +// In the memory expansion cases, we copy starting at offset 703 +// out of 704, forcing the memory to expand. It expands from +// 704 bytes to 1248 bytes, because the code size is 516 bytes +// (1219 bytes) which then gets pushed out to 39 words - 1248 bytes. +// +// the formula for memory expansion is: +// memory_size_word = (memory_byte_size + 31) / 32 +// memory_cost = (memory_size_word ** 2) / 512 + (3 * memory_size_word) +// memory_expansion_cost = new_memory_cost - last_memory_cost +// +// we care about the last_memory_cost, that happens at PC 309 +// when the CALLDATACOPY is executed for the +// line of solidity: bytes memory localCode = new bytes(codeSize); +// in that case the memory size increased from 160 to 704 bytes +// 704 bytes is 22 words. +// +// so we have memory_expansion_cost = +// (39 ** 2) / 512 + (3 * 39) - (22 ** 2) / 512 - (3 * 22) +// = 119 - 66 = 53 +var extCodeCopyMemoryExpansionCost uint64 = 53 + +// EXTCODECOPY reads from state and copies code to memory +// for gas dimensions, we don't care about expanding memory, but +// we do care about the cost being correct +// +// this test checks the cost of EXTCODECOPY when the code is cold +// and there is no memory expansion. We expect the cost to be +// be 2600, the computation to be 100, the state access to be +// 2500 + the minimum word cost, +// and all other gas dimensions to be 0 +func TestDimLogExtCodeCopyColdMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployExtCodeCopy) + _, receipt := callOnContract(t, builder, auth, contract.ExtCodeCopyColdNoMemExpansion) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + extCodeCopyLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "EXTCODECOPY") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + extCodeCopyMinimumWordCost, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: ColdMinusWarmAccountAccessCost + extCodeCopyMinimumWordCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + extCodeCopyLog, + ) + checkGasDimensionsEqualOneDimensionalGas(t, extCodeCopyLog) +} + +// EXTCODECOPY reads from state and copies code to memory +// for gas dimensions, we don't care about expanding memory, but +// we do care about the cost being correct +// +// this test checks the cost of EXTCODECOPY when the code is cold +// and there is memory expansion. We expect the cost to be +// be 2600 + whatever the memory expansion cost happens to be, +// + the minimum word cost, +// the computation to be 100 + the memory expansion cost, +// the state access to be 2500 + the minimum word cost, +// and all other gas dimensions to be 0 +func TestDimLogExtCodeCopyColdMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployExtCodeCopy) + _, receipt := callOnContract(t, builder, auth, contract.ExtCodeCopyColdMemExpansion) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + extCodeCopyLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "EXTCODECOPY") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + extCodeCopyMemoryExpansionCost + extCodeCopyMinimumWordCost, + Computation: params.WarmStorageReadCostEIP2929 + extCodeCopyMemoryExpansionCost, + StateAccess: ColdMinusWarmAccountAccessCost + extCodeCopyMinimumWordCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + extCodeCopyLog, + ) + checkGasDimensionsEqualOneDimensionalGas(t, extCodeCopyLog) +} + +// EXTCODECOPY reads from state and copies code to memory +// for gas dimensions, we don't care about expanding memory, but +// we do care about the cost being correct +// +// this test checks the cost of EXTCODECOPY when the code is warm +// and there is no memory expansion. We expect the cost to be +// be 100, the computation to be 100, the state access to be +// just the minimum word cost, +// and all other gas dimensions to be 0 +func TestDimLogExtCodeCopyWarmMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployExtCodeCopy) + _, receipt := callOnContract(t, builder, auth, contract.ExtCodeCopyWarmNoMemExpansion) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + extCodeCopyLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "EXTCODECOPY") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + extCodeCopyMinimumWordCost, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: extCodeCopyMinimumWordCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + extCodeCopyLog, + ) + checkGasDimensionsEqualOneDimensionalGas(t, extCodeCopyLog) +} + +// EXTCODECOPY reads from state and copies code to memory +// for gas dimensions, we don't care about expanding memory, but +// we do care about the cost being correct +// +// this test checks the cost of EXTCODECOPY when the code is warm +// and there is memory expansion. We expect the cost to be +// be 100 + whatever the memory expansion cost happens to be, +// + the minimum word cost, +// the computation to be 100 + the memory expansion cost, +// the state access to be the minimum word cost, +// and all other gas dimensions to be 0 +func TestDimLogExtCodeCopyWarmMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployExtCodeCopy) + _, receipt := callOnContract(t, builder, auth, contract.ExtCodeCopyWarmMemExpansion) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + extCodeCopyLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "EXTCODECOPY") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + extCodeCopyMemoryExpansionCost + extCodeCopyMinimumWordCost, + Computation: params.WarmStorageReadCostEIP2929 + extCodeCopyMemoryExpansionCost, + StateAccess: extCodeCopyMinimumWordCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + extCodeCopyLog, + ) + checkGasDimensionsEqualOneDimensionalGas(t, extCodeCopyLog) +} diff --git a/system_tests/gas_dim_log_log_test.go b/system_tests/gas_dim_log_log_test.go new file mode 100644 index 0000000000..1b7b3dd7a0 --- /dev/null +++ b/system_tests/gas_dim_log_log_test.go @@ -0,0 +1,624 @@ +package arbtest + +import ( + "testing" + + "github.com/ethereum/go-ethereum/params" + + "github.com/offchainlabs/nitro/solgen/go/gas_dimensionsgen" +) + +const ( + LogStaticCost = params.LogGas + LogDataGas = params.LogDataGas + LogTopicGasHistoryGrowth = 256 + LogTopicGasComputation = params.LogTopicGas - LogTopicGasHistoryGrowth +) + +// ######################################################################################################### +// ######################################################################################################### +// LOG0, LOG1, LOG2, LOG3, LOG4 +// ######################################################################################################### +// ######################################################################################################### +// +// Logs are pretty straightforward, they have a static cost +// we assign to computation, a linear size cost we assign directly +// to history growth, and a topic data cost. The topic data cost +// we subdivide, because some of the topic cost needs to pay for the +// cryptography we do for the bloom filter. +// 32 bytes per topic are stored in the history for the topic count. +// since the other data is charged 8 gas per byte, then each of the 375 +// should in theory be charged 256 gas for the 32 bytes of topic data and 119 gas for computation +// so we subdivide the 375 gas per topic count into 256 for the topic data and 119 for the computation. +// +// LOG has the following axes of variation: +// Number of topics (0-4) +// TopicsOnly vs ExtraData Do we have any un-indexed extra data (data that is not part of the topics)? +// MemExpansion or MemUnchanged (memory expansion or not) +// The memory expansion depends on doing a read of data from memory, +// so TopicsOnly cases cannot cause memory expansion. + +// This test deploys a contract that emits an empty LOG0 +// +// since it has no data, we expect the gas cost to just be +// the static cost of the LOG0 opcode, and we assign that to +// computation. +// Therefore we expect the one dimensional gas cost to be +// 375, computation to be 375, and all other gas dimensions to be 0 +func TestDimLogLog0TopicsOnlyMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitZeroTopicEmptyData) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + log0Log := getSpecificDimensionLog(t, traceResult.DimensionLogs, "LOG0") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: LogStaticCost, + Computation: LogStaticCost, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + log0Log, + ) + checkGasDimensionsEqualOneDimensionalGas(t, log0Log) +} + +// This test deploys a contract that emits a LOG0 with 7 bytes ("abcdefg") of data. +// +// since it has data, we expect the gas cost to be the static cost of the LOG0 opcode +// plus the 8 * 7 bytes of data cost. +// Therefore we expect the one dimensional gas cost to be 375 + the data gas cost, +// computation to be 375, the history growth to be 8 * 7, and all other gas dimensions to be 0 +func TestDimLogLog0ExtraDataMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitZeroTopicNonEmptyData) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + log0Log := getSpecificDimensionLog(t, traceResult.DimensionLogs, "LOG0") + + var numBytesWritten uint64 = 7 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: LogStaticCost + LogDataGas*numBytesWritten, + Computation: LogStaticCost, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: LogDataGas * numBytesWritten, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + log0Log, + ) + checkGasDimensionsEqualOneDimensionalGas(t, log0Log) +} + +// This test deploys a contract that a LOG1 with no data +// +// since it has no data, we expect the gas cost to be +// the static cost of the LOG1 opcode + the topic gas cost. +// Therefore we expect the one dimensional gas cost to be +// 375 + 375, the computation to be 375 + 119, +// the state access to be 0, the state growth to be 0, +// the history growth to be 256, and the state growth refund to be 0 +func TestDimLogLog1TopicsOnlyMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitOneTopicEmptyData) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + log1Log := getSpecificDimensionLog(t, traceResult.DimensionLogs, "LOG1") + var numTopics uint64 = 1 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: LogStaticCost + numTopics*(LogTopicGasHistoryGrowth+LogTopicGasComputation), + Computation: LogStaticCost + numTopics*LogTopicGasComputation, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: numTopics * LogTopicGasHistoryGrowth, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + log1Log, + ) + checkGasDimensionsEqualOneDimensionalGas(t, log1Log) +} + +// This test deploys a contract that emits a LOG1 with 9 bytes ("hijklmnop") of data. +// +// since it has data, we expect the gas cost to be the static cost of the LOG1 opcode +// plus the 8 * 9 bytes of data cost + the topic gas cost +// split between history growth and computation for 1 topic +// Therefore we expect the one dimensional gas cost to be 375 + 8 * 9 + 256 + 119, +// computation to be 375 + 119, the state access to be 0, the state growth to be 0, +// the history growth to be 256 + 8 * 9, and the state growth refund to be 0 +func TestDimLogLog1ExtraDataMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitOneTopicNonEmptyData) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + log1Log := getSpecificDimensionLog(t, traceResult.DimensionLogs, "LOG1") + + var numBytesWritten uint64 = 9 + var numTopics uint64 = 1 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: LogStaticCost + numTopics*(LogTopicGasHistoryGrowth+LogTopicGasComputation) + LogDataGas*numBytesWritten, + Computation: LogStaticCost + numTopics*LogTopicGasComputation, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: numTopics*LogTopicGasHistoryGrowth + LogDataGas*numBytesWritten, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + log1Log, + ) + checkGasDimensionsEqualOneDimensionalGas(t, log1Log) +} + +// This test checks the gas cost of a LOG2 with two topics, but no data +// +// since it has no data, we expect the one-dimensional gas cost to be the +// static cost of the LOG2 opcode plus the 2 * 375 for the two topics +// computation to be 375 + 2 * 119, the state access to be 0, the state growth to be 0, +// the history growth to be 2 * 256, and the state growth refund to be 0 +func TestDimLogLog2TopicsOnlyMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitTwoTopics) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + log2Log := getSpecificDimensionLog(t, traceResult.DimensionLogs, "LOG2") + + var numTopics uint64 = 2 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: LogStaticCost + numTopics*(LogTopicGasHistoryGrowth+LogTopicGasComputation), + Computation: LogStaticCost + numTopics*LogTopicGasComputation, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: numTopics * LogTopicGasHistoryGrowth, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + log2Log, + ) + checkGasDimensionsEqualOneDimensionalGas(t, log2Log) +} + +// This test checks the gas cost of a LOG2 with two topics, and emitting an address as extra data +// +// since it has 32 bytes (an address encoded as a uint256) of data, +// we expect the one-dimensional gas cost to be the static cost of the +// LOG2 opcode plus the 2 * 375 for the two topics +// plus the 32 bytes of data cost +// therefore we expect computation to be 375 + 2 * 119, +// the state access to be 0, the state growth to be 0, +// the history growth to be 2 * 256 + 32*8, and the state growth refund to be 0 +func TestDimLogLog2ExtraDataMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitTwoTopicsExtraData) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + log2Log := getSpecificDimensionLog(t, traceResult.DimensionLogs, "LOG2") + + var numBytesWritten uint64 = 32 // address is 20 bytes, but encoded as a uint256 so 32 bytes + var numTopics uint64 = 2 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: LogStaticCost + numTopics*(LogTopicGasHistoryGrowth+LogTopicGasComputation) + LogDataGas*numBytesWritten, + Computation: LogStaticCost + numTopics*LogTopicGasComputation, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: numTopics*LogTopicGasHistoryGrowth + LogDataGas*numBytesWritten, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + log2Log, + ) + checkGasDimensionsEqualOneDimensionalGas(t, log2Log) +} + +// This test checks the gas cost of a LOG3 with three topics, but no data +// +// since it has no data, we expect the one-dimensional gas cost to be the +// static cost of the LOG3 opcode plus the 3 * 375 for the three topics +// computation to be 375 + 3 * 119, the state access to be 0, the state growth to be 0, +// the history growth to be 3 * 256, and the state growth refund to be 0 +func TestDimLogLog3TopicsOnlyMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitThreeTopics) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + log3Log := getSpecificDimensionLog(t, traceResult.DimensionLogs, "LOG3") + + var numTopics uint64 = 3 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: LogStaticCost + numTopics*(LogTopicGasHistoryGrowth+LogTopicGasComputation), + Computation: LogStaticCost + numTopics*LogTopicGasComputation, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: numTopics * LogTopicGasHistoryGrowth, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + log3Log, + ) + checkGasDimensionsEqualOneDimensionalGas(t, log3Log) +} + +// This test checks the gas cost of a LOG3 with three topics, and emitting bytes32 as extra data +// +// since it has 32 bytes (a bytes32) of data, +// we expect the one-dimensional gas cost to be the static cost of the +// LOG3 opcode plus the 3 * 375 for the three topics +// plus the 32 bytes of data cost +// therefore we expect computation to be 375 + 3 * 119, +// the state access to be 0, the state growth to be 0, +// the history growth to be 3 * 256 + 32*8, and the state growth refund to be 0 +func TestDimLogLog3ExtraDataMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitThreeTopicsExtraData) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + log3Log := getSpecificDimensionLog(t, traceResult.DimensionLogs, "LOG3") + + var numBytesWritten uint64 = 32 + var numTopics uint64 = 3 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: LogStaticCost + numTopics*(LogTopicGasHistoryGrowth+LogTopicGasComputation) + LogDataGas*numBytesWritten, + Computation: LogStaticCost + numTopics*LogTopicGasComputation, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: numTopics*LogTopicGasHistoryGrowth + LogDataGas*numBytesWritten, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + log3Log, + ) + checkGasDimensionsEqualOneDimensionalGas(t, log3Log) +} + +// This test checks the gas cost of a LOG4 with four topics, but no data +// +// since it has no data, we expect the one-dimensional gas cost to be the +// static cost of the LOG4 opcode plus the 4 * 375 for the four topics +// computation to be 375 + 4 * 119, the state access to be 0, the state growth to be 0, +// the history growth to be 4 * 256, and the state growth refund to be 0 +func TestDimLogLog4TopicsOnlyMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitFourTopics) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + log4Log := getSpecificDimensionLog(t, traceResult.DimensionLogs, "LOG4") + + var numTopics uint64 = 4 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: LogStaticCost + numTopics*(LogTopicGasHistoryGrowth+LogTopicGasComputation), + Computation: LogStaticCost + numTopics*LogTopicGasComputation, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: numTopics * LogTopicGasHistoryGrowth, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + log4Log, + ) + checkGasDimensionsEqualOneDimensionalGas(t, log4Log) +} + +// This test checks the gas cost of a LOG4 with four topics, and emitting bytes32 as extra data +// +// since it has 32 bytes (a bytes32) of data, +// we expect the one-dimensional gas cost to be the static cost of the +// LOG4 opcode plus the 4 * 375 for the four topics +// plus the 32 bytes of data cost +// therefore we expect computation to be 375 + 4 * 119, +// the state access to be 0, the state growth to be 0, +// the history growth to be 4 * 256 + 32*8, and the state growth refund to be 0 +func TestDimLogLog4ExtraDataMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitFourTopicsExtraData) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + log4Log := getSpecificDimensionLog(t, traceResult.DimensionLogs, "LOG4") + + var numBytesWritten uint64 = 32 + var numTopics uint64 = 4 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: LogStaticCost + numTopics*(LogTopicGasHistoryGrowth+LogTopicGasComputation) + LogDataGas*numBytesWritten, + Computation: LogStaticCost + numTopics*LogTopicGasComputation, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: numTopics*LogTopicGasHistoryGrowth + LogDataGas*numBytesWritten, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + log4Log, + ) + checkGasDimensionsEqualOneDimensionalGas(t, log4Log) +} + +// Comments about the memory expanstion test cases: +// For all of the memory expansion tests, we set the +// offset to the end of the memory and the length to 64. +// The memory size at the time of the LOGN opcode is 96. +// therefore we expect the memory expansion cost to be: +// +// memory_size_word = (memory_byte_size + 31) / 32 +// memory_cost = (memory_size_word ** 2) / 512 + (3 * memory_size_word) +// memory_expansion_cost = new_memory_cost - last_memory_cost +// +// so we have 5**2 / 512 + (3 * 5) - (3**2)/512 - (3*3) +// 15 - 9 = 6 +var logNMemoryExpansionCost uint64 = 6 + +// This test checks the gas cost of a LOG0 with no topics, and with +// data that is garbage, since it's doing memory expansion, and reading +// past the end of the memory. In this test, we set up the memory of size +// 96 and tell the LOG0 to read starting at position 96 and read 64 bytes. +// +// We expect the one-dimensional gas cost to be the static cost of the +// LOG0 opcode plus the 64 bytes memory expansion cost, which at memory +// length 96, is 6. +// +// we expect the computation gas cost to be the static cost of the LOG0 opcode +// + the memory expansion cost +// we expect the state access to be 0, the state growth to be 0, +// the history growth to be 64*8, and the state growth refund to be 0 +func TestDimLogLog0ExtraDataMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitZeroTopicNonEmptyDataAndMemExpansion) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + log0Log := getSpecificDimensionLog(t, traceResult.DimensionLogs, "LOG0") + + var numBytesWritten uint64 = 64 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: LogStaticCost + LogDataGas*numBytesWritten + logNMemoryExpansionCost, + Computation: LogStaticCost + logNMemoryExpansionCost, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: LogDataGas * numBytesWritten, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + log0Log, + ) + checkGasDimensionsEqualOneDimensionalGas(t, log0Log) +} + +// This test checks the gas cost of a LOG1 with one topic, and with +// data that is garbage, since it's doing memory expansion, and reading +// past the end of the memory. In this test, we set up the memory of size +// 96 and tell the LOG1 to read starting at position 96 and read 64 bytes. +// +// since it has data, we expect the gas cost to be the static cost of the LOG1 opcode +// plus the 8 * 64 bytes of data cost + the topic gas cost +// split between history growth and computation for 1 topic +// Therefore we expect the one dimensional gas cost to be 375 + 8 * 64 + 256 + 119 + 6, +// computation to be 375 + 119 + 6, the state access to be 0, the state growth to be 0, +// the history growth to be 256 + 8 * 64, and the state growth refund to be 0 +func TestDimLogLog1ExtraDataMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitOneTopicNonEmptyDataAndMemExpansion) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + log1Log := getSpecificDimensionLog(t, traceResult.DimensionLogs, "LOG1") + + var numBytesWritten uint64 = 64 + var numTopics uint64 = 1 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: LogStaticCost + numTopics*(LogTopicGasHistoryGrowth+LogTopicGasComputation) + LogDataGas*numBytesWritten + logNMemoryExpansionCost, + Computation: LogStaticCost + numTopics*LogTopicGasComputation + logNMemoryExpansionCost, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: numTopics*LogTopicGasHistoryGrowth + LogDataGas*numBytesWritten, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + log1Log, + ) + checkGasDimensionsEqualOneDimensionalGas(t, log1Log) +} + +// This test checks the gas cost of a LOG2 with two topics, and with +// data that is garbage, since it's doing memory expansion, and reading +// past the end of the memory. In this test, we set up the memory of size +// 96 and tell the LOG2 to read starting at position 96 and read 64 bytes. +// +// since it has data, we expect the gas cost to be the static cost of the LOG2 opcode +// plus the 8 * 64 bytes of data cost + the topic gas cost +// split between history growth and computation for 2 topics +// Therefore we expect the one dimensional gas cost to be 375 + 8 * 64 + 2*(256 + 119) + 6, +// computation to be 375 + 2*119 + 6, the state access to be 0, the state growth to be 0, +// the history growth to be 2*256 + 8 * 64, and the state growth refund to be 0 +func TestDimLogLog2ExtraDataMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitTwoTopicsExtraDataAndMemExpansion) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + log2Log := getSpecificDimensionLog(t, traceResult.DimensionLogs, "LOG2") + + var numBytesWritten uint64 = 64 + var numTopics uint64 = 2 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: LogStaticCost + numTopics*(LogTopicGasHistoryGrowth+LogTopicGasComputation) + LogDataGas*numBytesWritten + logNMemoryExpansionCost, + Computation: LogStaticCost + numTopics*LogTopicGasComputation + logNMemoryExpansionCost, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: numTopics*LogTopicGasHistoryGrowth + LogDataGas*numBytesWritten, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + log2Log, + ) + checkGasDimensionsEqualOneDimensionalGas(t, log2Log) +} + +// This test checks the gas cost of a LOG3 with three topics, and with +// data that is garbage, since it's doing memory expansion, and reading +// past the end of the memory. In this test, we set up the memory of size +// 96 and tell the LOG3 to read starting at position 96 and read 64 bytes. +// +// since it has data, we expect the gas cost to be the static cost of the LOG3 opcode +// plus the 8 * 64 bytes of data cost + the topic gas cost +// split between history growth and computation for 3 topics +// Therefore we expect the one dimensional gas cost to be 375 + 8 * 64 + 3*(256 + 119) + 6, +// computation to be 375 + 3*119 + 6, the state access to be 0, the state growth to be 0, +// the history growth to be 3*256 + 8 * 64, and the state growth refund to be 0 +func TestDimLogLog3ExtraDataMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitThreeTopicsExtraDataAndMemExpansion) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + log3Log := getSpecificDimensionLog(t, traceResult.DimensionLogs, "LOG3") + + var numBytesWritten uint64 = 64 + var numTopics uint64 = 3 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: LogStaticCost + numTopics*(LogTopicGasHistoryGrowth+LogTopicGasComputation) + LogDataGas*numBytesWritten + logNMemoryExpansionCost, + Computation: LogStaticCost + numTopics*LogTopicGasComputation + logNMemoryExpansionCost, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: numTopics*LogTopicGasHistoryGrowth + LogDataGas*numBytesWritten, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + log3Log, + ) + checkGasDimensionsEqualOneDimensionalGas(t, log3Log) +} + +// This test checks the gas cost of a LOG4 with four topics, and with +// data that is garbage, since it's doing memory expansion, and reading +// past the end of the memory. In this test, we set up the memory of size +// 96 and tell the LOG4 to read starting at position 96 and read 64 bytes. +// +// since it has data, we expect the gas cost to be the static cost of the LOG4 opcode +// plus the 8 * 64 bytes of data cost + the topic gas cost +// split between history growth and computation for 4 topics +// Therefore we expect the one dimensional gas cost to be 375 + 8 * 64 + 4*(256 + 119) + 6, +// computation to be 375 + 4*119 + 6, the state access to be 0, the state growth to be 0, +// the history growth to be 4*256 + 8 * 64, and the state growth refund to be 0 +func TestDimLogLog4ExtraDataMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitFourTopicsExtraDataAndMemExpansion) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + log4Log := getSpecificDimensionLog(t, traceResult.DimensionLogs, "LOG4") + + var numBytesWritten uint64 = 64 + var numTopics uint64 = 4 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: LogStaticCost + numTopics*(LogTopicGasHistoryGrowth+LogTopicGasComputation) + LogDataGas*numBytesWritten + logNMemoryExpansionCost, + Computation: LogStaticCost + numTopics*LogTopicGasComputation + logNMemoryExpansionCost, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: numTopics*LogTopicGasHistoryGrowth + LogDataGas*numBytesWritten, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + log4Log, + ) + checkGasDimensionsEqualOneDimensionalGas(t, log4Log) +} diff --git a/system_tests/gas_dim_log_read_call_test.go b/system_tests/gas_dim_log_read_call_test.go new file mode 100644 index 0000000000..5f62008f09 --- /dev/null +++ b/system_tests/gas_dim_log_read_call_test.go @@ -0,0 +1,636 @@ +package arbtest + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" + + "github.com/offchainlabs/nitro/solgen/go/gas_dimensionsgen" +) + +// this file does the tests for the read-only call opcodes, i.e. DELEGATECALL and STATICCALL + +// ######################################################################################################### +// ######################################################################################################### +// DELEGATECALL & STATICCALL +// ######################################################################################################### +// ######################################################################################################### +// +// DELEGATECALL and STATICCALL have many permutations +// Warm vs Cold (in the access list) +// Contract vs NoCode (is there code at the target address that we are calling to? or is it an EOA?) +// MemExpansion or MemUnchanged (memory expansion or not) +// +// static_gas = 0 +// dynamic_gas = memory_expansion_cost + code_execution_cost + address_access_cost +// we do not consider the code_execution_cost as part of the cost of the call itself +// since those costs are counted and incurred by the children of the call. + +// this test does the case where the target address being delegatecalled to +// is empty - i.e. no address code at that location. The address being called +// to is also cold, therefore incurring the access list cold read cost. +// the solidity compiler forces no memory expansion for us. +// +// since it's a call, the call itself does not incur the computation cost +// but rather its children incur various costs. Therefore, we expect the +// one-dimensional gas cost to be 2600, for the access list cold read cost, +// computation to be 100 (for the warm access list read), +// state access to be 2500, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogDelegateCallColdNoCodeMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, delegateCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCaller) + + receipt := callOnContractWithOneArg(t, builder, auth, delegateCaller.TestDelegateCallEmptyCold, emptyAccountAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + delegateCallLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "DELEGATECALL") + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, delegateCallLog) + checkGasDimensionsEqualOneDimensionalGas(t, delegateCallLog) +} + +// this test does the case where the target address being delegatecalled to +// is empty - i.e. no address code at that location. +// the solidity compiler forces no memory expansion for us. +// +// since it's a call, the call itself does not incur the computation cost +// but rather its children incur various costs. Therefore, we expect the +// one-dimensional gas cost to be 100, for the warm access list read, +// computation to be 100 (for the warm access list read), +// state access to be 0, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogDelegateCallWarmNoCodeMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, delegateCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCaller) + + receipt := callOnContractWithOneArg(t, builder, auth, delegateCaller.TestDelegateCallEmptyWarm, emptyAccountAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + delegateCallLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "DELEGATECALL") + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, delegateCallLog) + checkGasDimensionsEqualOneDimensionalGas(t, delegateCallLog) +} + +// in this test, the target address being delegatecalled to +// is non-empty, there is code at that location that will be executed, +// and the address being called is cold. +// the solidity compiler forces no memory expansion for us. +// +// since it's a call, the call itself does not incur the computation cost +// but rather its children incur various costs. Therefore, we expect the +// call to have a one-dimensional gas cost of 2600 + the child execution gas, +// due to the access list cold read cost, +// computation to be 100 (for the warm access list read), +// state access to be 2500, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogDelegateCallColdContractMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, delegateCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCaller) + delegateCalleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCallee) + + receipt := callOnContractWithOneArg(t, builder, auth, delegateCaller.TestDelegateCallNonEmptyCold, delegateCalleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + delegateCallLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "DELEGATECALL") + var expectedChildGasExecutionCost uint64 = 22712 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, delegateCallLog) + checkGasDimensionsEqualOneDimensionalGas(t, delegateCallLog) +} + +// in this test, the target address being delegatecalled to +// is non-empty, there is code at that location that will be executed, +// and the address being called is warm. +// the solidity compiler forces no memory expansion for us. +// +// since it's a call, the call itself does not incur the computation cost +// but rather its children incur various costs. Therefore, we expect the +// call to have a one-dimensional gas cost of 100 + the child execution gas, +// due to the warm access list read cost, +// computation to be 100 (for the warm access list read), +// state access to be 0, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogDelegateCallWarmContractMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, delegateCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCaller) + delegateCalleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCallee) + + receipt := callOnContractWithOneArg(t, builder, auth, delegateCaller.TestDelegateCallNonEmptyWarm, delegateCalleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + delegateCallLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "DELEGATECALL") + var expectedChildGasExecutionCost uint64 = 22712 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, delegateCallLog) + checkGasDimensionsEqualOneDimensionalGas(t, delegateCallLog) +} + +// this test does the case where the target address being delegatecalled to +// is empty - i.e. no address code at that location. The address being called +// to is also cold, therefore incurring the access list cold read cost. +// in this case we force memory expansion for the call in the solidity +// assembly. By staring at the traces and debugging, we find that the +// memory expansion cost is 6. +// +// since it's a call, the call itself does not incur the computation cost +// but rather its children incur various costs. Therefore, we expect the +// one-dimensional gas cost to be 2600, for the access list cold read cost, +// computation to be 100 + 6 (warm access list read + memory expansion), +// state access to be 2500, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogDelegateCallColdNoCodeMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, delegateCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCaller) + + receipt := callOnContractWithOneArg(t, builder, auth, delegateCaller.TestDelegateCallEmptyColdMemExpansion, emptyAccountAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + delegateCallLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "DELEGATECALL") + + var memoryExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + memoryExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + memoryExpansionCost, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, delegateCallLog) + checkGasDimensionsEqualOneDimensionalGas(t, delegateCallLog) +} + +// this test does the case where the target address being delegatecalled to +// is empty - i.e. no address code at that location. +// we force memory expansion for the call in the solidity assembly. +// from staring at the traces and debugging,the memory expansion cost is 6. +// +// since it's a call, the call itself does not incur the computation cost +// but rather its children incur various costs. Therefore, we expect the +// one-dimensional gas cost to be 100 + 6, for the warm access list read + memory expansion, +// computation to be 100 + 6 (for the warm access list read + memory expansion), +// state access to be 0, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogDelegateCallWarmNoCodeMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, delegateCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCaller) + + receipt := callOnContractWithOneArg(t, builder, auth, delegateCaller.TestDelegateCallEmptyWarmMemExpansion, emptyAccountAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + delegateCallLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "DELEGATECALL") + + var memoryExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + memoryExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + memoryExpansionCost, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, delegateCallLog) + checkGasDimensionsEqualOneDimensionalGas(t, delegateCallLog) +} + +// in this test, the target address being delegatecalled to +// is non-empty, there is code at that location that will be executed, +// and the address being called is cold. +// we force memory expansion for the call in the solidity assembly. +// from staring at the traces and debugging,the memory expansion cost is 6. +// +// since it's a call, the call itself does not incur the computation cost +// but rather its children incur various costs. Therefore, we expect the +// call to have a one-dimensional gas cost of 2600 + 6 + the child execution gas, +// due to the access list cold read cost and memory expansion cost, +// computation to be 100 + 6 (for the warm access list read + memory expansion), +// state access to be 2500, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogDelegateCallColdContractMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, delegateCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCaller) + delegateCalleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCallee) + + receipt := callOnContractWithOneArg(t, builder, auth, delegateCaller.TestDelegateCallNonEmptyColdMemExpansion, delegateCalleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + delegateCallLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "DELEGATECALL") + + var memoryExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 22712 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + memoryExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + memoryExpansionCost, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, delegateCallLog) + checkGasDimensionsEqualOneDimensionalGas(t, delegateCallLog) +} + +// in this test, the target address being delegatecalled to +// is non-empty, there is code at that location that will be executed, +// and the address being called is warm. +// we force memory expansion for the call in the solidity assembly. +// from staring at the traces and debugging,the memory expansion cost is 6. +// +// since it's a call, the call itself does not incur the computation cost +// but rather its children incur various costs. Therefore, we expect the +// call to have a one-dimensional gas cost of 100 + 6 + the child execution gas, +// due to the warm access list read cost and memory expansion cost, +// computation to be 100 + 6 (for the warm access list read + memory expansion), +// state access to be 0, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogDelegateCallWarmContractMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, delegateCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCaller) + delegateCalleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCallee) + + receipt := callOnContractWithOneArg(t, builder, auth, delegateCaller.TestDelegateCallNonEmptyWarmMemExpansion, delegateCalleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + delegateCallLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "DELEGATECALL") + + var memoryExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 22712 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + memoryExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + memoryExpansionCost, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, delegateCallLog) + checkGasDimensionsEqualOneDimensionalGas(t, delegateCallLog) +} + +// this test does the case where the a contract calls another contract via staticcall +// the target address is empty (does not actually have code at that address), +// and the address being called is cold. +// there is no memory expansion in this case. +// +// we expect the one-dimensional gas cost to be 2500, for the cold access list read cost, +// computation to be 100 for the warm access list read cost, state access to be 2500, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogStaticCallColdNoCodeMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, staticCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCaller) + + receipt := callOnContractWithOneArg(t, builder, auth, staticCaller.TestStaticCallEmptyCold, emptyAccountAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + staticCallLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "STATICCALL") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: 0, + } + checkGasDimensionsMatch(t, expected, staticCallLog) + checkGasDimensionsEqualOneDimensionalGas(t, staticCallLog) +} + +// this test does the case where the a contract calls another contract via staticcall +// the target address is empty (does not actually have code at that address), +// and the address being called is warm. +// there is no memory expansion in this case. +// +// we expect the one-dimensional gas cost to be 100, for the warm access list read cost, +// computation to be 100 for the warm access list read cost, state access to be 0, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogStaticCallWarmNoCodeMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, staticCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCaller) + + receipt := callOnContractWithOneArg(t, builder, auth, staticCaller.TestStaticCallEmptyWarm, emptyAccountAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + staticCallLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "STATICCALL") + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, staticCallLog) + checkGasDimensionsEqualOneDimensionalGas(t, staticCallLog) +} + +// this test does the case where the a contract calls another contract via staticcall +// the target address is non-empty, so there is code at that location that will be executed, +// and the address being called is cold. +// there is no memory expansion in this case. +// +// we expect the one-dimensional gas cost to be 2500, for the cold access list read cost, +// computation to be 100 for the warm access list read cost, state access to be 2500, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogStaticCallColdContractMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, staticCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCaller) + staticCalleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCallee) + receipt := callOnContractWithOneArg(t, builder, auth, staticCaller.TestStaticCallNonEmptyCold, staticCalleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + staticCallLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "STATICCALL") + var expectedChildGasExecutionCost uint64 = 2409 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, staticCallLog) + checkGasDimensionsEqualOneDimensionalGas(t, staticCallLog) +} + +// this test does the case where the a contract calls another contract via staticcall +// the target address is non-empty, so there is code at that location that will be executed, +// and the address being called is warm. +// there is no memory expansion in this case. +// +// we expect the one-dimensional gas cost to be 100, for the warm access list read cost, +// computation to be 100 for the warm access list read cost, state access to be 0, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogStaticCallWarmContractMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, staticCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCaller) + staticCalleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCallee) + receipt := callOnContractWithOneArg(t, builder, auth, staticCaller.TestStaticCallNonEmptyWarm, staticCalleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + staticCallLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "STATICCALL") + var expectedChildGasExecutionCost uint64 = 2409 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, staticCallLog) + checkGasDimensionsEqualOneDimensionalGas(t, staticCallLog) +} + +// this test does the case where a contract calls another contract via staticcall +// the target address is empty, so there is no code at that location that will be executed, +// and the address being called is cold. +// memory expansion occurs in this case. +// +// we expect the one-dimensional gas cost to be 2600, for the cold access list read cost, +// plus the memory expansion cost, which via debugging and tracing, we know is 6 gas. +// computation to be 100+6 for the warm access list read cost, state access to be 2500, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogStaticCallColdNoCodeMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, staticCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCaller) + + receipt := callOnContractWithOneArg(t, builder, auth, staticCaller.TestStaticCallEmptyColdMemExpansion, emptyAccountAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + staticCallLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "STATICCALL") + + var memoryExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + memoryExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + memoryExpansionCost, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, staticCallLog) + checkGasDimensionsEqualOneDimensionalGas(t, staticCallLog) +} + +// this test does the case where a contract calls another contract via staticcall +// the target address is empty, so there is no code at that location that will be executed, +// and the address being called is warm. +// memory expansion occurs in this case. +// +// we expect the one-dimensional gas cost to be 100, for the warm access list read cost, +// plus the memory expansion cost, which via debugging and tracing, we know is 6 gas. +// computation to be 100+6 for the warm access list read cost, state access to be 0, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogStaticCallWarmNoCodeMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, staticCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCaller) + + receipt := callOnContractWithOneArg(t, builder, auth, staticCaller.TestStaticCallEmptyWarmMemExpansion, emptyAccountAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + staticCallLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "STATICCALL") + + var memoryExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 0 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + memoryExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + memoryExpansionCost, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, staticCallLog) + checkGasDimensionsEqualOneDimensionalGas(t, staticCallLog) +} + +// this test does the case where a contract calls another contract via staticcall +// the target address is non-empty, so there is code at that location that will be executed, +// and the address being called is cold. +// memory expansion occurs in this case. +// +// we expect the one-dimensional gas cost to be 2600, for the cold access list read cost, +// plus the memory expansion cost, which via debugging and tracing, we know is 6 gas. +// we also know the child execution gas is 2409 from debugging and tracing. +// computation to be 100+6 for the warm access list read cost, state access to be 2500, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogStaticCallColdContractMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, staticCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCaller) + staticCalleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCallee) + receipt := callOnContractWithOneArg(t, builder, auth, staticCaller.TestStaticCallNonEmptyColdMemExpansion, staticCalleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + staticCallLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "STATICCALL") + + var memoryExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 2409 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929 + memoryExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + memoryExpansionCost, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, staticCallLog) + checkGasDimensionsEqualOneDimensionalGas(t, staticCallLog) +} + +// this test does the case where a contract calls another contract via staticcall +// the target address is non-empty, so there is code at that location that will be executed, +// and the address being called is warm. +// memory expansion occurs in this case. +// +// we expect the one-dimensional gas cost to be 100, for the warm access list read cost, +// plus the memory expansion cost, which via debugging and tracing, we know is 6 gas. +// we also know the child execution gas is 2409 from debugging and tracing. +// computation to be 100+6 for the warm access list read cost, state access to be 0, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogStaticCallWarmContractMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, staticCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCaller) + staticCalleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCallee) + receipt := callOnContractWithOneArg(t, builder, auth, staticCaller.TestStaticCallNonEmptyWarmMemExpansion, staticCalleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + staticCallLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "STATICCALL") + + var memoryExpansionCost uint64 = 6 + var expectedChildGasExecutionCost uint64 = 2409 + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929 + memoryExpansionCost, + Computation: params.WarmStorageReadCostEIP2929 + memoryExpansionCost, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: expectedChildGasExecutionCost, + } + checkGasDimensionsMatch(t, expected, staticCallLog) + checkGasDimensionsEqualOneDimensionalGas(t, staticCallLog) +} diff --git a/system_tests/gas_dim_log_selfdestruct_test.go b/system_tests/gas_dim_log_selfdestruct_test.go new file mode 100644 index 0000000000..0689dfc572 --- /dev/null +++ b/system_tests/gas_dim_log_selfdestruct_test.go @@ -0,0 +1,336 @@ +package arbtest + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" + + "github.com/offchainlabs/nitro/solgen/go/gas_dimensionsgen" +) + +// ######################################################################################################### +// ######################################################################################################### +// SELFDESTRUCT +// ######################################################################################################### +// ######################################################################################################### +// +// SELFDESTRUCT has many permutations +// Warm vs Cold (in the access list) +// Paying vs NoTransfer (is ether value being sent with this call?) +// Virgin vs Funded (has the target address ever been seen before / does it have code already / is the account already in the accounts tree?) +// +// `value_to_empty_account_cost` is storage growth (25000) +// `address_access_cost` for cold addresses is a read/write cost +// since all this operation does is send ether at this point +// then we assign the static gas of 5000 to read/write, +// since in the non-state-growth case this would be a read/write +// and the access cost is read/write +// in the case where state growth happens due to sending funds to +// a new empty account, then we assign that to state growth. + +// in this test case, we self destruct and set the target of funds to be +// an empty address that has no code or value at that address. Normally +// that would trigger state growth, but we also have no money to send +// as part of the selfdestruct, so we don't trigger state growth. +// +// in this case we expect the one-dimensional cost to be 5000 + 2600, +// for the base selfdestruct cost and the access list cold read cost, +// computation to be 100 (for the warm access list read), +// state access to be 5000+2500, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogSelfdestructColdNoTransferVirgin(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, selfDestructor := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySelfDestructor) + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // call selfDestructor.warmSelfDestructor(0xdeadbeef) + receipt := callOnContractWithOneArg(t, builder, auth, selfDestructor.SelfDestruct, emptyAccountAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + selfDestructLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "SELFDESTRUCT") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.SelfdestructGasEIP150 + params.ColdAccountAccessCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: params.SelfdestructGasEIP150 + ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch(t, expected, selfDestructLog) + checkGasDimensionsEqualOneDimensionalGas(t, selfDestructLog) +} + +// in this test case, we self destruct and set the target of funds to be +// an address that is not empty (i.e. it has code or value). +// but we also self destruct with no funds to send, so it's kind of moot. +// +// in this case we expect the one-dimensional cost to be 5000 + 2600, +// for the base selfdestruct cost and the access list cold read cost, +// computation to be 100 (for the warm access list read), +// state access to be 5000+2500, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogSelfdestructColdNoTransferFunded(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, selfDestructor := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySelfDestructor) + payableCounterAddress, _ /*payableCounter*/ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployPayableCounter) + + // prefund the selfDestructor and payableCounter with some funds + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", payableCounterAddress, big.NewInt(1e17), builder.L2Info) + + // call selfDestructor.warmSelfDestructor(payableCounterAddress) + receipt := callOnContractWithOneArg(t, builder, auth, selfDestructor.SelfDestruct, payableCounterAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + selfDestructLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "SELFDESTRUCT") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.SelfdestructGasEIP150 + params.ColdAccountAccessCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: params.SelfdestructGasEIP150 + ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch(t, expected, selfDestructLog) + checkGasDimensionsEqualOneDimensionalGas(t, selfDestructLog) +} + +// in this test case, we self destruct and set the target of funds to be +// an address that has no code or value at that address. +// this does trigger state growth. +// +// in this case we expect the one-dimensional cost to be 5000 + 2600 + 25000, +// for the base selfdestruct cost and the access list cold read cost and the +// value to empty account cost, +// so we expect a computation of 100 (for the warm access list read), +// state access to be 5000+2500, state growth to be 25000, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogSelfdestructColdPayingVirgin(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + selfDestructorAddress, selfDestructor := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySelfDestructor) + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", selfDestructorAddress, big.NewInt(1e17), builder.L2Info) + + // call selfDestructor.SelfDestruct(emptyAccountAddress) - which is cold + receipt := callOnContractWithOneArg(t, builder, auth, selfDestructor.SelfDestruct, emptyAccountAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + selfDestructLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "SELFDESTRUCT") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.SelfdestructGasEIP150 + params.CreateBySelfdestructGas + params.ColdAccountAccessCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: params.SelfdestructGasEIP150 + ColdMinusWarmAccountAccessCost, + StateGrowth: params.CreateBySelfdestructGas, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch(t, expected, selfDestructLog) + checkGasDimensionsEqualOneDimensionalGas(t, selfDestructLog) +} + +// in this test case, we self destruct and set the target of funds to be +// an address that already has code or value at that address. +// since the address already has code or value, the operation is a state read/write +// rather than a state growth. +// +// in this case we expect the one-dimensional cost to be 5000 + 2600, +// for the base selfdestruct cost and the access list cold read cost, +// computation to be 100 (for the warm access list read), +// state access to be 5000+2500, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogSelfdestructColdPayingFunded(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + selfDestructorAddress, selfDestructor := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySelfDestructor) + emptyAccount := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // send some money to the self destructor address ahead of time + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", selfDestructorAddress, big.NewInt(1e17), builder.L2Info) + + // call selfDestructor.SelfDestruct(emptyAccountAddress) - which is cold + receipt := callOnContractWithOneArg(t, builder, auth, selfDestructor.SelfDestruct, emptyAccount) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + selfDestructLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "SELFDESTRUCT") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.SelfdestructGasEIP150 + params.CreateBySelfdestructGas + params.ColdAccountAccessCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: params.SelfdestructGasEIP150 + ColdMinusWarmAccountAccessCost, + StateGrowth: params.CreateBySelfdestructGas, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch(t, expected, selfDestructLog) + checkGasDimensionsEqualOneDimensionalGas(t, selfDestructLog) +} + +// in this test case, we self destruct and set the target of funds to be +// an empty address that has no code or value at that address. Normally +// that would trigger state growth, but we also have no money to send +// as part of the selfdestruct, so we don't trigger state growth. +// +// in this case we expect the one-dimensional cost to be 5000, +// computation to be 100 (for the warm access list read), +// state access to be 4900, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogSelfdestructWarmNoTransferVirgin(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, selfDestructor := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySelfDestructor) + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // call selfDestructor.warmSelfDestructor(0xdeadbeef) + receipt := callOnContractWithOneArg(t, builder, auth, selfDestructor.WarmEmptySelfDestructor, emptyAccountAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + selfDestructLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "SELFDESTRUCT") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.SelfdestructGasEIP150, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: params.SelfdestructGasEIP150 - params.WarmStorageReadCostEIP2929, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch(t, expected, selfDestructLog) + checkGasDimensionsEqualOneDimensionalGas(t, selfDestructLog) +} + +// in this test case, we self destruct and set the target of funds to be +// an address that has some code at that address and some eth value already, +// but we don't have any funds to send, so we don't send any as part of the +// selfdestruct +// +// for this transaction we expect a one-dimensional cost of 5000 +// computation to be 100 (for the warm access list read), +// state access to be 4900, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogSelfdestructWarmNoTransferFunded(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _ /*selfDestructorAddress*/, selfDestructor := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySelfDestructor) + payableCounterAddress, _ /*payableCounter*/ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployPayableCounter) + + // prefund the payableCounter with some funds, but not the selfDestructor + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", payableCounterAddress, big.NewInt(1e17), builder.L2Info) + + // call selfDestructor.warmSelfDestructor(payableCounterAddress) + receipt := callOnContractWithOneArg(t, builder, auth, selfDestructor.WarmSelfDestructor, payableCounterAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + selfDestructLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "SELFDESTRUCT") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.SelfdestructGasEIP150, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: params.SelfdestructGasEIP150 - params.WarmStorageReadCostEIP2929, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch(t, expected, selfDestructLog) + checkGasDimensionsEqualOneDimensionalGas(t, selfDestructLog) +} + +// in this test case we self destruct and set the target of funds to be +// an address that neither has code nor any eth value yet. Which means +// the resulting selfdestruct will cause state growth, assigning value +// to a new account. +// +// in this case we expect the one-dimensional cost to be 30000, +// 5000 from static cost and 25000 from the value to empty account cost +// 100 for warm access list read +// that gives us a computation of 100, state access of 4900, state growth of 25000, +// history growth of 0, and state growth refund of 0 +func TestDimLogSelfdestructWarmPayingVirgin(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + selfDestructorAddress, selfDestructor := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySelfDestructor) + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", selfDestructorAddress, big.NewInt(1e17), builder.L2Info) + + // call selfDestructor.warmSelfDestructor(0xdeadbeef) + receipt := callOnContractWithOneArg(t, builder, auth, selfDestructor.WarmEmptySelfDestructor, emptyAccountAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + selfDestructLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "SELFDESTRUCT") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.SelfdestructGasEIP150 + params.CreateBySelfdestructGas, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: params.SelfdestructGasEIP150 - params.WarmStorageReadCostEIP2929, + StateGrowth: params.CreateBySelfdestructGas, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch(t, expected, selfDestructLog) + checkGasDimensionsEqualOneDimensionalGas(t, selfDestructLog) +} + +// in this test case, we self destruct and set the target of funds to be +// an address that has some code at that address and some eth value already +// +// for this transaction we expect a one-dimensional cost of 5000 +// computation to be 100 (for the warm access list read), +// state access to be 4900, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogSelfdestructWarmPayingFunded(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + selfDestructorAddress, selfDestructor := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySelfDestructor) + payableCounterAddress, _ /*payableCounter*/ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployPayableCounter) + + // prefund the selfDestructor and payableCounter with some funds + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", selfDestructorAddress, big.NewInt(1e17), builder.L2Info) + _, _ = builder.L2.TransferBalanceTo(t, "Owner", payableCounterAddress, big.NewInt(1e17), builder.L2Info) + + // call selfDestructor.warmSelfDestructor(payableCounterAddress) + receipt := callOnContractWithOneArg(t, builder, auth, selfDestructor.WarmSelfDestructor, payableCounterAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + selfDestructLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "SELFDESTRUCT") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.SelfdestructGasEIP150, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: params.SelfdestructGasEIP150 - params.WarmStorageReadCostEIP2929, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch(t, expected, selfDestructLog) + checkGasDimensionsEqualOneDimensionalGas(t, selfDestructLog) +} diff --git a/system_tests/gas_dim_log_sstore_test.go b/system_tests/gas_dim_log_sstore_test.go new file mode 100644 index 0000000000..8eb4d18061 --- /dev/null +++ b/system_tests/gas_dim_log_sstore_test.go @@ -0,0 +1,536 @@ +package arbtest + +import ( + "testing" + + "github.com/ethereum/go-ethereum/params" + + "github.com/offchainlabs/nitro/solgen/go/gas_dimensionsgen" +) + +// ######################################################################################################### +// ######################################################################################################### +// SSTORE +// ######################################################################################################### +// ######################################################################################################### +// +// SSTORE has many permutations +// warm or cold +// 0 -> 0 +// 0 -> non-zero +// non-zero -> 0 +// non-zero -> non-zero (same value) +// non-zero -> non-zero (different value) +// +// Gas dimensionally, SSTORE has one rule basically: if the total gas is +// greater than 20,000 gas, then we know that a write of a 0->non-zero +// occured. That is the only case that actually grows state database size +// all of the other cases are either writing to existing values, or +// writing to a local cache that will eventually be written to the existing +// database values. +// so the majority of cases for SSTORE fall under read/write, except for +// the one case where the database size grows. + +// This test deployes a contract with a local variable that we can SSTORE to +// in this test, we SSTORE a variable that starts at 0 and SSTORE it to 0 +// +// we expect the gas cost of this operation to be 2200, 100 for the base sstore cost, +// and 2100 for cold access set access. +// we expect computation to be 0, state access to be 2200, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogSstoreColdZeroToZero(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreColdZeroToZero) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + sstoreLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "SSTORE") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: ColdMinusWarmSloadCost + params.NetSstoreDirtyGas, + Computation: 0, + StateAccess: ColdMinusWarmSloadCost + params.NetSstoreDirtyGas, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch(t, expected, sstoreLog) + checkGasDimensionsEqualOneDimensionalGas(t, sstoreLog) +} + +// This test deployes a contract with a local variable that we can SSTORE to +// in this test, we SSTORE a variable that starts at 0 and SSTORE it to a non-zero value +// +// we expect the gas cost of this operation to be 22100, 20000 for the sstore cost, +// and 2100 for cold access set access. +// we expect computation to be 0, state read/write to be 2100, state growth to be 20000, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogSstoreColdZeroToNonZeroValue(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreColdZeroToNonZero) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + sstoreLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "SSTORE") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.SstoreSetGasEIP2200 + params.ColdSloadCostEIP2929, + Computation: 0, + StateAccess: params.ColdSloadCostEIP2929, + StateGrowth: params.SstoreSetGasEIP2200, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch(t, expected, sstoreLog) + checkGasDimensionsEqualOneDimensionalGas(t, sstoreLog) +} + +// This test deployes a contract with a local variable that we can SSTORE to +// in this test, we SSTORE a variable that starts at a non-zero value and SSTORE it to 0 +// +// we expect the gas cost of this operation to be 5000 and a gas refund of 4800. +// this is from an sstore cost of 2900, and a cold access set cost of 2100. +// we expect computation to be 100, state read/write to be 0, state growth to be 4900, +// history growth to be 0, and state growth refund to be 4800 +func TestDimLogSstoreColdNonZeroValueToZero(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreColdNonZeroValueToZero) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + sstoreLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "SSTORE") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.SstoreResetGasEIP2200, + Computation: 0, + StateAccess: params.SstoreResetGasEIP2200, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: int64(params.NetSstoreResetRefund), + } + checkGasDimensionsMatch(t, expected, sstoreLog) + checkGasDimensionsEqualOneDimensionalGas(t, sstoreLog) +} + +// This test deployes a contract with a local variable that we can SSTORE to +// in this test, we SSTORE a variable that starts at a non-zero value and SSTORE it to +// the same non-zero value +// +// we expect the gas cost of this operation to be 2200, 100 for the base sstore cost, +// and 2100 for cold access set access. +// we expect computation to be 0, state read/write to be 2200, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogSstoreColdNonZeroToSameNonZeroValue(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreColdNonZeroToSameNonZeroValue) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + sstoreLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "SSTORE") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdSloadCostEIP2929 + params.WarmStorageReadCostEIP2929, + Computation: 0, + StateAccess: params.ColdSloadCostEIP2929 + params.WarmStorageReadCostEIP2929, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch(t, expected, sstoreLog) + checkGasDimensionsEqualOneDimensionalGas(t, sstoreLog) +} + +// This test deployes a contract with a local variable that we can SSTORE to +// in this test, we SSTORE a variable that starts at a non-zero value and SSTORE it to +// a different non-zero value +// +// we expect the gas cost of this operation to be 5000, 2900 for the sstore cost, +// and 2100 for cold access set access. +// we expect computation to be 0, state read/write to be 5000, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogSstoreColdNonZeroToDifferentNonZeroValue(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreColdNonZeroToDifferentNonZeroValue) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + sstoreLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "SSTORE") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.SstoreClearGas, + Computation: 0, + StateAccess: params.SstoreClearGas, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch(t, expected, sstoreLog) + checkGasDimensionsEqualOneDimensionalGas(t, sstoreLog) +} + +// This test deployes a contract with a local variable that we can SSTORE to +// in this test, we SSTORE a variable that starts at 0 and SSTORE it to 0 +// +// we expect the gas cost of this operation to be 100, 100 for the base sstore cost, +// we expect computation to be 0, state access to be 100, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogSstoreWarmZeroToZero(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreWarmZeroToZero) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + sstoreLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "SSTORE") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: 0, + StateAccess: params.WarmStorageReadCostEIP2929, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch(t, expected, sstoreLog) + checkGasDimensionsEqualOneDimensionalGas(t, sstoreLog) +} + +// This test deployes a contract with a local variable that we can SSTORE to +// in this test, we SSTORE a variable that starts at 0 and SSTORE it to a non-zero value +// +// we expect the gas cost of this operation to be 20000, 20000 for the sstore cost, +// we expect computation to be 0, state access to be 0, state growth to be 20000, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogSstoreWarmZeroToNonZeroValue(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreWarmZeroToNonZeroValue) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + sstoreLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "SSTORE") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.SstoreSetGasEIP2200, + Computation: 0, + StateAccess: 0, + StateGrowth: params.SstoreSetGasEIP2200, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch(t, expected, sstoreLog) + checkGasDimensionsEqualOneDimensionalGas(t, sstoreLog) +} + +// This test deployes a contract with a local variable that we can SSTORE to +// in this test, we SSTORE a variable that starts at a non-zero value and SSTORE it to 0 +// +// we expect the gas cost of this operation to be 2900, with a gas refund of 4800 +// This is 2900 just for the sstore cost +// we expect computation to be 0, state read/write to be 2900, state growth to be 0, +// history growth to be 0, and state growth refund to be 4800 +func TestDimLogSstoreWarmNonZeroValueToZero(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreWarmNonZeroValueToZero) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + sstoreLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "SSTORE") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: 2900, // there's no params reference for this, it just is 2900 + Computation: 0, + StateAccess: 2900, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: int64(params.NetSstoreResetRefund), + } + checkGasDimensionsMatch(t, expected, sstoreLog) + checkGasDimensionsEqualOneDimensionalGas(t, sstoreLog) +} + +// This test deployes a contract with a local variable that we can SSTORE to +// in this test, we SSTORE a variable that starts at a non-zero value and SSTORE it to +// the same non-zero value +// +// we expect the gas cost of this operation to be 100 for the base sstore cost, +// we expect computation to be 0, state read/write to be 100, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogSstoreWarmNonZeroToSameNonZeroValue(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreWarmNonZeroToSameNonZeroValue) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + sstoreLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "SSTORE") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: 0, + StateAccess: params.WarmStorageReadCostEIP2929, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch(t, expected, sstoreLog) + checkGasDimensionsEqualOneDimensionalGas(t, sstoreLog) +} + +// This test deployes a contract with a local variable that we can SSTORE to +// in this test, we SSTORE a variable that starts at a non-zero value and SSTORE it to +// a different non-zero value +// +// we expect the gas cost of this operation to be 2900 for the base sstore cost, +// we expect computation to be 0, state read/write to be 2900, state growth to be 0, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogSstoreWarmNonZeroToDifferentNonZeroValue(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreWarmNonZeroToDifferentNonZeroValue) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + sstoreLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "SSTORE") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: 2900, // there's no params reference for this, it just is 2900 + Computation: 0, + StateAccess: 2900, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch(t, expected, sstoreLog) + checkGasDimensionsEqualOneDimensionalGas(t, sstoreLog) +} + +// This test deployes a contract with a local variable that we can SSTORE to +// we test multiple SSTOREs and the interaction for the second SSTORE to an already changed value +// in this test we test a change where the first SSTORE changed a value from non zero to non zero +// in the past, and now we're evaluating the cost for this second sstore which is changing the value +// again, to a different non zero value +// for example, changing some value from 2->3->4 +// +// we expect the gas cost of this operation to be 100 for the base sstore cost, +// we expect computation to be 0, state read/write to be 0, state growth to be 100, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogSstoreMultipleWarmNonZeroToNonZeroToNonZero(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreMultipleWarmNonZeroToNonZeroToNonZero) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + sstoreLog := getLastOfTwoDimensionLogs(t, traceResult.DimensionLogs, "SSTORE") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: 0, + StateAccess: params.WarmStorageReadCostEIP2929, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch(t, expected, sstoreLog) + checkGasDimensionsEqualOneDimensionalGas(t, sstoreLog) +} + +// This test deployes a contract with a local variable that we can SSTORE to +// we test multiple SSTOREs and the interaction for the second SSTORE to an already changed value +// in this test we test a change where the first SSTORE changed a value from non zero to non zero +// in the past, and now we're evaluating the cost for this second sstore which is changing the value +// again, to the same non zero value +// for example, changing some value from 2->3->2 +// +// we expect the gas cost of this operation to be 100 for the base sstore cost, +// and to get a gas refund of 2800 +// we expect computation to be 0, state read/write to be 0, state growth to be 100, +// history growth to be 0, and state growth refund to be 2800 +func TestDimLogSstoreMultipleWarmNonZeroToNonZeroToSameNonZero(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreMultipleWarmNonZeroToNonZeroToSameNonZero) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + sstoreLog := getLastOfTwoDimensionLogs(t, traceResult.DimensionLogs, "SSTORE") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: 0, + StateAccess: params.WarmStorageReadCostEIP2929, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 2800, // i didn't see anything in params directly for this case + } + checkGasDimensionsMatch(t, expected, sstoreLog) + checkGasDimensionsEqualOneDimensionalGas(t, sstoreLog) +} + +// This test deployes a contract with a local variable that we can SSTORE to +// we test multiple SSTOREs and the interaction for the second SSTORE to an already changed value +// in this test we test a change where the first SSTORE changed a value from non zero to zero +// in the past, and now we're evaluating the cost for this second sstore which is changing the value +// again, to a non zero value +// for example, changing some value from 2->0->3 +// +// we expect the gas cost of this operation to be 100 for the base sstore cost, +// and to have a NEGATIVE gas refund of -4800, i.e. taking away from the previous gas refund +// that was granted for changing the sstore from non zero to zero +// we expect computation to be 0, state read/write to be 0, state growth to be 100, +// history growth to be 0, and state growth refund to be -4800 +func TestDimLogSstoreMultipleWarmNonZeroToZeroToNonZero(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreMultipleWarmNonZeroToZeroToNonZero) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + sstoreLog := getLastOfTwoDimensionLogs(t, traceResult.DimensionLogs, "SSTORE") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: 0, + StateAccess: params.WarmStorageReadCostEIP2929, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: -4800, + } + checkGasDimensionsMatch(t, expected, sstoreLog) + checkGasDimensionsEqualOneDimensionalGas(t, sstoreLog) +} + +// This test deployes a contract with a local variable that we can SSTORE to +// we test multiple SSTOREs and the interaction for the second SSTORE to an already changed value +// in this test we test a change where the first SSTORE changed a value from non zero to zero +// in the past, and now we're evaluating the cost for this second sstore which is changing the value +// again, to the same non zero value +// for example, changing some value from 2->0->2 +// +// we expect the gas cost of this operation to be 100 for the base sstore cost, +// and to have a NEGATIVE gas refund of -2000, i.e. taking away from the previous gas refund +// that was granted for changing the sstore from non zero to zero +// we expect computation to be 0, state read/write to be 0, state growth to be 100, +// history growth to be 0, and state growth refund to be -2000 +func TestDimLogSstoreMultipleWarmNonZeroToZeroToSameNonZero(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreMultipleWarmNonZeroToZeroToSameNonZero) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + sstoreLog := getLastOfTwoDimensionLogs(t, traceResult.DimensionLogs, "SSTORE") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: 0, + StateAccess: params.WarmStorageReadCostEIP2929, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: -2000, + } + checkGasDimensionsMatch(t, expected, sstoreLog) + checkGasDimensionsEqualOneDimensionalGas(t, sstoreLog) +} + +// This test deployes a contract with a local variable that we can SSTORE to +// we test multiple SSTOREs and the interaction for the second SSTORE to an already changed value +// in this test we test a change where the first SSTORE changed a value from zero to non zero +// in the past, and now we're evaluating the cost for this second sstore which is changing the value +// again, to a different non zero value +// for example, changing some value from 0->2->3 +// +// We expect the gas cost of this operation to be 100 for the base sstore cost, +// we expect computation to be 0, state read/write to be 0, state growth to be 100, +// history growth to be 0, and state growth refund to be 0 +func TestDimLogSstoreMultipleWarmZeroToNonZeroToNonZero(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreMultipleWarmZeroToNonZeroToNonZero) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + sstoreLog := getLastOfTwoDimensionLogs(t, traceResult.DimensionLogs, "SSTORE") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: 0, + StateAccess: params.WarmStorageReadCostEIP2929, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch(t, expected, sstoreLog) + checkGasDimensionsEqualOneDimensionalGas(t, sstoreLog) +} + +// This test deployes a contract with a local variable that we can SSTORE to +// we test multiple SSTOREs and the interaction for the second SSTORE to an already changed value +// in this test we test a change where the first SSTORE changed a value from zero to non zero +// in the past, and now we're evaluating the cost for this second sstore which is changing the value +// again, back to zero +// for example, changing some value from 0->3->0 +// +// We expect the gas cost of this operation to be 100 for the base sstore cost, +// we expect to get a gas refund of 19900 +// we expect computation to be 0, state read/write to be 0, state growth to be 100, +// history growth to be 0, and state growth refund to be 19900 +func TestDimLogSstoreMultipleWarmZeroToNonZeroBackToZero(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreMultipleWarmZeroToNonZeroBackToZero) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + sstoreLog := getLastOfTwoDimensionLogs(t, traceResult.DimensionLogs, "SSTORE") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: 0, + StateAccess: params.WarmStorageReadCostEIP2929, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 19900, + } + checkGasDimensionsMatch(t, expected, sstoreLog) + checkGasDimensionsEqualOneDimensionalGas(t, sstoreLog) +} diff --git a/system_tests/gas_dim_log_state_lookup_test.go b/system_tests/gas_dim_log_state_lookup_test.go new file mode 100644 index 0000000000..6192ce710d --- /dev/null +++ b/system_tests/gas_dim_log_state_lookup_test.go @@ -0,0 +1,300 @@ +package arbtest + +import ( + "testing" + + "github.com/ethereum/go-ethereum/params" + + "github.com/offchainlabs/nitro/solgen/go/gas_dimensionsgen" +) + +const ColdSloadCost = params.ColdSloadCostEIP2929 + +// ######################################################################################################### +// ######################################################################################################### +// SIMPLE STATE ACCESS OPCODES (BALANCE, EXTCODESIZE, EXTCODEHASH) +// ######################################################################################################### +// ######################################################################################################### +// BALANCE, EXTCODESIZE, EXTCODEHASH are all read-only operations on state access +// They only operate on one axis: +// Warm vs Cold (in the access list) + +// ############################################################ +// BALANCE +// ############################################################ + +// this test deployes a contract that calls BALANCE on a cold access list address +// +// on the cold BALANCE, we expect the total one-dimensional gas cost to be 2600 +// the computation to be 100 (for the warm access cost of the address) +// and the state access to be 2500 (for the cold access cost of the address) +// and all other gas dimensions to be 0 +func TestDimLogBalanceCold(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployBalance) + _, receipt := callOnContract(t, builder, auth, contract.CallBalanceCold) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + balanceLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "BALANCE") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + balanceLog, + ) + checkGasDimensionsEqualOneDimensionalGas(t, balanceLog) +} + +// this test deployes a contract that calls BALANCE on a warm access list address +// +// on the warm BALANCE, we expect the total one-dimensional gas cost to be 100 +// the computation to be 100 (for the warm access cost of the address) +// and all other gas dimensions to be 0 +func TestDimLogBalanceWarm(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployBalance) + _, receipt := callOnContract(t, builder, auth, contract.CallBalanceWarm) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + balanceLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "BALANCE") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + balanceLog, + ) + checkGasDimensionsEqualOneDimensionalGas(t, balanceLog) +} + +// ############################################################ +// EXTCODESIZE +// ############################################################ + +// this test deployes a contract that calls EXTCODESIZE on a cold access list address +// +// on the cold EXTCODESIZE, we expect the total one-dimensional gas cost to be 2600 +// the computation to be 100 (for the warm access cost of the address) +// and the state access to be 2500 (for the cold access cost of the address) +// and all other gas dimensions to be 0 +func TestDimLogExtCodeSizeCold(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployExtCodeSize) + _, receipt := callOnContract(t, builder, auth, contract.GetExtCodeSizeCold) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + extCodeSizeLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "EXTCODESIZE") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + extCodeSizeLog, + ) + checkGasDimensionsEqualOneDimensionalGas(t, extCodeSizeLog) +} + +// this test deployes a contract that calls EXTCODESIZE on a warm access list address +// +// on the warm EXTCODESIZE, we expect the total one-dimensional gas cost to be 100 +// the computation to be 100 (for the warm access cost of the address) +// and all other gas dimensions to be 0 +func TestDimLogExtCodeSizeWarm(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployExtCodeSize) + _, receipt := callOnContract(t, builder, auth, contract.GetExtCodeSizeWarm) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + extCodeSizeLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "EXTCODESIZE") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + extCodeSizeLog, + ) + checkGasDimensionsEqualOneDimensionalGas(t, extCodeSizeLog) +} + +// ############################################################ +// EXTCODEHASH +// ############################################################ + +// this test deployes a contract that calls EXTCODEHASH on a cold access list address +// +// on the cold EXTCODEHASH, we expect the total one-dimensional gas cost to be 2600 +// the computation to be 100 (for the warm access cost of the address) +// and the state access to be 2500 (for the cold access cost of the address) +// and all other gas dimensions to be 0 +func TestDimLogExtCodeHashCold(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployExtCodeHash) + _, receipt := callOnContract(t, builder, auth, contract.GetExtCodeHashCold) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + extCodeHashLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "EXTCODEHASH") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.ColdAccountAccessCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: ColdMinusWarmAccountAccessCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + extCodeHashLog, + ) + checkGasDimensionsEqualOneDimensionalGas(t, extCodeHashLog) +} + +// this test deployes a contract that calls EXTCODEHASH on a warm access list address +// +// on the warm EXTCODEHASH, we expect the total one-dimensional gas cost to be 100 +// the computation to be 100 (for the warm access cost of the address) +// and all other gas dimensions to be 0 +func TestDimLogExtCodeHashWarm(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployExtCodeHash) + _, receipt := callOnContract(t, builder, auth, contract.GetExtCodeHashWarm) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + extCodeHashLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "EXTCODEHASH") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + extCodeHashLog, + ) + checkGasDimensionsEqualOneDimensionalGas(t, extCodeHashLog) +} + +// ############################################################ +// SLOAD +// ############################################################ +// SLOAD has only one axis of variation: +// Warm vs Cold (in the access list) + +// In this test we deploy a contract with a function that all it does +// is perform an sload on a cold slot that has not been touched yet +// +// on the cold sload, we expect the total one-dimensional gas cost to be 2100 +// the computation to be 100 (for the warm base access cost) +// the state access to be 2000 (for the cold sload cost) +// all others zero +func TestDimLogSloadCold(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySload) + _, receipt := callOnContract(t, builder, auth, contract.ColdSload) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + sloadLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "SLOAD") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: ColdSloadCost, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: ColdMinusWarmSloadCost, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + sloadLog, + ) + checkGasDimensionsEqualOneDimensionalGas(t, sloadLog) +} + +// In this test we deploy a contract with a function that all it does +// is perform an sload on an already warm slot (by SSTORE-ing to the slot first) +// +// on the warm sload, we expect the total one-dimensional gas cost to be 100 +// the computation to be 100 (for the warm base access cost) +// all others zero +func TestDimLogSloadWarm(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySload) + _, receipt := callOnContract(t, builder, auth, contract.WarmSload) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + sloadLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "SLOAD") + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + } + checkGasDimensionsMatch( + t, + expected, + sloadLog, + ) + checkGasDimensionsEqualOneDimensionalGas(t, sloadLog) +} diff --git a/system_tests/gas_dim_tx_op_a_common_test.go b/system_tests/gas_dim_tx_op_a_common_test.go new file mode 100644 index 0000000000..c6ddd1a0cc --- /dev/null +++ b/system_tests/gas_dim_tx_op_a_common_test.go @@ -0,0 +1,130 @@ +package arbtest + +import ( + "context" + "encoding/json" + "fmt" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/tracers/native" + + "github.com/offchainlabs/nitro/solgen/go/gas_dimensionsgen" +) + +type OpcodeSumTraceResult = native.TxGasDimensionByOpcodeExecutionResult + +// ######################################################################################################### +// ######################################################################################################### +// REGULAR COMPUTATION OPCODES (ADD, SWAP, ETC) +// ######################################################################################################### +// ######################################################################################################### + +// this test tests if the tracer calculates the same gas +// used for a transaction as the TX receipt, for +// computation-only opcodes. +func TestDimTxOpComputationOnlyOpcodes(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCounter) + _, receipt := callOnContract(t, builder, auth, contract.NoSpecials) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// ######################################################################################################### +// ######################################################################################################### +// HELPER FUNCTIONS +// ######################################################################################################### +// ######################################################################################################### + +func callDebugTraceTransactionWithTxGasDimensionByOpcodeTracer( + t *testing.T, + ctx context.Context, + builder *NodeBuilder, + txHash common.Hash, +) OpcodeSumTraceResult { + t.Helper() + // Call debug_traceTransaction with txGasDimensionLogger tracer + rpcClient := builder.L2.ConsensusNode.Stack.Attach() + var result json.RawMessage + err := rpcClient.CallContext(ctx, &result, "debug_traceTransaction", txHash, map[string]interface{}{ + "tracer": "txGasDimensionByOpcode", + }) + Require(t, err) + + // Parse the result + var traceResult OpcodeSumTraceResult + if err := json.Unmarshal(result, &traceResult); err != nil { + Fatal(t, err) + } + + // Validate basic structure + if traceResult.GasUsed == 0 { + Fatal(t, "Expected non-zero gas usage") + } + if traceResult.GasUsedForL1 == 0 { + Fatal(t, "Expected non-zero gas usage for L1") + } + if traceResult.GasUsedForL2 == 0 { + Fatal(t, "Expected non-zero gas usage for L2") + } + if traceResult.IntrinsicGas == 0 { + Fatal(t, "Expected non-zero intrinsic gas") + } + if traceResult.Failed { + Fatal(t, "Transaction should not have failed") + } + txHashHex := txHash.Hex() + if traceResult.TxHash != txHashHex { + Fatal(t, "Expected txHash %s, got %s", txHashHex, traceResult.TxHash) + } + if len(traceResult.Dimensions) == 0 { + Fatal(t, "Expected non-empty dimension summary map") + } + return traceResult +} + +func sumUpDimensionalGasCosts( + t *testing.T, + gasesByDimension map[string]native.GasesByDimension, + intrinsicGas uint64, + adjustedRefund uint64, +) (oneDimensionSum, allDimensionsSum uint64) { + t.Helper() + oneDimensionSum = intrinsicGas + allDimensionsSum = intrinsicGas + for _, gasByDimension := range gasesByDimension { + oneDimensionSum += gasByDimension.OneDimensionalGasCost + allDimensionsSum += gasByDimension.Computation + gasByDimension.StateAccess + gasByDimension.StateGrowth + gasByDimension.HistoryGrowth + } + if adjustedRefund > oneDimensionSum { + Fatal(t, "adjustedRefund should never be greater than oneDimensionSum: %d > %d", adjustedRefund, oneDimensionSum) + } + oneDimensionSum -= adjustedRefund + if adjustedRefund > allDimensionsSum { + Fatal(t, "adjustedRefund should never be greater than allDimensionsSum: %d > %d", adjustedRefund, allDimensionsSum) + } + allDimensionsSum -= adjustedRefund + return oneDimensionSum, allDimensionsSum +} + +// basically all of the TxOp tests do the same checks, the only difference is the setup. +func TxOpTraceAndCheck(t *testing.T, ctx context.Context, builder *NodeBuilder, receipt *types.Receipt) { + traceResult := callDebugTraceTransactionWithTxGasDimensionByOpcodeTracer(t, ctx, builder, receipt.TxHash) + + CheckEqual(t, receipt.GasUsed, traceResult.GasUsed) + CheckEqual(t, receipt.GasUsedForL1, traceResult.GasUsedForL1) + CheckEqual(t, receipt.GasUsedForL2(), traceResult.GasUsedForL2) + + expectedGasUsed := receipt.GasUsedForL2() + sumOneDimensionalGasCosts, sumAllGasCosts := sumUpDimensionalGasCosts(t, traceResult.Dimensions, traceResult.IntrinsicGas, traceResult.AdjustedRefund) + + CheckEqual(t, expectedGasUsed, sumOneDimensionalGasCosts, + fmt.Sprintf("expected gas used: %d, one-dim used: %d\nDimensions:\n%v", expectedGasUsed, sumOneDimensionalGasCosts, traceResult.Dimensions)) + CheckEqual(t, expectedGasUsed, sumAllGasCosts, + fmt.Sprintf("expected gas used: %d, all used: %d\nDimensions:\n%v", expectedGasUsed, sumAllGasCosts, traceResult.Dimensions)) +} diff --git a/system_tests/gas_dim_tx_op_call_test.go b/system_tests/gas_dim_tx_op_call_test.go new file mode 100644 index 0000000000..58ad972dd7 --- /dev/null +++ b/system_tests/gas_dim_tx_op_call_test.go @@ -0,0 +1,937 @@ +package arbtest + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + + "github.com/offchainlabs/nitro/solgen/go/gas_dimensionsgen" +) + +// this file tests the opcodes that are able to do write-operation CALLs +// looking for STATICCALL and DELEGATECALL? check out gas_dim_log_read_call_test.go + +// ######################################################################################################### +// ######################################################################################################### +// CALL and CALLCODE +// ######################################################################################################### +// ######################################################################################################### +// +// CALL and CALLCODE have many permutations /axes of variation +// +// Warm vs Cold (in the access list) +// Paying vs NoTransfer (is ether value being sent with this call?) +// Contract vs NoCode (is there code at the target address that we are calling to? or is it an EOA?) +// Virgin vs Funded (has the target address ever been seen before / is the account already in the accounts tree?) +// MemExpansion or MemUnchanged (memory expansion or not) + +// ######################################################################################################### +// ######################################################################################################### +// CALL +// ######################################################################################################### +// ######################################################################################################### + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a cold, virgin, no-code callee, no value transfer, no memory expansion. +func TestDimTxOpCallColdNoTransferNoCodeVirginMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemUnchanged, noCodeVirginAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a cold, virgin, no-code callee, no value transfer, with memory expansion. +func TestDimTxOpCallColdNoTransferNoCodeVirginMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemExpansion, noCodeVirginAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a cold, funded, no-code callee, no value transfer, no memory expansion. +func TestDimTxOpCallColdNoTransferNoCodeFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // prefund callee address + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemUnchanged, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a cold, funded, no-code callee, no value transfer, with memory expansion. +func TestDimTxOpCallColdNoTransferNoCodeFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // prefund callee address + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemExpansion, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a cold, funded, contract callee, no value transfer, no memory expansion. +func TestDimTxOpCallColdNoTransferContractFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallee) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemUnchanged, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a cold, funded, contract callee, no value transfer, with memory expansion. +func TestDimTxOpCallColdNoTransferContractFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallee) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemExpansion, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a cold, virgin, no-code callee, with value transfer, no memory expansion. +func TestDimTxOpCallColdPayingNoCodeVirginMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + // prefund the caller with some funds + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemUnchanged, noCodeVirginAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a cold, virgin, no-code callee, with value transfer, with memory expansion. +func TestDimTxOpCallColdPayingNoCodeVirginMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + // prefund the caller with some funds + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemExpansion, noCodeVirginAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a cold, funded, no-code callee, with value transfer, no memory expansion. +func TestDimTxOpCallColdPayingNoCodeFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + // prefund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + noCodeFundedAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, _ = builder.L2.TransferBalanceTo(t, "Owner", noCodeFundedAddress, big.NewInt(1e17), builder.L2Info) + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemUnchanged, noCodeFundedAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a cold, funded, no-code callee, with value transfer, with memory expansion. +func TestDimTxOpCallColdPayingNoCodeFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + // prefund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + noCodeFundedAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, _ = builder.L2.TransferBalanceTo(t, "Owner", noCodeFundedAddress, big.NewInt(1e17), builder.L2Info) + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemExpansion, noCodeFundedAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a cold, funded, contract callee, with value transfer, no memory expansion. +func TestDimTxOpCallColdPayingContractFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallee) + + // fund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // fund the callee address with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemUnchanged, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a cold, funded, contract callee, with value transfer, with memory expansion. +func TestDimTxOpCallColdPayingContractFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallee) + + // fund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // fund the callee address with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemExpansion, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a warm, virgin, no-code callee, no value transfer, no memory expansion. +func TestDimTxOpCallWarmNoTransferNoCodeVirginMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemUnchanged, noCodeVirginAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a warm, virgin, no-code callee, no value transfer, with memory expansion. +func TestDimTxOpCallWarmNoTransferNoCodeVirginMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemExpansion, noCodeVirginAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a warm, funded, no-code callee, no value transfer, no memory expansion. +func TestDimTxOpCallWarmNoTransferNoCodeFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // prefund callee address + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemUnchanged, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a warm, funded, no-code callee, no value transfer, with memory expansion. +func TestDimTxOpCallWarmNoTransferNoCodeFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // prefund callee address + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemExpansion, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a warm, funded, contract callee, no value transfer, no memory expansion. +func TestDimTxOpCallWarmNoTransferContractFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallee) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemUnchanged, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a warm, funded, contract callee, no value transfer, with memory expansion. +func TestDimTxOpCallWarmNoTransferContractFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallee) + + // fund the callee address with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemExpansion, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a warm, virgin, no-code callee, with value transfer, no memory expansion. +func TestDimTxOpCallWarmPayingNoCodeVirginMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + // prefund the caller with some funds + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemUnchanged, noCodeVirginAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a warm, virgin, no-code callee, with value transfer, with memory expansion. +func TestDimTxOpCallWarmPayingNoCodeVirginMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + // prefund the caller with some funds + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemExpansion, noCodeVirginAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a warm, funded, no-code callee, with value transfer, no memory expansion. +func TestDimTxOpCallWarmPayingNoCodeFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + + noCodeFundedAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + // prefund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // prefund the callee with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", noCodeFundedAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemUnchanged, noCodeFundedAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a warm, funded, no-code callee, with value transfer, with memory expansion. +func TestDimTxOpCallWarmPayingNoCodeFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + + noCodeFundedAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + // prefund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // prefund the callee with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", noCodeFundedAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemExpansion, noCodeFundedAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a warm, funded, contract callee, with value transfer, no memory expansion. +func TestDimTxOpCallWarmPayingContractFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallee) + + // prefund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // prefund the callee with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemUnchanged, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALL to a warm, funded, contract callee, with value transfer, with memory expansion. +func TestDimTxOpCallWarmPayingContractFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCaller) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallee) + + // fund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // fund the callee address with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemExpansion, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// ######################################################################################################### +// ######################################################################################################### +// CALLCODE +// ######################################################################################################### +// ######################################################################################################### +// CALLCODE is deprecated and also weird. +// It operates identically to DELEGATECALL but allows you to send funds with the call +// if you send those funds, it will send them to the library address rather than yourself. +// So it has a positive_value_cost just like CALL, +// but it does NOT have the positive_value_to_new_account cost (params.CallNewAccountGas) +// so all of the test cases for CALLCODE are identical to CALL, EXCEPT FOR +// the NoCodeVirgin test cases, where there is no cost to positive_value_to_new_account +// (which tbh sounds like an EVM mispricing bug) + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a cold, virgin, no-code callee, no value transfer, no memory expansion. +func TestDimTxOpCallCodeColdNoTransferNoCodeVirginMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemUnchanged, noCodeVirginAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a cold, virgin, no-code callee, no value transfer, with memory expansion. +func TestDimTxOpCallCodeColdNoTransferNoCodeVirginMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemExpansion, noCodeVirginAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a cold, funded, no-code callee, no value transfer, no memory expansion. +func TestDimTxOpCallCodeColdNoTransferNoCodeFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // prefund callee address + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemUnchanged, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a cold, funded, no-code callee, no value transfer, with memory expansion. +func TestDimTxOpCallCodeColdNoTransferNoCodeFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // prefund callee address + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemExpansion, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a cold, funded, contract callee, no value transfer, no memory expansion. +func TestDimTxOpCallCodeColdNoTransferContractFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCodee) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemUnchanged, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a cold, funded, contract callee, no value transfer, with memory expansion. +func TestDimTxOpCallCodeColdNoTransferContractFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCodee) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdNoTransferMemExpansion, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a cold, virgin, no-code callee, with value transfer, no memory expansion. +func TestDimTxOpCallCodeColdPayingNoCodeVirginMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + // prefund the caller with some funds + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemUnchanged, noCodeVirginAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a cold, virgin, no-code callee, with value transfer, with memory expansion. +func TestDimTxOpCallCodeColdPayingNoCodeVirginMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + // prefund the caller with some funds + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemExpansion, noCodeVirginAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a cold, funded, no-code callee, with value transfer, no memory expansion. +func TestDimTxOpCallCodeColdPayingNoCodeFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + // prefund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + noCodeFundedAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, _ = builder.L2.TransferBalanceTo(t, "Owner", noCodeFundedAddress, big.NewInt(1e17), builder.L2Info) + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemUnchanged, noCodeFundedAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a cold, funded, no-code callee, with value transfer, with memory expansion. +func TestDimTxOpCallCodeColdPayingNoCodeFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + // prefund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + noCodeFundedAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, _ = builder.L2.TransferBalanceTo(t, "Owner", noCodeFundedAddress, big.NewInt(1e17), builder.L2Info) + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemExpansion, noCodeFundedAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a cold, funded, contract callee, with value transfer, no memory expansion. +func TestDimTxOpCallCodeColdPayingContractFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCodee) + + // fund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // fund the callee address with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemUnchanged, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a cold, funded, contract callee, with value transfer, with memory expansion. +func TestDimTxOpCallCodeColdPayingContractFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCodee) + + // fund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // fund the callee address with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.ColdPayableMemExpansion, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a warm, virgin, no-code callee, no value transfer, no memory expansion. +func TestDimTxOpCallCodeWarmNoTransferNoCodeVirginMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemUnchanged, noCodeVirginAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a warm, virgin, no-code callee, no value transfer, with memory expansion. +func TestDimTxOpCallCodeWarmNoTransferNoCodeVirginMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemExpansion, noCodeVirginAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a warm, funded, no-code callee, no value transfer, no memory expansion. +func TestDimTxOpCallCodeWarmNoTransferNoCodeFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // prefund callee address + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemUnchanged, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a warm, funded, no-code callee, no value transfer, with memory expansion. +func TestDimTxOpCallCodeWarmNoTransferNoCodeFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // prefund callee address + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemExpansion, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a warm, funded, contract callee, no value transfer, no memory expansion. +func TestDimTxOpCallCodeWarmNoTransferContractFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCodee) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemUnchanged, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a warm, funded, contract callee, no value transfer, with memory expansion. +func TestDimTxOpCallCodeWarmNoTransferContractFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCodee) + + // fund the callee address with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmNoTransferMemExpansion, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a warm, virgin, no-code callee, with value transfer, no memory expansion. +func TestDimTxOpCallCodeWarmPayingNoCodeVirginMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + // prefund the caller with some funds + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemUnchanged, noCodeVirginAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a warm, virgin, no-code callee, with value transfer, with memory expansion. +func TestDimTxOpCallCodeWarmPayingNoCodeVirginMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + + noCodeVirginAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + // prefund the caller with some funds + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemExpansion, noCodeVirginAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a warm, funded, no-code callee, with value transfer, no memory expansion. +func TestDimTxOpCallCodeWarmPayingNoCodeFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + + noCodeFundedAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + // prefund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // prefund the callee with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", noCodeFundedAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemUnchanged, noCodeFundedAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a warm, funded, no-code callee, with value transfer, with memory expansion. +func TestDimTxOpCallCodeWarmPayingNoCodeFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + + noCodeFundedAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + // prefund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // prefund the callee with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", noCodeFundedAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemExpansion, noCodeFundedAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a warm, funded, contract callee, with value transfer, no memory expansion. +func TestDimTxOpCallCodeWarmPayingContractFundedMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCodee) + + // prefund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // prefund the callee with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemUnchanged, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CALLCODE to a warm, funded, contract callee, with value transfer, with memory expansion. +func TestDimTxOpCallCodeWarmPayingContractFundedMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + callerAddress, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCoder) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCallCodee) + + // fund the caller with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", callerAddress, big.NewInt(1e17), builder.L2Info) + // fund the callee address with some funds + _, _ = builder.L2.TransferBalanceTo(t, "Owner", calleeAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.WarmPayableMemExpansion, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} diff --git a/system_tests/gas_dim_tx_op_create_test.go b/system_tests/gas_dim_tx_op_create_test.go new file mode 100644 index 0000000000..260ae9323a --- /dev/null +++ b/system_tests/gas_dim_tx_op_create_test.go @@ -0,0 +1,151 @@ +package arbtest + +import ( + "math/big" + "testing" + + "github.com/offchainlabs/nitro/solgen/go/gas_dimensionsgen" +) + +// ######################################################################################################### +// ######################################################################################################### +// CREATE & CREATE2 +// ######################################################################################################### +// ######################################################################################################### +// CREATE and CREATE2 have permutations: +// Paying vs NoTransfer (is ether value being sent with this call?) +// MemExpansion vs MemUnchanged (does the creation write to new additional memory?) + +// ######################################################################################################### +// CREATE +// ######################################################################################################### + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CREATE operation with no value transfer and no memory expansion. +func TestDimTxOpCreateNoTransferMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, creator := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCreator) + + _, receipt := callOnContract(t, builder, auth, creator.CreateNoTransferMemUnchanged) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CREATE operation with no value transfer and memory expansion. +func TestDimTxOpCreateNoTransferMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, creator := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCreator) + + _, receipt := callOnContract(t, builder, auth, creator.CreateNoTransferMemExpansion) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CREATE operation with value transfer and no memory expansion. +func TestDimTxOpCreatePayingMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + creatorAddress, creator := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCreator) + // transfer some eth to the creator contract + _, _ = builder.L2.TransferBalanceTo(t, "Owner", creatorAddress, big.NewInt(1e17), builder.L2Info) + + _, receipt := callOnContract(t, builder, auth, creator.CreatePayableMemUnchanged) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CREATE operation with value transfer and memory expansion. +func TestDimTxOpCreatePayingMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + creatorAddress, creator := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCreator) + // transfer some eth to the creator contract + _, _ = builder.L2.TransferBalanceTo(t, "Owner", creatorAddress, big.NewInt(1e17), builder.L2Info) + + _, receipt := callOnContract(t, builder, auth, creator.CreatePayableMemExpansion) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// ######################################################################################################### +// CREATE2 +// ######################################################################################################### + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CREATE2 operation with no value transfer and no memory expansion. +func TestDimTxOpCreate2NoTransferMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, creator := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCreatorTwo) + + receipt := callOnContractWithOneArg(t, builder, auth, creator.CreateTwoNoTransferMemUnchanged, [32]byte{0x13, 0x37}) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CREATE2 operation with no value transfer and memory expansion. +func TestDimTxOpCreate2NoTransferMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, creator := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCreatorTwo) + + receipt := callOnContractWithOneArg(t, builder, auth, creator.CreateTwoNoTransferMemExpansion, [32]byte{0x13, 0x37}) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CREATE2 operation with value transfer and no memory expansion. +func TestDimTxOpCreate2PayingMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, creator := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCreatorTwo) + + receipt := callOnContractWithOneArg(t, builder, auth, creator.CreateTwoNoTransferMemUnchanged, [32]byte{0x13, 0x37}) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: CREATE2 operation with value transfer and memory expansion. +func TestDimTxOpCreate2PayingMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + creatorAddress, creator := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployCreatorTwo) + // transfer some eth to the creator contract + _, _ = builder.L2.TransferBalanceTo(t, "Owner", creatorAddress, big.NewInt(1e17), builder.L2Info) + + receipt := callOnContractWithOneArg(t, builder, auth, creator.CreateTwoPayableMemExpansion, [32]byte{0x13, 0x37}) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} diff --git a/system_tests/gas_dim_tx_op_extcodecopy_test.go b/system_tests/gas_dim_tx_op_extcodecopy_test.go new file mode 100644 index 0000000000..4da3df5faf --- /dev/null +++ b/system_tests/gas_dim_tx_op_extcodecopy_test.go @@ -0,0 +1,80 @@ +package arbtest + +import ( + "testing" + + "github.com/offchainlabs/nitro/solgen/go/gas_dimensionsgen" +) + +// ######################################################################################################### +// ######################################################################################################### +// EXTCODECOPY +// ######################################################################################################### +// ######################################################################################################### + +// EXTCODECOPY reads from state and copies code to memory +// for gas dimensions, we don't care about expanding memory, but +// we do care about the cost being correct +// EXTCODECOPY has two axes of variation: +// Warm vs Cold (in the access list) +// MemExpansion or MemUnchanged (memory expansion or not) + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: EXTCODECOPY with cold code access, no memory expansion. +// Expected: 2600 gas total (100 computation + 2500 state access + minimum word cost). +func TestDimTxOpExtCodeCopyColdMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployExtCodeCopy) + _, receipt := callOnContract(t, builder, auth, contract.ExtCodeCopyColdNoMemExpansion) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: EXTCODECOPY with cold code access and memory expansion. +// Expected: 2600 gas + memory expansion cost (100 computation + 2500 state access + minimum word cost + memory expansion). +func TestDimTxOpExtCodeCopyColdMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployExtCodeCopy) + _, receipt := callOnContract(t, builder, auth, contract.ExtCodeCopyColdMemExpansion) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: EXTCODECOPY with warm code access, no memory expansion. +// Expected: 100 gas total (100 computation + minimum word cost). +func TestDimTxOpExtCodeCopyWarmMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployExtCodeCopy) + _, receipt := callOnContract(t, builder, auth, contract.ExtCodeCopyWarmNoMemExpansion) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: EXTCODECOPY with warm code access and memory expansion. +// Expected: 100 gas + memory expansion cost (100 computation + minimum word cost + memory expansion). +func TestDimTxOpExtCodeCopyWarmMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployExtCodeCopy) + _, receipt := callOnContract(t, builder, auth, contract.ExtCodeCopyWarmMemExpansion) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} diff --git a/system_tests/gas_dim_tx_op_log_test.go b/system_tests/gas_dim_tx_op_log_test.go new file mode 100644 index 0000000000..54bb5952bf --- /dev/null +++ b/system_tests/gas_dim_tx_op_log_test.go @@ -0,0 +1,240 @@ +package arbtest + +import ( + "testing" + + "github.com/offchainlabs/nitro/solgen/go/gas_dimensionsgen" +) + +// ######################################################################################################### +// ######################################################################################################### +// LOG0, LOG1, LOG2, LOG3, LOG4 +// ######################################################################################################### +// ######################################################################################################### +// +// Logs are pretty straightforward, they have a static cost +// we assign to computation, a linear size cost we assign directly +// to history growth, and a topic data cost. The topic data cost +// we subdivide, because some of the topic cost needs to pay for the +// cryptography we do for the bloom filter. +// 32 bytes per topic are stored in the history for the topic count. +// since the other data is charged 8 gas per byte, then each of the 375 +// should in theory be charged 256 gas for the 32 bytes of topic data and 119 gas for computation +// so we subdivide the 375 gas per topic count into 256 for the topic data and 119 for the computation. +// +// LOG has the following axes of variation: +// Number of topics (0-4) +// TopicsOnly vs ExtraData Do we have any un-indexed extra data (data that is not part of the topics)? +// MemExpansion or MemUnchanged (memory expansion or not) +// The memory expansion depends on doing a read of data from memory, +// so TopicsOnly cases cannot cause memory expansion. + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: LOG0 with no topics, no data, no memory expansion. +func TestDimTxOpLog0TopicsOnlyMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitZeroTopicEmptyData) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: LOG0 with no topics, 7 bytes of data, no memory expansion. +func TestDimTxOpLog0ExtraDataMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitZeroTopicNonEmptyData) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: LOG1 with one topic, no data, no memory expansion. +func TestDimTxOpLog1TopicsOnlyMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitOneTopicEmptyData) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: LOG1 with one topic, 9 bytes of data, no memory expansion. +func TestDimTxOpLog1ExtraDataMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitOneTopicNonEmptyData) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: LOG2 with two topics, no data, no memory expansion. +func TestDimTxOpLog2TopicsOnlyMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitTwoTopics) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: LOG2 with two topics, 32 bytes of data (address), no memory expansion. +func TestDimTxOpLog2ExtraDataMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitTwoTopicsExtraData) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: LOG3 with three topics, no data, no memory expansion. +func TestDimTxOpLog3TopicsOnlyMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitThreeTopics) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: LOG3 with three topics, 32 bytes of data (bytes32), no memory expansion. +func TestDimTxOpLog3ExtraDataMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitThreeTopicsExtraData) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: LOG4 with four topics, no data, no memory expansion. +func TestDimTxOpLog4TopicsOnlyMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitFourTopics) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: LOG4 with four topics, 32 bytes of data (bytes32), no memory expansion. +func TestDimTxOpLog4ExtraDataMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitFourTopicsExtraData) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: LOG0 with no topics, 64 bytes of data, memory expansion from 96 to 160 bytes. +func TestDimTxOpLog0ExtraDataMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitZeroTopicNonEmptyDataAndMemExpansion) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: LOG1 with one topic, 64 bytes of data, memory expansion from 96 to 160 bytes. +func TestDimTxOpLog1ExtraDataMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitOneTopicNonEmptyDataAndMemExpansion) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: LOG2 with two topics, 64 bytes of data, memory expansion from 96 to 160 bytes. +func TestDimTxOpLog2ExtraDataMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitTwoTopicsExtraDataAndMemExpansion) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: LOG3 with three topics, 64 bytes of data, memory expansion from 96 to 160 bytes. +func TestDimTxOpLog3ExtraDataMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitThreeTopicsExtraDataAndMemExpansion) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: LOG4 with four topics, 64 bytes of data, memory expansion from 96 to 160 bytes. +func TestDimTxOpLog4ExtraDataMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployLogEmitter) + _, receipt := callOnContract(t, builder, auth, contract.EmitFourTopicsExtraDataAndMemExpansion) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} diff --git a/system_tests/gas_dim_tx_op_read_call_test.go b/system_tests/gas_dim_tx_op_read_call_test.go new file mode 100644 index 0000000000..c923019d4f --- /dev/null +++ b/system_tests/gas_dim_tx_op_read_call_test.go @@ -0,0 +1,280 @@ +package arbtest + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + + "github.com/offchainlabs/nitro/solgen/go/gas_dimensionsgen" +) + +// this file does the tests for the read-only call opcodes, i.e. DELEGATECALL and STATICCALL + +// ######################################################################################################### +// ######################################################################################################### +// DELEGATECALL & STATICCALL +// ######################################################################################################### +// ######################################################################################################### +// +// DELEGATECALL and STATICCALL have many permutations +// Warm vs Cold (in the access list) +// Contract vs NoCode (is there code at the target address that we are calling to? or is it an EOA?) +// MemExpansion or MemUnchanged (memory expansion or not) +// +// static_gas = 0 +// dynamic_gas = memory_expansion_cost + code_execution_cost + address_access_cost +// we do not consider the code_execution_cost as part of the cost of the call itself +// since those costs are counted and incurred by the children of the call. + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: DELEGATECALL to a cold, no-code address, no memory expansion. +func TestDimTxOpDelegateCallColdNoCodeMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, delegateCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCaller) + + receipt := callOnContractWithOneArg(t, builder, auth, delegateCaller.TestDelegateCallEmptyCold, emptyAccountAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: DELEGATECALL to a warm, no-code address, no memory expansion. +func TestDimTxOpDelegateCallWarmNoCodeMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, delegateCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCaller) + + receipt := callOnContractWithOneArg(t, builder, auth, delegateCaller.TestDelegateCallEmptyWarm, emptyAccountAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: DELEGATECALL to a cold contract address, no memory expansion. +func TestDimTxOpDelegateCallColdContractMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, delegateCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCaller) + delegateCalleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCallee) + + receipt := callOnContractWithOneArg(t, builder, auth, delegateCaller.TestDelegateCallNonEmptyCold, delegateCalleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: DELEGATECALL to a warm contract address, no memory expansion. +func TestDimTxOpDelegateCallWarmContractMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, delegateCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCaller) + delegateCalleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCallee) + + receipt := callOnContractWithOneArg(t, builder, auth, delegateCaller.TestDelegateCallNonEmptyWarm, delegateCalleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) + +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: DELEGATECALL to a cold, no-code address, with memory expansion. +func TestDimTxOpDelegateCallColdNoCodeMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, delegateCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCaller) + + receipt := callOnContractWithOneArg(t, builder, auth, delegateCaller.TestDelegateCallEmptyColdMemExpansion, emptyAccountAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: DELEGATECALL to a warm, no-code address, with memory expansion. +func TestDimTxOpDelegateCallWarmNoCodeMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, delegateCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCaller) + + receipt := callOnContractWithOneArg(t, builder, auth, delegateCaller.TestDelegateCallEmptyWarmMemExpansion, emptyAccountAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: DELEGATECALL to a cold contract address, with memory expansion. +func TestDimTxOpDelegateCallColdContractMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, delegateCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCaller) + delegateCalleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCallee) + + receipt := callOnContractWithOneArg(t, builder, auth, delegateCaller.TestDelegateCallNonEmptyColdMemExpansion, delegateCalleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: DELEGATECALL to a warm contract address, with memory expansion. +func TestDimTxOpDelegateCallWarmContractMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, delegateCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCaller) + delegateCalleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployDelegateCallee) + + receipt := callOnContractWithOneArg(t, builder, auth, delegateCaller.TestDelegateCallNonEmptyWarmMemExpansion, delegateCalleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: STATICCALL to a cold, no-code address, no memory expansion. +func TestDimTxOpStaticCallColdNoCodeMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, staticCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCaller) + + receipt := callOnContractWithOneArg(t, builder, auth, staticCaller.TestStaticCallEmptyCold, emptyAccountAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: STATICCALL to a warm, no-code address, no memory expansion. +func TestDimTxOpStaticCallWarmNoCodeMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, staticCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCaller) + + receipt := callOnContractWithOneArg(t, builder, auth, staticCaller.TestStaticCallEmptyWarm, emptyAccountAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: STATICCALL to a cold contract address, no memory expansion. +func TestDimTxOpStaticCallColdContractMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, staticCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCaller) + staticCalleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCallee) + receipt := callOnContractWithOneArg(t, builder, auth, staticCaller.TestStaticCallNonEmptyCold, staticCalleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: STATICCALL to a warm contract address, no memory expansion. +func TestDimTxOpStaticCallWarmContractMemUnchanged(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, staticCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCaller) + staticCalleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCallee) + receipt := callOnContractWithOneArg(t, builder, auth, staticCaller.TestStaticCallNonEmptyWarm, staticCalleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: STATICCALL to a cold, no-code address, with memory expansion. +func TestDimTxOpStaticCallColdNoCodeMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, staticCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCaller) + + receipt := callOnContractWithOneArg(t, builder, auth, staticCaller.TestStaticCallEmptyColdMemExpansion, emptyAccountAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: STATICCALL to a warm, no-code address, with memory expansion. +func TestDimTxOpStaticCallWarmNoCodeMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + _, staticCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCaller) + + receipt := callOnContractWithOneArg(t, builder, auth, staticCaller.TestStaticCallEmptyWarmMemExpansion, emptyAccountAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: STATICCALL to a cold contract address, with memory expansion. +func TestDimTxOpStaticCallColdContractMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, staticCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCaller) + staticCalleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCallee) + receipt := callOnContractWithOneArg(t, builder, auth, staticCaller.TestStaticCallNonEmptyColdMemExpansion, staticCalleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: STATICCALL to a warm contract address, with memory expansion. +func TestDimTxOpStaticCallWarmContractMemExpansion(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, staticCaller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCaller) + staticCalleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployStaticCallee) + receipt := callOnContractWithOneArg(t, builder, auth, staticCaller.TestStaticCallNonEmptyWarmMemExpansion, staticCalleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} diff --git a/system_tests/gas_dim_tx_op_selfdestruct_test.go b/system_tests/gas_dim_tx_op_selfdestruct_test.go new file mode 100644 index 0000000000..baca3ab1b0 --- /dev/null +++ b/system_tests/gas_dim_tx_op_selfdestruct_test.go @@ -0,0 +1,189 @@ +package arbtest + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + + "github.com/offchainlabs/nitro/solgen/go/gas_dimensionsgen" +) + +// ######################################################################################################### +// ######################################################################################################### +// SELFDESTRUCT +// ######################################################################################################### +// ######################################################################################################### +// +// SELFDESTRUCT has many permutations +// Warm vs Cold (in the access list) +// Paying vs NoTransfer (is ether value being sent with this call?) +// Virgin vs Funded (has the target address ever been seen before / does it have code already / is the account already in the accounts tree?) +// +// `value_to_empty_account_cost` is storage growth (25000) +// `address_access_cost` for cold addresses is a read/write cost +// since all this operation does is send ether at this point +// then we assign the static gas of 5000 to read/write, +// since in the non-state-growth case this would be a read/write +// and the access cost is read/write +// in the case where state growth happens due to sending funds to +// a new empty account, then we assign that to state growth. + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: SELFDESTRUCT to a cold, virgin address, no value transfer. +func TestDimTxOpSelfdestructColdNoTransferVirgin(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, selfDestructor := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySelfDestructor) + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // call selfDestructor.warmSelfDestructor(0xdeadbeef) + receipt := callOnContractWithOneArg(t, builder, auth, selfDestructor.SelfDestruct, emptyAccountAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: SELFDESTRUCT to a cold, funded address, no value transfer. +func TestDimTxOpSelfdestructColdNoTransferFunded(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, selfDestructor := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySelfDestructor) + payableCounterAddress, _ /*payableCounter*/ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployPayableCounter) + + // prefund the selfDestructor and payableCounter with some funds + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", payableCounterAddress, big.NewInt(1e17), builder.L2Info) + + // call selfDestructor.warmSelfDestructor(payableCounterAddress) + receipt := callOnContractWithOneArg(t, builder, auth, selfDestructor.SelfDestruct, payableCounterAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: SELFDESTRUCT to a cold, virgin address with value transfer. +func TestDimTxOpSelfdestructColdPayingVirgin(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + selfDestructorAddress, selfDestructor := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySelfDestructor) + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", selfDestructorAddress, big.NewInt(1e17), builder.L2Info) + + // call selfDestructor.SelfDestruct(emptyAccountAddress) - which is cold + receipt := callOnContractWithOneArg(t, builder, auth, selfDestructor.SelfDestruct, emptyAccountAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: SELFDESTRUCT to a cold, funded address with value transfer. +func TestDimTxOpSelfdestructColdPayingFunded(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + selfDestructorAddress, selfDestructor := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySelfDestructor) + emptyAccount := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // send some money to the self destructor address ahead of time + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", selfDestructorAddress, big.NewInt(1e17), builder.L2Info) + + // call selfDestructor.SelfDestruct(emptyAccountAddress) - which is cold + receipt := callOnContractWithOneArg(t, builder, auth, selfDestructor.SelfDestruct, emptyAccount) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: SELFDESTRUCT to a warm, virgin address, no value transfer. +func TestDimTxOpSelfdestructWarmNoTransferVirgin(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, selfDestructor := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySelfDestructor) + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // call selfDestructor.warmSelfDestructor(0xdeadbeef) + receipt := callOnContractWithOneArg(t, builder, auth, selfDestructor.WarmEmptySelfDestructor, emptyAccountAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: SELFDESTRUCT to a warm, funded address, no value transfer. +func TestDimTxOpSelfdestructWarmNoTransferFunded(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _ /*selfDestructorAddress*/, selfDestructor := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySelfDestructor) + payableCounterAddress, _ /*payableCounter*/ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployPayableCounter) + + // prefund the payableCounter with some funds, but not the selfDestructor + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", payableCounterAddress, big.NewInt(1e17), builder.L2Info) + + // call selfDestructor.warmSelfDestructor(payableCounterAddress) + receipt := callOnContractWithOneArg(t, builder, auth, selfDestructor.WarmSelfDestructor, payableCounterAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: SELFDESTRUCT to a warm, virgin address with value transfer. +func TestDimTxOpSelfdestructWarmPayingVirgin(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + selfDestructorAddress, selfDestructor := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySelfDestructor) + emptyAccountAddress := common.HexToAddress("0x00000000000000000000000000000000DeaDBeef") + + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", selfDestructorAddress, big.NewInt(1e17), builder.L2Info) + + // call selfDestructor.warmSelfDestructor(0xdeadbeef) + receipt := callOnContractWithOneArg(t, builder, auth, selfDestructor.WarmEmptySelfDestructor, emptyAccountAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: SELFDESTRUCT to a warm, funded address with value transfer. +func TestDimTxOpSelfdestructWarmPayingFunded(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + selfDestructorAddress, selfDestructor := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySelfDestructor) + payableCounterAddress, _ /*payableCounter*/ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployPayableCounter) + + // prefund the selfDestructor and payableCounter with some funds + // the TransferBalanceTo helper function does the require statements and waiting etc for us + _, _ = builder.L2.TransferBalanceTo(t, "Owner", selfDestructorAddress, big.NewInt(1e17), builder.L2Info) + _, _ = builder.L2.TransferBalanceTo(t, "Owner", payableCounterAddress, big.NewInt(1e17), builder.L2Info) + + // call selfDestructor.warmSelfDestructor(payableCounterAddress) + receipt := callOnContractWithOneArg(t, builder, auth, selfDestructor.WarmSelfDestructor, payableCounterAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} diff --git a/system_tests/gas_dim_tx_op_sstore_test.go b/system_tests/gas_dim_tx_op_sstore_test.go new file mode 100644 index 0000000000..9f99216e74 --- /dev/null +++ b/system_tests/gas_dim_tx_op_sstore_test.go @@ -0,0 +1,245 @@ +package arbtest + +import ( + "testing" + + "github.com/offchainlabs/nitro/solgen/go/gas_dimensionsgen" +) + +// ######################################################################################################### +// ######################################################################################################### +// SSTORE +// ######################################################################################################### +// ######################################################################################################### +// +// SSTORE has many permutations +// warm or cold +// 0 -> 0 +// 0 -> non-zero +// non-zero -> 0 +// non-zero -> non-zero (same value) +// non-zero -> non-zero (different value) + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: SSTORE cold slot from 0 to 0 (no state change). +func TestDimTxOpSstoreColdZeroToZero(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreColdZeroToZero) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: SSTORE cold slot from 0 to non-zero (state growth). +func TestDimTxOpSstoreColdZeroToNonZeroValue(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreColdZeroToNonZero) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: SSTORE cold slot from non-zero to 0 (state cleanup with refund). +func TestDimTxOpSstoreColdNonZeroValueToZero(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreColdNonZeroValueToZero) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: SSTORE cold slot from non-zero to same non-zero value (no state change). +func TestDimTxOpSstoreColdNonZeroToSameNonZeroValue(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreColdNonZeroToSameNonZeroValue) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: SSTORE cold slot from non-zero to different non-zero value (state update). +func TestDimTxOpSstoreColdNonZeroToDifferentNonZeroValue(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreColdNonZeroToDifferentNonZeroValue) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: SSTORE warm slot from 0 to 0 (no state change). +func TestDimTxOpSstoreWarmZeroToZero(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreWarmZeroToZero) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: SSTORE warm slot from 0 to non-zero (state growth). +func TestDimTxOpSstoreWarmZeroToNonZeroValue(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreWarmZeroToNonZeroValue) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: SSTORE warm slot from non-zero to 0 (state cleanup with refund). +func TestDimTxOpSstoreWarmNonZeroValueToZero(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreWarmNonZeroValueToZero) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: SSTORE warm slot from non-zero to same non-zero value (no state change). +func TestDimTxOpSstoreWarmNonZeroToSameNonZeroValue(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreWarmNonZeroToSameNonZeroValue) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: SSTORE warm slot from non-zero to different non-zero value (state update). +func TestDimTxOpSstoreWarmNonZeroToDifferentNonZeroValue(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreWarmNonZeroToDifferentNonZeroValue) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: Multiple SSTORE operations on warm slot: non-zero -> non-zero -> different non-zero. +func TestDimTxOpSstoreMultipleWarmNonZeroToNonZeroToNonZero(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreMultipleWarmNonZeroToNonZeroToNonZero) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: Multiple SSTORE operations on warm slot: non-zero -> non-zero -> same non-zero (with refund). +func TestDimTxOpSstoreMultipleWarmNonZeroToNonZeroToSameNonZero(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreMultipleWarmNonZeroToNonZeroToSameNonZero) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: Multiple SSTORE operations on warm slot: non-zero -> zero -> non-zero (refund adjustment). +func TestDimTxOpSstoreMultipleWarmNonZeroToZeroToNonZero(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreMultipleWarmNonZeroToZeroToNonZero) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: Multiple SSTORE operations on warm slot: non-zero -> zero -> same non-zero (refund adjustment). +func TestDimTxOpSstoreMultipleWarmNonZeroToZeroToSameNonZero(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreMultipleWarmNonZeroToZeroToSameNonZero) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: Multiple SSTORE operations on warm slot: zero -> non-zero -> different non-zero. +func TestDimTxOpSstoreMultipleWarmZeroToNonZeroToNonZero(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreMultipleWarmZeroToNonZeroToNonZero) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: Multiple SSTORE operations on warm slot: zero -> non-zero -> zero (with refund). +func TestDimTxOpSstoreMultipleWarmZeroToNonZeroBackToZero(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySstore) + _, receipt := callOnContract(t, builder, auth, contract.SstoreMultipleWarmZeroToNonZeroBackToZero) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} diff --git a/system_tests/gas_dim_tx_op_state_lookup_test.go b/system_tests/gas_dim_tx_op_state_lookup_test.go new file mode 100644 index 0000000000..be0a603bf8 --- /dev/null +++ b/system_tests/gas_dim_tx_op_state_lookup_test.go @@ -0,0 +1,146 @@ +package arbtest + +import ( + "testing" + + "github.com/offchainlabs/nitro/solgen/go/gas_dimensionsgen" +) + +// ######################################################################################################### +// ######################################################################################################### +// SIMPLE STATE ACCESS OPCODES (BALANCE, EXTCODESIZE, EXTCODEHASH) +// ######################################################################################################### +// ######################################################################################################### +// BALANCE, EXTCODESIZE, EXTCODEHASH are all read-only operations on state access +// They only operate on one axis: +// Warm vs Cold (in the access list) + +// ############################################################ +// BALANCE +// ############################################################ + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: BALANCE operation on a cold address (not in access list). +func TestDimTxOpBalanceCold(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployBalance) + _, receipt := callOnContract(t, builder, auth, contract.CallBalanceCold) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: BALANCE operation on a warm address (in access list). +func TestDimTxOpBalanceWarm(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployBalance) + _, receipt := callOnContract(t, builder, auth, contract.CallBalanceWarm) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// ############################################################ +// EXTCODESIZE +// ############################################################ + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: EXTCODESIZE operation on a cold address (not in access list). +func TestDimTxOpExtCodeSizeCold(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployExtCodeSize) + _, receipt := callOnContract(t, builder, auth, contract.GetExtCodeSizeCold) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: EXTCODESIZE operation on a warm address (in access list). +func TestDimTxOpExtCodeSizeWarm(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployExtCodeSize) + _, receipt := callOnContract(t, builder, auth, contract.GetExtCodeSizeWarm) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// ############################################################ +// EXTCODEHASH +// ############################################################ + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: EXTCODEHASH operation on a cold address (not in access list). +func TestDimTxOpExtCodeHashCold(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployExtCodeHash) + _, receipt := callOnContract(t, builder, auth, contract.GetExtCodeHashCold) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: EXTCODEHASH operation on a warm address (in access list). +func TestDimTxOpExtCodeHashWarm(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployExtCodeHash) + _, receipt := callOnContract(t, builder, auth, contract.GetExtCodeHashWarm) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// ############################################################ +// SLOAD +// ############################################################ +// SLOAD has only one axis of variation: +// Warm vs Cold (in the access list) + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: SLOAD operation on a cold storage slot (not previously accessed). +func TestDimTxOpSloadCold(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySload) + _, receipt := callOnContract(t, builder, auth, contract.ColdSload) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} + +// Tests that the total gas used by the transaction matches the expected value in the receipt, +// and that all gas dimension components sum to the total gas consumed. +// Scenario: SLOAD operation on a warm storage slot (previously accessed). +func TestDimTxOpSloadWarm(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t) + defer cancel() + defer cleanup() + + _, contract := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeploySload) + _, receipt := callOnContract(t, builder, auth, contract.WarmSload) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +}