From 18616c4e024e45dfe732510fe568e78ba573a49a Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Wed, 4 Oct 2023 12:11:06 -0600 Subject: [PATCH] separate estimation tests from nodeinterface --- system_tests/estimation_test.go | 254 +++++++++++++++++++++++++++++ system_tests/nodeinterface_test.go | 237 --------------------------- 2 files changed, 254 insertions(+), 237 deletions(-) create mode 100644 system_tests/estimation_test.go diff --git a/system_tests/estimation_test.go b/system_tests/estimation_test.go new file mode 100644 index 0000000000..9f2db62dab --- /dev/null +++ b/system_tests/estimation_test.go @@ -0,0 +1,254 @@ +// Copyright 2021-2022, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +package arbtest + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/offchainlabs/nitro/arbos/arbostypes" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" + "github.com/offchainlabs/nitro/solgen/go/node_interfacegen" + "github.com/offchainlabs/nitro/solgen/go/precompilesgen" + "github.com/offchainlabs/nitro/util/arbmath" + "github.com/offchainlabs/nitro/util/colors" + "github.com/offchainlabs/nitro/util/testhelpers" +) + +func TestDeploy(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + l2info, node, client := CreateTestL2(t, ctx) + defer node.StopAndWait() + + auth := l2info.GetDefaultTransactOpts("Owner", ctx) + auth.GasMargin = 0 // don't adjust, we want to see if the estimate alone is sufficient + + _, simple := deploySimple(t, ctx, auth, client) + + tx, err := simple.Increment(&auth) + Require(t, err, "failed to call Increment()") + _, err = EnsureTxSucceeded(ctx, client, tx) + Require(t, err) + + counter, err := simple.Counter(&bind.CallOpts{}) + Require(t, err, "failed to get counter") + + if counter != 1 { + Fatal(t, "Unexpected counter value", counter) + } +} + +func TestEstimate(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + l2info, node, client := CreateTestL2(t, ctx) + defer node.StopAndWait() + + auth := l2info.GetDefaultTransactOpts("Owner", ctx) + auth.GasMargin = 0 // don't adjust, we want to see if the estimate alone is sufficient + + gasPrice := big.NewInt(params.GWei / 10) + + // set the gas price + arbOwner, err := precompilesgen.NewArbOwner(common.HexToAddress("0x70"), client) + Require(t, err, "could not deploy ArbOwner contract") + tx, err := arbOwner.SetMinimumL2BaseFee(&auth, gasPrice) + Require(t, err, "could not set L2 gas price") + _, err = EnsureTxSucceeded(ctx, client, tx) + Require(t, err) + + // connect to arbGasInfo precompile + arbGasInfo, err := precompilesgen.NewArbGasInfo(common.HexToAddress("0x6c"), client) + Require(t, err, "could not deploy contract") + + // wait for price to come to equilibrium + equilibrated := false + numTriesLeft := 20 + for !equilibrated && numTriesLeft > 0 { + // make an empty block to let the gas price update + l2info.GasPrice = new(big.Int).Mul(l2info.GasPrice, big.NewInt(2)) + TransferBalance(t, "Owner", "Owner", common.Big0, l2info, client, ctx) + + // check if the price has equilibrated + _, _, _, _, _, setPrice, err := arbGasInfo.GetPricesInWei(&bind.CallOpts{}) + Require(t, err, "could not get L2 gas price") + if gasPrice.Cmp(setPrice) == 0 { + equilibrated = true + } + numTriesLeft-- + } + if !equilibrated { + Fatal(t, "L2 gas price did not converge", gasPrice) + } + + initialBalance, err := client.BalanceAt(ctx, auth.From, nil) + Require(t, err, "could not get balance") + + // deploy a test contract + _, tx, simple, err := mocksgen.DeploySimple(&auth, client) + Require(t, err, "could not deploy contract") + receipt, err := EnsureTxSucceeded(ctx, client, tx) + Require(t, err) + + header, err := client.HeaderByNumber(ctx, receipt.BlockNumber) + Require(t, err, "could not get header") + if header.BaseFee.Cmp(gasPrice) != 0 { + Fatal(t, "Header has wrong basefee", header.BaseFee, gasPrice) + } + + balance, err := client.BalanceAt(ctx, auth.From, nil) + Require(t, err, "could not get balance") + expectedCost := receipt.GasUsed * gasPrice.Uint64() + observedCost := initialBalance.Uint64() - balance.Uint64() + if expectedCost != observedCost { + Fatal(t, "Expected deployment to cost", expectedCost, "instead of", observedCost) + } + + tx, err = simple.Increment(&auth) + Require(t, err, "failed to call Increment()") + _, err = EnsureTxSucceeded(ctx, client, tx) + Require(t, err) + + counter, err := simple.Counter(&bind.CallOpts{}) + Require(t, err, "failed to get counter") + + if counter != 1 { + Fatal(t, "Unexpected counter value", counter) + } +} + +func TestComponentEstimate(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + l2info, node, client := CreateTestL2(t, ctx) + defer node.StopAndWait() + + l1BaseFee := new(big.Int).Set(arbostypes.DefaultInitialL1BaseFee) + l2BaseFee := GetBaseFee(t, client, ctx) + + colors.PrintGrey("l1 basefee ", l1BaseFee) + colors.PrintGrey("l2 basefee ", l2BaseFee) + + userBalance := big.NewInt(1e16) + maxPriorityFeePerGas := big.NewInt(0) + maxFeePerGas := arbmath.BigMulByUfrac(l2BaseFee, 3, 2) + + l2info.GenerateAccount("User") + TransferBalance(t, "Owner", "User", userBalance, l2info, client, ctx) + + from := l2info.GetAddress("User") + to := testhelpers.RandomAddress() + gas := uint64(100000000) + calldata := []byte{0x00, 0x12} + value := big.NewInt(4096) + + nodeAbi, err := node_interfacegen.NodeInterfaceMetaData.GetAbi() + Require(t, err) + + nodeMethod := nodeAbi.Methods["gasEstimateComponents"] + estimateCalldata := append([]byte{}, nodeMethod.ID...) + packed, err := nodeMethod.Inputs.Pack(to, false, calldata) + Require(t, err) + estimateCalldata = append(estimateCalldata, packed...) + + msg := ethereum.CallMsg{ + From: from, + To: &types.NodeInterfaceAddress, + Gas: gas, + GasFeeCap: maxFeePerGas, + GasTipCap: maxPriorityFeePerGas, + Value: value, + Data: estimateCalldata, + } + returnData, err := client.CallContract(ctx, msg, nil) + Require(t, err) + + outputs, err := nodeMethod.Outputs.Unpack(returnData) + Require(t, err) + if len(outputs) != 4 { + Fatal(t, "expected 4 outputs from gasEstimateComponents, got", len(outputs)) + } + + gasEstimate, _ := outputs[0].(uint64) + gasEstimateForL1, _ := outputs[1].(uint64) + baseFee, _ := outputs[2].(*big.Int) + l1BaseFeeEstimate, _ := outputs[3].(*big.Int) + + execNode := getExecNode(t, node) + tx := l2info.SignTxAs("User", &types.DynamicFeeTx{ + ChainID: execNode.ArbInterface.BlockChain().Config().ChainID, + Nonce: 0, + GasTipCap: maxPriorityFeePerGas, + GasFeeCap: maxFeePerGas, + Gas: gasEstimate, + To: &to, + Value: value, + Data: calldata, + }) + + l2Estimate := gasEstimate - gasEstimateForL1 + + colors.PrintBlue("Est. ", gasEstimate, " - ", gasEstimateForL1, " = ", l2Estimate) + + if !arbmath.BigEquals(l1BaseFeeEstimate, l1BaseFee) { + Fatal(t, l1BaseFeeEstimate, l1BaseFee) + } + if !arbmath.BigEquals(baseFee, l2BaseFee) { + Fatal(t, baseFee, l2BaseFee.Uint64()) + } + + Require(t, client.SendTransaction(ctx, tx)) + receipt, err := EnsureTxSucceeded(ctx, client, tx) + Require(t, err) + + l2Used := receipt.GasUsed - receipt.GasUsedForL1 + colors.PrintMint("True ", receipt.GasUsed, " - ", receipt.GasUsedForL1, " = ", l2Used) + + if l2Estimate != l2Used { + Fatal(t, l2Estimate, l2Used) + } +} + +func TestDisableL1Charging(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + _, node, client := CreateTestL2(t, ctx) + defer node.StopAndWait() + addr := common.HexToAddress("0x12345678") + + gasWithL1Charging, err := client.EstimateGas(ctx, ethereum.CallMsg{To: &addr}) + Require(t, err) + + gasWithoutL1Charging, err := client.EstimateGas(ctx, ethereum.CallMsg{To: &addr, SkipL1Charging: true}) + Require(t, err) + + if gasWithL1Charging <= gasWithoutL1Charging { + Fatal(t, "SkipL1Charging didn't disable L1 charging") + } + if gasWithoutL1Charging != params.TxGas { + Fatal(t, "Incorrect gas estimate with disabled L1 charging") + } + + _, err = client.CallContract(ctx, ethereum.CallMsg{To: &addr, Gas: gasWithL1Charging}, nil) + Require(t, err) + + _, err = client.CallContract(ctx, ethereum.CallMsg{To: &addr, Gas: gasWithoutL1Charging}, nil) + if err == nil { + Fatal(t, "CallContract passed with insufficient gas") + } + + _, err = client.CallContract(ctx, ethereum.CallMsg{To: &addr, Gas: gasWithoutL1Charging, SkipL1Charging: true}, nil) + Require(t, err) +} diff --git a/system_tests/nodeinterface_test.go b/system_tests/nodeinterface_test.go index 0b060b2b99..20152a2ef4 100644 --- a/system_tests/nodeinterface_test.go +++ b/system_tests/nodeinterface_test.go @@ -16,214 +16,10 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbnode" - "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/arbos/util" - "github.com/offchainlabs/nitro/solgen/go/mocksgen" "github.com/offchainlabs/nitro/solgen/go/node_interfacegen" - "github.com/offchainlabs/nitro/solgen/go/precompilesgen" - "github.com/offchainlabs/nitro/util/arbmath" - "github.com/offchainlabs/nitro/util/colors" - "github.com/offchainlabs/nitro/util/testhelpers" ) -func TestDeploy(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - l2info, node, client := CreateTestL2(t, ctx) - defer node.StopAndWait() - - auth := l2info.GetDefaultTransactOpts("Owner", ctx) - auth.GasMargin = 0 // don't adjust, we want to see if the estimate alone is sufficient - - _, simple := deploySimple(t, ctx, auth, client) - - tx, err := simple.Increment(&auth) - Require(t, err, "failed to call Increment()") - _, err = EnsureTxSucceeded(ctx, client, tx) - Require(t, err) - - counter, err := simple.Counter(&bind.CallOpts{}) - Require(t, err, "failed to get counter") - - if counter != 1 { - Fatal(t, "Unexpected counter value", counter) - } -} - -func TestEstimate(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - l2info, node, client := CreateTestL2(t, ctx) - defer node.StopAndWait() - - auth := l2info.GetDefaultTransactOpts("Owner", ctx) - auth.GasMargin = 0 // don't adjust, we want to see if the estimate alone is sufficient - - gasPrice := big.NewInt(params.GWei / 10) - - // set the gas price - arbOwner, err := precompilesgen.NewArbOwner(common.HexToAddress("0x70"), client) - Require(t, err, "could not deploy ArbOwner contract") - tx, err := arbOwner.SetMinimumL2BaseFee(&auth, gasPrice) - Require(t, err, "could not set L2 gas price") - _, err = EnsureTxSucceeded(ctx, client, tx) - Require(t, err) - - // connect to arbGasInfo precompile - arbGasInfo, err := precompilesgen.NewArbGasInfo(common.HexToAddress("0x6c"), client) - Require(t, err, "could not deploy contract") - - // wait for price to come to equilibrium - equilibrated := false - numTriesLeft := 20 - for !equilibrated && numTriesLeft > 0 { - // make an empty block to let the gas price update - l2info.GasPrice = new(big.Int).Mul(l2info.GasPrice, big.NewInt(2)) - TransferBalance(t, "Owner", "Owner", common.Big0, l2info, client, ctx) - - // check if the price has equilibrated - _, _, _, _, _, setPrice, err := arbGasInfo.GetPricesInWei(&bind.CallOpts{}) - Require(t, err, "could not get L2 gas price") - if gasPrice.Cmp(setPrice) == 0 { - equilibrated = true - } - numTriesLeft-- - } - if !equilibrated { - Fatal(t, "L2 gas price did not converge", gasPrice) - } - - initialBalance, err := client.BalanceAt(ctx, auth.From, nil) - Require(t, err, "could not get balance") - - // deploy a test contract - _, tx, simple, err := mocksgen.DeploySimple(&auth, client) - Require(t, err, "could not deploy contract") - receipt, err := EnsureTxSucceeded(ctx, client, tx) - Require(t, err) - - header, err := client.HeaderByNumber(ctx, receipt.BlockNumber) - Require(t, err, "could not get header") - if header.BaseFee.Cmp(gasPrice) != 0 { - Fatal(t, "Header has wrong basefee", header.BaseFee, gasPrice) - } - - balance, err := client.BalanceAt(ctx, auth.From, nil) - Require(t, err, "could not get balance") - expectedCost := receipt.GasUsed * gasPrice.Uint64() - observedCost := initialBalance.Uint64() - balance.Uint64() - if expectedCost != observedCost { - Fatal(t, "Expected deployment to cost", expectedCost, "instead of", observedCost) - } - - tx, err = simple.Increment(&auth) - Require(t, err, "failed to call Increment()") - _, err = EnsureTxSucceeded(ctx, client, tx) - Require(t, err) - - counter, err := simple.Counter(&bind.CallOpts{}) - Require(t, err, "failed to get counter") - - if counter != 1 { - Fatal(t, "Unexpected counter value", counter) - } -} - -func TestComponentEstimate(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - l2info, node, client := CreateTestL2(t, ctx) - defer node.StopAndWait() - - l1BaseFee := new(big.Int).Set(arbostypes.DefaultInitialL1BaseFee) - l2BaseFee := GetBaseFee(t, client, ctx) - - colors.PrintGrey("l1 basefee ", l1BaseFee) - colors.PrintGrey("l2 basefee ", l2BaseFee) - - userBalance := big.NewInt(1e16) - maxPriorityFeePerGas := big.NewInt(0) - maxFeePerGas := arbmath.BigMulByUfrac(l2BaseFee, 3, 2) - - l2info.GenerateAccount("User") - TransferBalance(t, "Owner", "User", userBalance, l2info, client, ctx) - - from := l2info.GetAddress("User") - to := testhelpers.RandomAddress() - gas := uint64(100000000) - calldata := []byte{0x00, 0x12} - value := big.NewInt(4096) - - nodeAbi, err := node_interfacegen.NodeInterfaceMetaData.GetAbi() - Require(t, err) - - nodeMethod := nodeAbi.Methods["gasEstimateComponents"] - estimateCalldata := append([]byte{}, nodeMethod.ID...) - packed, err := nodeMethod.Inputs.Pack(to, false, calldata) - Require(t, err) - estimateCalldata = append(estimateCalldata, packed...) - - msg := ethereum.CallMsg{ - From: from, - To: &types.NodeInterfaceAddress, - Gas: gas, - GasFeeCap: maxFeePerGas, - GasTipCap: maxPriorityFeePerGas, - Value: value, - Data: estimateCalldata, - } - returnData, err := client.CallContract(ctx, msg, nil) - Require(t, err) - - outputs, err := nodeMethod.Outputs.Unpack(returnData) - Require(t, err) - if len(outputs) != 4 { - Fatal(t, "expected 4 outputs from gasEstimateComponents, got", len(outputs)) - } - - gasEstimate, _ := outputs[0].(uint64) - gasEstimateForL1, _ := outputs[1].(uint64) - baseFee, _ := outputs[2].(*big.Int) - l1BaseFeeEstimate, _ := outputs[3].(*big.Int) - - execNode := getExecNode(t, node) - tx := l2info.SignTxAs("User", &types.DynamicFeeTx{ - ChainID: execNode.ArbInterface.BlockChain().Config().ChainID, - Nonce: 0, - GasTipCap: maxPriorityFeePerGas, - GasFeeCap: maxFeePerGas, - Gas: gasEstimate, - To: &to, - Value: value, - Data: calldata, - }) - - l2Estimate := gasEstimate - gasEstimateForL1 - - colors.PrintBlue("Est. ", gasEstimate, " - ", gasEstimateForL1, " = ", l2Estimate) - - if !arbmath.BigEquals(l1BaseFeeEstimate, l1BaseFee) { - Fatal(t, l1BaseFeeEstimate, l1BaseFee) - } - if !arbmath.BigEquals(baseFee, l2BaseFee) { - Fatal(t, baseFee, l2BaseFee.Uint64()) - } - - Require(t, client.SendTransaction(ctx, tx)) - receipt, err := EnsureTxSucceeded(ctx, client, tx) - Require(t, err) - - l2Used := receipt.GasUsed - receipt.GasUsedForL1 - colors.PrintMint("True ", receipt.GasUsed, " - ", receipt.GasUsedForL1, " = ", l2Used) - - if l2Estimate != l2Used { - Fatal(t, l2Estimate, l2Used) - } -} - func callFindBatchContainig(t *testing.T, ctx context.Context, client *ethclient.Client, nodeAbi *abi.ABI, blockNum uint64) uint64 { findBatch := nodeAbi.Methods["findBatchContainingBlock"] callData := append([]byte{}, findBatch.ID...) @@ -338,39 +134,6 @@ func TestFindBatch(t *testing.T) { } } -func TestDisableL1Charging(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - _, node, client := CreateTestL2(t, ctx) - defer node.StopAndWait() - addr := common.HexToAddress("0x12345678") - - gasWithL1Charging, err := client.EstimateGas(ctx, ethereum.CallMsg{To: &addr}) - Require(t, err) - - gasWithoutL1Charging, err := client.EstimateGas(ctx, ethereum.CallMsg{To: &addr, SkipL1Charging: true}) - Require(t, err) - - if gasWithL1Charging <= gasWithoutL1Charging { - Fatal(t, "SkipL1Charging didn't disable L1 charging") - } - if gasWithoutL1Charging != params.TxGas { - Fatal(t, "Incorrect gas estimate with disabled L1 charging") - } - - _, err = client.CallContract(ctx, ethereum.CallMsg{To: &addr, Gas: gasWithL1Charging}, nil) - Require(t, err) - - _, err = client.CallContract(ctx, ethereum.CallMsg{To: &addr, Gas: gasWithoutL1Charging}, nil) - if err == nil { - Fatal(t, "CallContract passed with insufficient gas") - } - - _, err = client.CallContract(ctx, ethereum.CallMsg{To: &addr, Gas: gasWithoutL1Charging, SkipL1Charging: true}, nil) - Require(t, err) -} - func TestL2BlockRangeForL1(t *testing.T) { t.Parallel() ctx, cancel := context.WithCancel(context.Background())