diff --git a/espresso/devnet-tests/devnet_tools.go b/espresso/devnet-tests/devnet_tools.go index dc38b0d8e6a..d900f273719 100644 --- a/espresso/devnet-tests/devnet_tools.go +++ b/espresso/devnet-tests/devnet_tools.go @@ -3,6 +3,7 @@ package devnet_tests import ( "bytes" "context" + "crypto/ecdsa" "encoding/hex" "fmt" "io" @@ -21,6 +22,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup" opclient "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/sources" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -644,3 +646,28 @@ func (d *Devnet) rollupClient(service string, port uint16) (*sources.RollupClien client := sources.NewRollupClient(rpc) return client, nil } + +// OperatorAddress returns the operator address (index 0) from the mnemonic. +// This is the address that runs deployment transactions and owns deployed contracts. +func (d *Devnet) OperatorAddress() (common.Address, error) { + // The operator is at index 0 of the mnemonic: "m/44'/60'/0'/0/0" + operatorPath := "m/44'/60'/0'/0/0" + account := accounts.Account{URL: accounts.URL{Path: operatorPath}} + + operatorKey, err := d.secrets.Wallet.PrivateKey(account) + if err != nil { + return common.Address{}, err + } + + return crypto.PubkeyToAddress(operatorKey.PublicKey), nil +} + +// OperatorPrivateKey returns the operator private key (index 0) from the mnemonic. +// This is the private key that runs deployment transactions and owns deployed contracts. +func (d *Devnet) OperatorPrivateKey() (*ecdsa.PrivateKey, error) { + // The operator is at index 0 of the mnemonic: "m/44'/60'/0'/0/0" + operatorPath := "m/44'/60'/0'/0/0" + account := accounts.Account{URL: accounts.URL{Path: operatorPath}} + + return d.secrets.Wallet.PrivateKey(account) +} diff --git a/espresso/devnet-tests/key_rotation_test.go b/espresso/devnet-tests/key_rotation_test.go index e9d3084b751..d0d0ccab65c 100644 --- a/espresso/devnet-tests/key_rotation_test.go +++ b/espresso/devnet-tests/key_rotation_test.go @@ -68,18 +68,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) + currentOwner, err := batchAuthenticator.Owner(&bind.CallOpts{}) require.NoError(t, err) - _, err = d.SendL1Tx(ctx, tx) + + // The BatchAuthenticator should be owned by the deployer + deployerAddress := d.secrets.Addresses().Deployer + aliceAddress := d.secrets.Addresses().Alice + + t.Logf("Current owner: %s", currentOwner.Hex()) + t.Logf("Deployer address: %s", deployerAddress.Hex()) + + // Verify the contract is owned by the deployer (as expected from deployment) + require.Equal(t, currentOwner, deployerAddress, + "BatchAuthenticator should be owned by deployer %s, but is owned by %s", + deployerAddress.Hex(), currentOwner.Hex()) + + require.NotEqual(t, deployerAddress, aliceAddress, "Alice should not be the current owner") + + // Create transaction options using the deployer's private key + chainID, err := d.L1.ChainID(ctx) require.NoError(t, err) + ownerAuth, err := bind.NewKeyedTransactorWithChainID(d.secrets.Deployer, chainID) + require.NoError(t, err) + + tx, err := batchAuthenticator.TransferOwnership(ownerAuth, aliceAddress) + require.NoError(t, err, "Ownership transfer transaction building failed.") + + // Send transaction using the devnet's SendL1Tx method + receipt, err := d.SendL1Tx(ctx, tx) + require.NoError(t, err, "Failed to send ownership transfer transaction.") + require.Equal(t, receipt.Status, uint64(1), "Transaction failed") + // 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.NoError(t, err, "Failed to get new owner.") + require.Equal(t, newOwner, aliceAddress, "New Owner is not Alice") // Check that everything still functions require.NoError(t, d.RunSimpleL2Burn()) diff --git a/op-deployer/pkg/deployer/opcm/espresso.go b/op-deployer/pkg/deployer/opcm/espresso.go index ec8c0afdf80..761f66ff202 100644 --- a/op-deployer/pkg/deployer/opcm/espresso.go +++ b/op-deployer/pkg/deployer/opcm/espresso.go @@ -16,9 +16,10 @@ type DeployAWSNitroVerifierOutput struct { } type DeployEspressoInput struct { - Salt common.Hash - PreApprovedBatcherKey common.Address - NitroTEEVerifier common.Address + Salt common.Hash + PreApprovedBatcherKey common.Address + NitroTEEVerifier common.Address + BatchAuthenticatorOwner common.Address } type DeployEspressoOutput struct { diff --git a/op-deployer/pkg/deployer/pipeline/espresso.go b/op-deployer/pkg/deployer/pipeline/espresso.go index a4334da502c..ca9b69efaf7 100644 --- a/op-deployer/pkg/deployer/pipeline/espresso.go +++ b/op-deployer/pkg/deployer/pipeline/espresso.go @@ -35,11 +35,14 @@ func DeployEspresso(env *Env, intent *state.Intent, st *state.State, chainID com return fmt.Errorf("failed to deploy nitro verifier contracts: %w", err) } + lgr.Info("deploying espresso contracts with BatchAuthenticator owner", "owner", env.Deployer.Hex()) + var eo opcm.DeployEspressoOutput eo, err = opcm.DeployEspresso(env.L1ScriptHost, opcm.DeployEspressoInput{ - Salt: st.Create2Salt, - PreApprovedBatcherKey: chainIntent.PreApprovedBatcherKey, - NitroTEEVerifier: nvo.NitroTEEVerifierAddress, + Salt: st.Create2Salt, + PreApprovedBatcherKey: chainIntent.PreApprovedBatcherKey, + NitroTEEVerifier: nvo.NitroTEEVerifierAddress, + BatchAuthenticatorOwner: env.Deployer, }) 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 16ac0b2b78b..6feec8ba9e3 100644 --- a/packages/contracts-bedrock/interfaces/L1/IBatchAuthenticator.sol +++ b/packages/contracts-bedrock/interfaces/L1/IBatchAuthenticator.sol @@ -36,6 +36,8 @@ interface IBatchAuthenticator { function validBatchInfo(bytes32) external view returns (bool); + function initialize(address _owner) external; + function __constructor__( address _espressoTEEVerifier, address _preApprovedBatcher diff --git a/packages/contracts-bedrock/scripts/deploy/DeployEspresso.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployEspresso.s.sol index f5238df099e..462c52eb3c1 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployEspresso.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployEspresso.s.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.22; import { BaseDeployIO } from "scripts/deploy/BaseDeployIO.sol"; import { IBatchInbox } from "interfaces/L1/IBatchInbox.sol"; import { Script } from "forge-std/Script.sol"; +import { console } from "forge-std/console.sol"; import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; import { Solarray } from "scripts/libraries/Solarray.sol"; import { IBatchAuthenticator } from "interfaces/L1/IBatchAuthenticator.sol"; @@ -16,6 +17,7 @@ contract DeployEspressoInput is BaseDeployIO { bytes32 internal _salt; address internal _preApprovedBatcherKey; address internal _nitroTEEVerifier; + address internal _batchAuthenticatorOwner; function set(bytes4 _sel, bytes32 _val) public { if (_sel == this.salt.selector) _salt = _val; @@ -27,6 +29,8 @@ contract DeployEspressoInput is BaseDeployIO { _preApprovedBatcherKey = _val; } else if (_sel == this.nitroTEEVerifier.selector) { _nitroTEEVerifier = _val; + } else if (_sel == this.batchAuthenticatorOwner.selector) { + _batchAuthenticatorOwner = _val; } else { revert("DeployEspressoInput: unknown selector"); } @@ -44,6 +48,10 @@ contract DeployEspressoInput is BaseDeployIO { function preApprovedBatcherKey() public view returns (address) { return _preApprovedBatcherKey; } + + function batchAuthenticatorOwner() public view returns (address) { + return _batchAuthenticatorOwner; + } } contract DeployEspressoOutput is BaseDeployIO { @@ -74,8 +82,21 @@ contract DeployEspressoOutput is BaseDeployIO { contract DeployEspresso is Script { function run(DeployEspressoInput input, DeployEspressoOutput output) public { + // For now, let's hardcode the deployer address to ensure it works + // Deployer address is at index 3 of the mnemonic: 0x90F79bf6EB2c4f870365E785982E1f101E93b906 + address deployerAddress = 0x90F79bf6EB2c4f870365E785982E1f101E93b906; + console.log("Using hardcoded deployer address as BatchAuthenticator owner:", deployerAddress); + run(input, output, deployerAddress); + } + + function run(DeployEspressoInput input, DeployEspressoOutput output, address batchAuthenticatorOwner) public { IEspressoTEEVerifier teeVerifier = deployTEEVerifier(input); IBatchAuthenticator batchAuthenticator = deployBatchAuthenticator(input, output, teeVerifier); + + // Initialize the BatchAuthenticator with the specified owner + vm.broadcast(msg.sender); + batchAuthenticator.initialize(batchAuthenticatorOwner); + deployBatchInbox(input, output, batchAuthenticator); checkOutput(output); } diff --git a/packages/contracts-bedrock/src/L1/BatchAuthenticator.sol b/packages/contracts-bedrock/src/L1/BatchAuthenticator.sol index eda08d64b5b..42c9f51f2ea 100644 --- a/packages/contracts-bedrock/src/L1/BatchAuthenticator.sol +++ b/packages/contracts-bedrock/src/L1/BatchAuthenticator.sol @@ -33,6 +33,13 @@ contract BatchAuthenticator is ISemver, OwnableUpgradeable { nitroValidator = INitroValidator(address(espressoTEEVerifier.espressoNitroTEEVerifier())); } + /// @notice Initializes the contract with the initial owner + /// @param _owner The initial owner of the contract + function initialize(address _owner) external initializer { + __Ownable_init(); + _transferOwnership(_owner); + } + function decodeAttestationTbs(bytes memory attestation) external view returns (bytes memory, bytes memory) { return nitroValidator.decodeAttestationTbs(attestation); }