diff --git a/.github/workflows/espresso-devnet-tests.yaml b/.github/workflows/espresso-devnet-tests.yaml index 8cbf2980e2d81..ef0aa4996835f 100644 --- a/.github/workflows/espresso-devnet-tests.yaml +++ b/.github/workflows/espresso-devnet-tests.yaml @@ -63,8 +63,8 @@ jobs: - name: Run Key Rotation test run: go test -timeout 30m -p 1 -count 1 -run 'TestKeyRotation' -v ./espresso/devnet-tests/... - # - name: Run Change Batch Inbox Owner test - # run: go test -timeout 30m -p 1 -count 1 -run 'TestChangeBatchInboxOwner -v ./espresso/devnet-tests/... + - name: Run Change Batch Inbox Owner test + run: go test -timeout 30m -p 1 -count 1 -run 'TestChangeBatchInboxOwner' -v ./espresso/devnet-tests/... - name: Save Nix cache uses: nix-community/cache-nix-action/save@v6 diff --git a/espresso/.env b/espresso/.env index e118a8de685f1..26de758565de3 100644 --- a/espresso/.env +++ b/espresso/.env @@ -34,6 +34,12 @@ OPERATOR_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf # cast wallet address --private-key $OPERATOR_PRIVATE_KEY OPERATOR_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +# BatchAuthenticator owner address for contract ownership (index 3 from mnemonic) +# cast wallet address --mnemonic "test test ... junk" --hd-path "m/44'/60'/0'/0/3" +BATCH_AUTHENTICATOR_OWNER_ADDRESS=0x90F79bf6EB2c4f870365E785982E1f101E93b906 +# cast wallet private-key --mnemonic "test test ... junk" --hd-path "m/44'/60'/0'/0/3" +BATCH_AUTHENTICATOR_OWNER_PRIVATE_KEY=0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6 + # cast wallet address --mnemonic "test test ... junk" --hd-path "m/44'/60'/0'/0/1" PROPOSER_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8 diff --git a/espresso/devnet-tests/devnet_tools.go b/espresso/devnet-tests/devnet_tools.go index 522814f553777..3f23cb2204c3d 100644 --- a/espresso/devnet-tests/devnet_tools.go +++ b/espresso/devnet-tests/devnet_tools.go @@ -9,6 +9,7 @@ import ( "math/big" "os" "os/exec" + "path/filepath" "reflect" "strconv" "strings" @@ -27,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" + "github.com/joho/godotenv" env "github.com/ethereum-optimism/optimism/espresso/environment" "github.com/ethereum-optimism/optimism/op-e2e/config/secrets" @@ -44,6 +46,18 @@ type Devnet struct { L2VerifRollup *sources.RollupClient } +// LoadEnvFile loads environment variables from a .env file +func LoadEnvFile(filename string) error { + return godotenv.Load(filename) +} + +// LoadDevnetEnv loads the espresso/.env file for devnet tests +func LoadDevnetEnv() error { + // Get the path to the espresso/.env file relative to the test directory + envPath := filepath.Join("..", ".env") + return LoadEnvFile(envPath) +} + func NewDevnet(ctx context.Context, t *testing.T) *Devnet { if testing.Short() { diff --git a/espresso/devnet-tests/key_rotation_test.go b/espresso/devnet-tests/key_rotation_test.go index e9d3084b751c0..cf9a39fd67fd0 100644 --- a/espresso/devnet-tests/key_rotation_test.go +++ b/espresso/devnet-tests/key_rotation_test.go @@ -2,15 +2,20 @@ package devnet_tests import ( "context" + "os" + "strings" "testing" "github.com/ethereum-optimism/optimism/op-batcher/bindings" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" ) func TestRotateBatcherKey(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -52,6 +57,10 @@ func TestRotateBatcherKey(t *testing.T) { } func TestChangeBatchInboxOwner(t *testing.T) { + // Load environment variables from .env file + err := LoadDevnetEnv() + require.NoError(t, err, "Failed to load .env file") + ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -68,18 +77,44 @@ func TestChangeBatchInboxOwner(t *testing.T) { config, err := d.RollupConfig(ctx) require.NoError(t, err) - // Change the BatchAuthenticator's owner batchAuthenticator, err := bindings.NewBatchAuthenticator(config.BatchAuthenticatorAddress, d.L1) require.NoError(t, err) - tx, err := batchAuthenticator.TransferOwnership(&bind.TransactOpts{}, d.secrets.Addresses().Bob) + + // Get L1 chain ID for transaction signing + l1ChainID, err := d.L1.ChainID(ctx) require.NoError(t, err) - _, err = d.SendL1Tx(ctx, tx) + + // Check current owner first + currentOwner, err := batchAuthenticator.Owner(&bind.CallOpts{}) + require.NoError(t, err) + + // Check that the new owner is different from the current one + bobAddress := d.secrets.Addresses().Bob + require.NotEqual(t, currentOwner, bobAddress) + + // Use batch authenticator owner key to sign the transaction + batchAuthenticatorPrivateKeyHex := os.Getenv("BATCH_AUTHENTICATOR_OWNER_PRIVATE_KEY") + require.NotEmpty(t, batchAuthenticatorPrivateKeyHex, "BATCH_AUTHENTICATOR_OWNER_PRIVATE_KEY must be set") + t.Logf("Using BATCH_AUTHENTICATOR_OWNER_PRIVATE_KEY from environment: %s...", batchAuthenticatorPrivateKeyHex[:10]) + + batchAuthenticatorKey, err := crypto.HexToECDSA(strings.TrimPrefix(batchAuthenticatorPrivateKeyHex, "0x")) + require.NoError(t, err) + + batchAuthenticatorOwnerOpts, err := bind.NewKeyedTransactorWithChainID(batchAuthenticatorKey, l1ChainID) + require.NoError(t, err) + + // Call TransferOwnership + tx, err := batchAuthenticator.TransferOwnership(batchAuthenticatorOwnerOpts, bobAddress) + require.NoError(t, err) + + // Wait for transaction receipt and check if it succeeded + _, err = wait.ForReceiptOK(ctx, d.L1, tx.Hash()) require.NoError(t, err) // Ensure the owner has been changed newOwner, err := batchAuthenticator.Owner(&bind.CallOpts{}) require.NoError(t, err) - require.Equal(t, newOwner, d.secrets.Addresses().Bob) + require.Equal(t, newOwner, bobAddress) // Check that everything still functions require.NoError(t, d.RunSimpleL2Burn()) diff --git a/espresso/scripts/prepare-allocs.sh b/espresso/scripts/prepare-allocs.sh index d017b84ff5bcf..8a827be6be3ef 100755 --- a/espresso/scripts/prepare-allocs.sh +++ b/espresso/scripts/prepare-allocs.sh @@ -96,7 +96,7 @@ dasel put -f "${DEPLOYER_DIR}/intent.toml" -s .chains.[0].roles.proposer -v "${P # contract addresses are deterministic. dasel put -f "${DEPLOYER_DIR}/state.json" -s create2Salt -v "0xaecea4f57fadb2097ccd56594f2f22715ac52f92971c5913b70a7f1134b68feb" -op-deployer apply --l1-rpc-url "${ANVIL_URL}" \ +BATCH_AUTHENTICATOR_OWNER_ADDRESS="${BATCH_AUTHENTICATOR_OWNER_ADDRESS}" op-deployer apply --l1-rpc-url "${ANVIL_URL}" \ --workdir "${DEPLOYER_DIR}" \ --private-key="${OPERATOR_PRIVATE_KEY}" diff --git a/go.mod b/go.mod index dac506b929cae..756bc7ddb83ef 100644 --- a/go.mod +++ b/go.mod @@ -79,6 +79,8 @@ require ( gopkg.in/yaml.v3 v3.0.1 ) +require github.com/joho/godotenv v1.5.1 + require ( codeberg.org/go-fonts/liberation v0.5.0 // indirect codeberg.org/go-latex/latex v0.1.0 // indirect diff --git a/go.sum b/go.sum index 9b4f87069c658..0b61b062daa30 100644 --- a/go.sum +++ b/go.sum @@ -473,6 +473,8 @@ github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZl github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= diff --git a/op-deployer/pkg/deployer/opcm/espresso.go b/op-deployer/pkg/deployer/opcm/espresso.go index ec8c0afdf804a..9bd2fdd4c3896 100644 --- a/op-deployer/pkg/deployer/opcm/espresso.go +++ b/op-deployer/pkg/deployer/opcm/espresso.go @@ -27,7 +27,7 @@ type DeployEspressoOutput struct { } type DeployEspressoScript struct { - Run func(input, output common.Address) error + Run func(input, output, deployerAddress common.Address) error } type DeployAWSNitroVerifierScript struct { @@ -72,6 +72,7 @@ func DeployAWSNitroVerifier( func DeployEspresso( host *script.Host, input DeployEspressoInput, + deployerAddress common.Address, ) (DeployEspressoOutput, error) { var output DeployEspressoOutput inputAddr := host.NewScriptAddress() @@ -97,7 +98,7 @@ func DeployEspresso( } defer cleanupDeploy() - if err := deployScript.Run(inputAddr, outputAddr); err != nil { + if err := deployScript.Run(inputAddr, outputAddr, deployerAddress); err != nil { return output, fmt.Errorf("failed to run %s script: %w", implContract, err) } diff --git a/op-deployer/pkg/deployer/pipeline/espresso.go b/op-deployer/pkg/deployer/pipeline/espresso.go index a4334da502cd3..1702c7bfe5374 100644 --- a/op-deployer/pkg/deployer/pipeline/espresso.go +++ b/op-deployer/pkg/deployer/pipeline/espresso.go @@ -2,6 +2,7 @@ package pipeline import ( "fmt" + "os" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" @@ -36,11 +37,21 @@ func DeployEspresso(env *Env, intent *state.Intent, st *state.State, chainID com } var eo opcm.DeployEspressoOutput + // Read batch authenticator owner address from environment variable, fallback to env.Deployer + var batchAuthenticatorOwnwerAddress common.Address + if batchAuthenticatorOwnerEnv := os.Getenv("BATCH_AUTHENTICATOR_OWNER_ADDRESS"); batchAuthenticatorOwnerEnv != "" { + batchAuthenticatorOwnwerAddress = common.HexToAddress(batchAuthenticatorOwnerEnv) + lgr.Info("Using batch authenticator owner address from BATCH_AUTHENTICATOR_OWNER_ADDRESS env var", "address", batchAuthenticatorOwnwerAddress.Hex()) + } else { + batchAuthenticatorOwnwerAddress = env.Deployer + lgr.Info("Using deployer address from env.Deployer", "address", batchAuthenticatorOwnwerAddress.Hex()) + } + eo, err = opcm.DeployEspresso(env.L1ScriptHost, opcm.DeployEspressoInput{ Salt: st.Create2Salt, PreApprovedBatcherKey: chainIntent.PreApprovedBatcherKey, NitroTEEVerifier: nvo.NitroTEEVerifierAddress, - }) + }, batchAuthenticatorOwnwerAddress) if err != nil { return fmt.Errorf("failed to deploy espresso contracts: %w", err) } diff --git a/packages/contracts-bedrock/interfaces/L1/IBatchAuthenticator.sol b/packages/contracts-bedrock/interfaces/L1/IBatchAuthenticator.sol index 16ac0b2b78b5a..9d76da2c58d10 100644 --- a/packages/contracts-bedrock/interfaces/L1/IBatchAuthenticator.sol +++ b/packages/contracts-bedrock/interfaces/L1/IBatchAuthenticator.sol @@ -38,6 +38,7 @@ interface IBatchAuthenticator { function __constructor__( address _espressoTEEVerifier, - address _preApprovedBatcher + address _preApprovedBatcher, + address _owner ) external; } diff --git a/packages/contracts-bedrock/scripts/deploy/DeployEspresso.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployEspresso.s.sol index f5238df099eca..0b1ee985b06b6 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployEspresso.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployEspresso.s.sol @@ -73,9 +73,9 @@ contract DeployEspressoOutput is BaseDeployIO { } contract DeployEspresso is Script { - function run(DeployEspressoInput input, DeployEspressoOutput output) public { + function run(DeployEspressoInput input, DeployEspressoOutput output, address deployerAddress) public { IEspressoTEEVerifier teeVerifier = deployTEEVerifier(input); - IBatchAuthenticator batchAuthenticator = deployBatchAuthenticator(input, output, teeVerifier); + IBatchAuthenticator batchAuthenticator = deployBatchAuthenticator(input, output, teeVerifier, deployerAddress); deployBatchInbox(input, output, batchAuthenticator); checkOutput(output); } @@ -83,7 +83,8 @@ contract DeployEspresso is Script { function deployBatchAuthenticator( DeployEspressoInput input, DeployEspressoOutput output, - IEspressoTEEVerifier teeVerifier + IEspressoTEEVerifier teeVerifier, + address owner ) public returns (IBatchAuthenticator) @@ -96,11 +97,14 @@ contract DeployEspresso is Script { _name: "BatchAuthenticator", _salt: salt, _args: DeployUtils.encodeConstructor( - abi.encodeCall(IBatchAuthenticator.__constructor__, (address(teeVerifier), preApprovedBatcherKey)) + abi.encodeCall( + IBatchAuthenticator.__constructor__, (address(teeVerifier), preApprovedBatcherKey, owner) + ) ) }) ); vm.label(address(impl), "BatchAuthenticatorImpl"); + output.set(output.batchAuthenticatorAddress.selector, address(impl)); return impl; } diff --git a/packages/contracts-bedrock/src/L1/BatchAuthenticator.sol b/packages/contracts-bedrock/src/L1/BatchAuthenticator.sol index 74c86a933f650..0545c40860c1d 100644 --- a/packages/contracts-bedrock/src/L1/BatchAuthenticator.sol +++ b/packages/contracts-bedrock/src/L1/BatchAuthenticator.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { ISemver } from "interfaces/universal/ISemver.sol"; import { EspressoTEEVerifier } from "@espresso-tee-contracts/EspressoTEEVerifier.sol"; import { IEspressoTEEVerifier } from "@espresso-tee-contracts/interface/IEspressoTEEVerifier.sol"; @@ -14,7 +14,7 @@ interface INitroValidator { returns (bytes memory attestationTbs, bytes memory signature); } -contract BatchAuthenticator is ISemver, OwnableUpgradeable { +contract BatchAuthenticator is ISemver, Ownable { /// @notice Semantic version. /// @custom:semver 1.0.0 string public constant version = "1.0.0"; @@ -27,10 +27,11 @@ contract BatchAuthenticator is ISemver, OwnableUpgradeable { EspressoTEEVerifier public immutable espressoTEEVerifier; INitroValidator public immutable nitroValidator; - constructor(EspressoTEEVerifier _espressoTEEVerifier, address _preApprovedBatcher) OwnableUpgradeable() { + constructor(EspressoTEEVerifier _espressoTEEVerifier, address _preApprovedBatcher, address _owner) Ownable() { espressoTEEVerifier = _espressoTEEVerifier; preApprovedBatcher = _preApprovedBatcher; nitroValidator = INitroValidator(address(espressoTEEVerifier.espressoNitroTEEVerifier())); + _transferOwnership(_owner); } function decodeAttestationTbs(bytes memory attestation) external view returns (bytes memory, bytes memory) {