diff --git a/.gitignore b/.gitignore index fb007bb..2463d30 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ metadata.json # Ignores yaml configuration files in the config directory config/*.yaml + +contracts/script/output/ diff --git a/README.md b/README.md index facdda4..acbb42d 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,7 @@ Coming soon | OperatorStateRetriever | [`0xb7bb920538e038DFFEfcB55caBf713652ED2031F`](https://holesky.etherscan.io/address/0xb7bb920538e038DFFEfcB55caBf713652ED2031F) | | PauserRegistry | [`0x3A8ea6e4202CdDe4a9e0cCE19c4Dc1739ba2cF0b`](https://holesky.etherscan.io/address/0x3A8ea6e4202CdDe4a9e0cCE19c4Dc1739ba2cF0b) | | StakeRegistry | [`0x7BacD5dd5A7C3acf8bf1a3c88fB0D00B68EE626A`](https://holesky.etherscan.io/address/0x7BacD5dd5A7C3acf8bf1a3c88fB0D00B68EE626A) | +| ApConfig | [`0xb8abbb082ecaae8d1cd68378cf3b060f6f0e07eb`](https://holesky.etherscan.io/address/0xb8abbb082ecaae8d1cd68378cf3b060f6f0e07eb) | @@ -172,3 +173,4 @@ Coming soon | PauserRegistry | [`0xeec585186c37c517030ba371deac5c17e728c135`](https://etherscan.io/address/0xeec585186c37c517030ba371deac5c17e728c135) | | StakeRegistry | [`0x363b3604fE8c2323a98c00906115c8b87a512a12`](https://etherscan.io/address/0x363b3604fE8c2323a98c00906115c8b87a512a12) | | TaskManager | [`0x940f62f75cbbbd723d37c9171dc681dfba653b49`](https://etherscan.io/address/0x940f62f75cbbbd723d37c9171dc681dfba653b49) | +| ApConfig | [`0x9c02dfc92eea988902a98919bf4f035e4aaefced`](https://etherscan.io/address/0x9c02dfc92eea988902a98919bf4f035e4aaefced) | diff --git a/cmd/createAliasKey.go b/cmd/createAliasKey.go new file mode 100644 index 0000000..dcbca2a --- /dev/null +++ b/cmd/createAliasKey.go @@ -0,0 +1,39 @@ +/* +Copyright © 2024 Ava Protocol +*/ +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/AvaProtocol/ap-avs/operator" +) + +var ( + aliasKeyOption = operator.CreateAliasKeyOption{} +) + +// createAliasKeyCmd represents the createAliasKey command +var createAliasKeyCmd = &cobra.Command{ + Use: "create-alias-key", + Short: "Create an ECDSA private key only for AP AVS operation", + Long: `Generate an ECDSA private key to use for AP AVS operation. + +Instead of using the operator's ECDSA private key to interact with +Ava Protocol AVS, you can generate an alias key and use this key to +interact with Ava Protocol operator. You will still need the EigenLayer +Operator ECDSA key to register or deregister from the AVS. But once +you registered, you don't need that operator key anymore`, + Run: func(cmd *cobra.Command, args []string) { + operator.CreateOrImportAliasKey(aliasKeyOption) + }, +} + +func init() { + rootCmd.AddCommand(createAliasKeyCmd) + + createAliasKeyCmd.Flags().StringVarP(&(aliasKeyOption.PrivateKey), "ecdsa-private-key", "k", "", "a private key start with 0x to import as alias key") + + createAliasKeyCmd.Flags().StringVarP(&(aliasKeyOption.Filename), "name", "n", "alias-ecdsa.key.json", "absolute or relative file path to save your ECDSA key to") + createAliasKeyCmd.MarkPersistentFlagRequired("name") +} diff --git a/cmd/declareAlias.go b/cmd/declareAlias.go new file mode 100644 index 0000000..0ec113f --- /dev/null +++ b/cmd/declareAlias.go @@ -0,0 +1,30 @@ +/* +Copyright © 2024 Ava Protocol +*/ +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/AvaProtocol/ap-avs/operator" +) + +// declareAliasCmd represents the declareAlias command +var declareAliasCmd = &cobra.Command{ + Use: "declare-alias", + Short: "Declare an alias ecdsa key file for the operator address", + Long: `Declare an alias ecdsa key file for the operator address + +After creating an alias key, they key can be declare as +an alias for the operator address`, + Run: func(cmd *cobra.Command, args []string) { + operator.DeclareAlias(config, aliasKeyOption.Filename) + }, +} + +func init() { + rootCmd.AddCommand(declareAliasCmd) + + declareAliasCmd.Flags().StringVarP(&(aliasKeyOption.Filename), "name", "n", "alias-ecdsa.key.json", "absolute or relative file path to alias ECDSA key to declare alias") + declareAliasCmd.MarkPersistentFlagRequired("name") +} diff --git a/cmd/removeAlias.go b/cmd/removeAlias.go new file mode 100644 index 0000000..ac4e12a --- /dev/null +++ b/cmd/removeAlias.go @@ -0,0 +1,29 @@ +/* +Copyright © 2024 NAME HERE +*/ +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/AvaProtocol/ap-avs/operator" +) + +// removeAliasCmd represents the removeAlias command +var removeAliasCmd = &cobra.Command{ + Use: "remove-alias", + Short: "Unbind alias address from your operator", + Long: `Unbind alias key from your operator address + +After removal, you will either need to setup another alias key, or to use your operator ECDSA key. + +When removing alias, you can run it with alias key +`, + Run: func(cmd *cobra.Command, args []string) { + operator.RemoveAlias(config) + }, +} + +func init() { + rootCmd.AddCommand(removeAliasCmd) +} diff --git a/contracts/deploy-ap-config.sh b/contracts/deploy-ap-config.sh new file mode 100644 index 0000000..42f4309 --- /dev/null +++ b/contracts/deploy-ap-config.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -xe + +forge script \ + script/DeployAPConfig.s.sol:DeployAPConfig \ + --rpc-url $RPC_URL \ + --private-key $PRIVATE_KEY \ + --etherscan-api-key $ETHSCAN_API_KEY \ + --broadcast --verify \ + --slow \ + -vvvv + + diff --git a/contracts/script/DeployAPConfig.s.sol b/contracts/script/DeployAPConfig.s.sol new file mode 100644 index 0000000..9a7b0d3 --- /dev/null +++ b/contracts/script/DeployAPConfig.s.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.12; + +import "forge-std/Script.sol"; + +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; + +import {APConfig} from "../src/core/APConfig.sol"; + +// To deployment and swap the implementation set 2 envs: +// CREATE_PROXY=false +// AP_PROXY_ADDRESS=0x123 +// PROXY_ADMIN_ADDRESS=0x456 +// When seeing the two env, the script will upgrade the underlying contract +// +// Example: +// AP_PROXY_ADDRESS=0xb8abbb082ecaae8d1cd68378cf3b060f6f0e07eb \ +// SWAP_IMPL=true bash deploy-ap-config.sh +contract DeployAPConfig is Script { + function run() external { + address oakAVSProxyAdmin = vm.envAddress("PROXY_ADMIN_ADDRESS"); + bool swapImpl = vm.envBool("SWAP_IMPL"); + + vm.startBroadcast(); + + string memory output = "APConfig deployment output"; + + // 1. Deploy the implementation + APConfig apConfig = new APConfig(); + vm.serializeAddress(output, "apConfigImpl", address(apConfig)); + + if (swapImpl) { + ProxyAdmin oakProxyAdmin = + ProxyAdmin(oakAVSProxyAdmin); + + // 3. Here we want + address apProxyAddress = vm.envAddress("AP_PROXY_ADDRESS"); + oakProxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(apProxyAddress)), + address(apConfig) + ); + } else { + // 2. Deploy the proxy contract + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(apConfig), + address(oakAVSProxyAdmin), + "" + ); + vm.serializeAddress(output, "apConfigProxy", address(proxy)); + } + + string memory registryJson = vm.serializeString(output, "object", output); + vm.writeJson(registryJson, "./script/output/ap_config.json"); + + vm.stopBroadcast(); + } +} diff --git a/contracts/src/core/APConfig.sol b/contracts/src/core/APConfig.sol new file mode 100644 index 0000000..dc2dfe5 --- /dev/null +++ b/contracts/src/core/APConfig.sol @@ -0,0 +1,39 @@ +pragma solidity ^0.8.12; + +import "../interfaces/IAPConfig.sol"; + +contract APConfig is IAPConfig { + // Mapping from operator address to alias address + mapping(address => address) private operatorToAlias; + mapping(address => address) private aliasToOperator; + + // Function to declare an alias for the operator + function declareAlias(address _alias) external override { + require(_alias != address(0), "Alias address cannot be the zero address"); + require(_alias != msg.sender, "Alias address cannot be the same with operator address"); + + operatorToAlias[msg.sender] = _alias; + aliasToOperator[_alias] = msg.sender; + + emit AliasDeclared(msg.sender, _alias); + } + + // Function to undeclare an alias for the operator + function undeclare() external override { + require(aliasToOperator[msg.sender] != address(0), "No alias declared for this operator"); + + delete operatorToAlias[aliasToOperator[msg.sender]]; + delete aliasToOperator[msg.sender]; + + emit AliasUndeclared(msg.sender); + } + + // Function to get the alias of an operator + function getAlias(address _operator) external view override returns (address) { + return operatorToAlias[_operator]; + } + + function getOperatorForAlias(address _alias) external view override returns (address) { + return aliasToOperator[_alias]; + } +} diff --git a/contracts/src/interfaces/IAPConfig.sol b/contracts/src/interfaces/IAPConfig.sol new file mode 100644 index 0000000..ed64727 --- /dev/null +++ b/contracts/src/interfaces/IAPConfig.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.9; + +interface IAPConfig { + // Event emitted when an alias is declared + event AliasDeclared(address indexed operator, address indexed aliasAddress); + + // Event emitted when an alias is undeclared + event AliasUndeclared(address indexed operator); + + // Function to declare an alias for the operator + function declareAlias(address _alias) external; + + // Function to undeclare an alias for the operator + function undeclare() external; + + // Function to get the alias of an operator + function getAlias(address _operator) external view returns (address); + function getOperatorForAlias(address _alias) external view returns (address); +} diff --git a/core/chainio/abis/apconfig.abi b/core/chainio/abis/apconfig.abi new file mode 100644 index 0000000..dd2545c --- /dev/null +++ b/core/chainio/abis/apconfig.abi @@ -0,0 +1 @@ +[{"type":"function","name":"declareAlias","inputs":[{"name":"_alias","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"getAlias","inputs":[{"name":"_operator","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"getOperatorForAlias","inputs":[{"name":"_alias","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"undeclare","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"AliasDeclared","inputs":[{"name":"operator","type":"address","indexed":true,"internalType":"address"},{"name":"aliasAddress","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"AliasUndeclared","inputs":[{"name":"operator","type":"address","indexed":true,"internalType":"address"}],"anonymous":false}] diff --git a/core/chainio/apconfig/Makefile b/core/chainio/apconfig/Makefile new file mode 100644 index 0000000..5e672a0 --- /dev/null +++ b/core/chainio/apconfig/Makefile @@ -0,0 +1,2 @@ +genabi: + abigen --abi=../abis/apconfig.abi --pkg=apconfig --type=APConfig --out=binding.go diff --git a/core/chainio/apconfig/apconfig.go b/core/chainio/apconfig/apconfig.go new file mode 100644 index 0000000..77c8f86 --- /dev/null +++ b/core/chainio/apconfig/apconfig.go @@ -0,0 +1,15 @@ +package apconfig + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" +) + +func GetContract(ethRpcURL string, address common.Address) (*APConfig, error) { + ethRpcClient, err := ethclient.Dial(ethRpcURL) + if err != nil { + return nil, err + } + + return NewAPConfig(address, ethRpcClient) +} diff --git a/core/chainio/apconfig/binding.go b/core/chainio/apconfig/binding.go new file mode 100644 index 0000000..60aa47a --- /dev/null +++ b/core/chainio/apconfig/binding.go @@ -0,0 +1,582 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package apconfig + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "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/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// APConfigMetaData contains all meta data concerning the APConfig contract. +var APConfigMetaData = &bind.MetaData{ + ABI: "[{\"type\":\"function\",\"name\":\"declareAlias\",\"inputs\":[{\"name\":\"_alias\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"getAlias\",\"inputs\":[{\"name\":\"_operator\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getOperatorForAlias\",\"inputs\":[{\"name\":\"_alias\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"undeclare\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"AliasDeclared\",\"inputs\":[{\"name\":\"operator\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"aliasAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AliasUndeclared\",\"inputs\":[{\"name\":\"operator\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false}]", +} + +// APConfigABI is the input ABI used to generate the binding from. +// Deprecated: Use APConfigMetaData.ABI instead. +var APConfigABI = APConfigMetaData.ABI + +// APConfig is an auto generated Go binding around an Ethereum contract. +type APConfig struct { + APConfigCaller // Read-only binding to the contract + APConfigTransactor // Write-only binding to the contract + APConfigFilterer // Log filterer for contract events +} + +// APConfigCaller is an auto generated read-only Go binding around an Ethereum contract. +type APConfigCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// APConfigTransactor is an auto generated write-only Go binding around an Ethereum contract. +type APConfigTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// APConfigFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type APConfigFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// APConfigSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type APConfigSession struct { + Contract *APConfig // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// APConfigCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type APConfigCallerSession struct { + Contract *APConfigCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// APConfigTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type APConfigTransactorSession struct { + Contract *APConfigTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// APConfigRaw is an auto generated low-level Go binding around an Ethereum contract. +type APConfigRaw struct { + Contract *APConfig // Generic contract binding to access the raw methods on +} + +// APConfigCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type APConfigCallerRaw struct { + Contract *APConfigCaller // Generic read-only contract binding to access the raw methods on +} + +// APConfigTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type APConfigTransactorRaw struct { + Contract *APConfigTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewAPConfig creates a new instance of APConfig, bound to a specific deployed contract. +func NewAPConfig(address common.Address, backend bind.ContractBackend) (*APConfig, error) { + contract, err := bindAPConfig(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &APConfig{APConfigCaller: APConfigCaller{contract: contract}, APConfigTransactor: APConfigTransactor{contract: contract}, APConfigFilterer: APConfigFilterer{contract: contract}}, nil +} + +// NewAPConfigCaller creates a new read-only instance of APConfig, bound to a specific deployed contract. +func NewAPConfigCaller(address common.Address, caller bind.ContractCaller) (*APConfigCaller, error) { + contract, err := bindAPConfig(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &APConfigCaller{contract: contract}, nil +} + +// NewAPConfigTransactor creates a new write-only instance of APConfig, bound to a specific deployed contract. +func NewAPConfigTransactor(address common.Address, transactor bind.ContractTransactor) (*APConfigTransactor, error) { + contract, err := bindAPConfig(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &APConfigTransactor{contract: contract}, nil +} + +// NewAPConfigFilterer creates a new log filterer instance of APConfig, bound to a specific deployed contract. +func NewAPConfigFilterer(address common.Address, filterer bind.ContractFilterer) (*APConfigFilterer, error) { + contract, err := bindAPConfig(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &APConfigFilterer{contract: contract}, nil +} + +// bindAPConfig binds a generic wrapper to an already deployed contract. +func bindAPConfig(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := APConfigMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_APConfig *APConfigRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _APConfig.Contract.APConfigCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_APConfig *APConfigRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _APConfig.Contract.APConfigTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_APConfig *APConfigRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _APConfig.Contract.APConfigTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_APConfig *APConfigCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _APConfig.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_APConfig *APConfigTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _APConfig.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_APConfig *APConfigTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _APConfig.Contract.contract.Transact(opts, method, params...) +} + +// GetAlias is a free data retrieval call binding the contract method 0x99900d11. +// +// Solidity: function getAlias(address _operator) view returns(address) +func (_APConfig *APConfigCaller) GetAlias(opts *bind.CallOpts, _operator common.Address) (common.Address, error) { + var out []interface{} + err := _APConfig.contract.Call(opts, &out, "getAlias", _operator) + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// GetAlias is a free data retrieval call binding the contract method 0x99900d11. +// +// Solidity: function getAlias(address _operator) view returns(address) +func (_APConfig *APConfigSession) GetAlias(_operator common.Address) (common.Address, error) { + return _APConfig.Contract.GetAlias(&_APConfig.CallOpts, _operator) +} + +// GetAlias is a free data retrieval call binding the contract method 0x99900d11. +// +// Solidity: function getAlias(address _operator) view returns(address) +func (_APConfig *APConfigCallerSession) GetAlias(_operator common.Address) (common.Address, error) { + return _APConfig.Contract.GetAlias(&_APConfig.CallOpts, _operator) +} + +// GetOperatorForAlias is a free data retrieval call binding the contract method 0x8139d05b. +// +// Solidity: function getOperatorForAlias(address _alias) view returns(address) +func (_APConfig *APConfigCaller) GetOperatorForAlias(opts *bind.CallOpts, _alias common.Address) (common.Address, error) { + var out []interface{} + err := _APConfig.contract.Call(opts, &out, "getOperatorForAlias", _alias) + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// GetOperatorForAlias is a free data retrieval call binding the contract method 0x8139d05b. +// +// Solidity: function getOperatorForAlias(address _alias) view returns(address) +func (_APConfig *APConfigSession) GetOperatorForAlias(_alias common.Address) (common.Address, error) { + return _APConfig.Contract.GetOperatorForAlias(&_APConfig.CallOpts, _alias) +} + +// GetOperatorForAlias is a free data retrieval call binding the contract method 0x8139d05b. +// +// Solidity: function getOperatorForAlias(address _alias) view returns(address) +func (_APConfig *APConfigCallerSession) GetOperatorForAlias(_alias common.Address) (common.Address, error) { + return _APConfig.Contract.GetOperatorForAlias(&_APConfig.CallOpts, _alias) +} + +// DeclareAlias is a paid mutator transaction binding the contract method 0xf405566d. +// +// Solidity: function declareAlias(address _alias) returns() +func (_APConfig *APConfigTransactor) DeclareAlias(opts *bind.TransactOpts, _alias common.Address) (*types.Transaction, error) { + return _APConfig.contract.Transact(opts, "declareAlias", _alias) +} + +// DeclareAlias is a paid mutator transaction binding the contract method 0xf405566d. +// +// Solidity: function declareAlias(address _alias) returns() +func (_APConfig *APConfigSession) DeclareAlias(_alias common.Address) (*types.Transaction, error) { + return _APConfig.Contract.DeclareAlias(&_APConfig.TransactOpts, _alias) +} + +// DeclareAlias is a paid mutator transaction binding the contract method 0xf405566d. +// +// Solidity: function declareAlias(address _alias) returns() +func (_APConfig *APConfigTransactorSession) DeclareAlias(_alias common.Address) (*types.Transaction, error) { + return _APConfig.Contract.DeclareAlias(&_APConfig.TransactOpts, _alias) +} + +// Undeclare is a paid mutator transaction binding the contract method 0x2c46b3e1. +// +// Solidity: function undeclare() returns() +func (_APConfig *APConfigTransactor) Undeclare(opts *bind.TransactOpts) (*types.Transaction, error) { + return _APConfig.contract.Transact(opts, "undeclare") +} + +// Undeclare is a paid mutator transaction binding the contract method 0x2c46b3e1. +// +// Solidity: function undeclare() returns() +func (_APConfig *APConfigSession) Undeclare() (*types.Transaction, error) { + return _APConfig.Contract.Undeclare(&_APConfig.TransactOpts) +} + +// Undeclare is a paid mutator transaction binding the contract method 0x2c46b3e1. +// +// Solidity: function undeclare() returns() +func (_APConfig *APConfigTransactorSession) Undeclare() (*types.Transaction, error) { + return _APConfig.Contract.Undeclare(&_APConfig.TransactOpts) +} + +// APConfigAliasDeclaredIterator is returned from FilterAliasDeclared and is used to iterate over the raw logs and unpacked data for AliasDeclared events raised by the APConfig contract. +type APConfigAliasDeclaredIterator struct { + Event *APConfigAliasDeclared // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *APConfigAliasDeclaredIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(APConfigAliasDeclared) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(APConfigAliasDeclared) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *APConfigAliasDeclaredIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *APConfigAliasDeclaredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// APConfigAliasDeclared represents a AliasDeclared event raised by the APConfig contract. +type APConfigAliasDeclared struct { + Operator common.Address + AliasAddress common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterAliasDeclared is a free log retrieval operation binding the contract event 0xf9528232876ca43a75b6f0ae52cdae8a80b29a7a53569f2e9966c414fa029195. +// +// Solidity: event AliasDeclared(address indexed operator, address indexed aliasAddress) +func (_APConfig *APConfigFilterer) FilterAliasDeclared(opts *bind.FilterOpts, operator []common.Address, aliasAddress []common.Address) (*APConfigAliasDeclaredIterator, error) { + + var operatorRule []interface{} + for _, operatorItem := range operator { + operatorRule = append(operatorRule, operatorItem) + } + var aliasAddressRule []interface{} + for _, aliasAddressItem := range aliasAddress { + aliasAddressRule = append(aliasAddressRule, aliasAddressItem) + } + + logs, sub, err := _APConfig.contract.FilterLogs(opts, "AliasDeclared", operatorRule, aliasAddressRule) + if err != nil { + return nil, err + } + return &APConfigAliasDeclaredIterator{contract: _APConfig.contract, event: "AliasDeclared", logs: logs, sub: sub}, nil +} + +// WatchAliasDeclared is a free log subscription operation binding the contract event 0xf9528232876ca43a75b6f0ae52cdae8a80b29a7a53569f2e9966c414fa029195. +// +// Solidity: event AliasDeclared(address indexed operator, address indexed aliasAddress) +func (_APConfig *APConfigFilterer) WatchAliasDeclared(opts *bind.WatchOpts, sink chan<- *APConfigAliasDeclared, operator []common.Address, aliasAddress []common.Address) (event.Subscription, error) { + + var operatorRule []interface{} + for _, operatorItem := range operator { + operatorRule = append(operatorRule, operatorItem) + } + var aliasAddressRule []interface{} + for _, aliasAddressItem := range aliasAddress { + aliasAddressRule = append(aliasAddressRule, aliasAddressItem) + } + + logs, sub, err := _APConfig.contract.WatchLogs(opts, "AliasDeclared", operatorRule, aliasAddressRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(APConfigAliasDeclared) + if err := _APConfig.contract.UnpackLog(event, "AliasDeclared", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseAliasDeclared is a log parse operation binding the contract event 0xf9528232876ca43a75b6f0ae52cdae8a80b29a7a53569f2e9966c414fa029195. +// +// Solidity: event AliasDeclared(address indexed operator, address indexed aliasAddress) +func (_APConfig *APConfigFilterer) ParseAliasDeclared(log types.Log) (*APConfigAliasDeclared, error) { + event := new(APConfigAliasDeclared) + if err := _APConfig.contract.UnpackLog(event, "AliasDeclared", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// APConfigAliasUndeclaredIterator is returned from FilterAliasUndeclared and is used to iterate over the raw logs and unpacked data for AliasUndeclared events raised by the APConfig contract. +type APConfigAliasUndeclaredIterator struct { + Event *APConfigAliasUndeclared // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *APConfigAliasUndeclaredIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(APConfigAliasUndeclared) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(APConfigAliasUndeclared) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *APConfigAliasUndeclaredIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *APConfigAliasUndeclaredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// APConfigAliasUndeclared represents a AliasUndeclared event raised by the APConfig contract. +type APConfigAliasUndeclared struct { + Operator common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterAliasUndeclared is a free log retrieval operation binding the contract event 0x8f92aba3a92a6e1c96ff1ae5812518155f45d1baf5651a7653e2250371805c0d. +// +// Solidity: event AliasUndeclared(address indexed operator) +func (_APConfig *APConfigFilterer) FilterAliasUndeclared(opts *bind.FilterOpts, operator []common.Address) (*APConfigAliasUndeclaredIterator, error) { + + var operatorRule []interface{} + for _, operatorItem := range operator { + operatorRule = append(operatorRule, operatorItem) + } + + logs, sub, err := _APConfig.contract.FilterLogs(opts, "AliasUndeclared", operatorRule) + if err != nil { + return nil, err + } + return &APConfigAliasUndeclaredIterator{contract: _APConfig.contract, event: "AliasUndeclared", logs: logs, sub: sub}, nil +} + +// WatchAliasUndeclared is a free log subscription operation binding the contract event 0x8f92aba3a92a6e1c96ff1ae5812518155f45d1baf5651a7653e2250371805c0d. +// +// Solidity: event AliasUndeclared(address indexed operator) +func (_APConfig *APConfigFilterer) WatchAliasUndeclared(opts *bind.WatchOpts, sink chan<- *APConfigAliasUndeclared, operator []common.Address) (event.Subscription, error) { + + var operatorRule []interface{} + for _, operatorItem := range operator { + operatorRule = append(operatorRule, operatorItem) + } + + logs, sub, err := _APConfig.contract.WatchLogs(opts, "AliasUndeclared", operatorRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(APConfigAliasUndeclared) + if err := _APConfig.contract.UnpackLog(event, "AliasUndeclared", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseAliasUndeclared is a log parse operation binding the contract event 0x8f92aba3a92a6e1c96ff1ae5812518155f45d1baf5651a7653e2250371805c0d. +// +// Solidity: event AliasUndeclared(address indexed operator) +func (_APConfig *APConfigFilterer) ParseAliasUndeclared(log types.Log) (*APConfigAliasUndeclared, error) { + event := new(APConfigAliasUndeclared) + if err := _APConfig.contract.UnpackLog(event, "AliasUndeclared", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/operator/alias.go b/operator/alias.go new file mode 100644 index 0000000..0d023d4 --- /dev/null +++ b/operator/alias.go @@ -0,0 +1,159 @@ +package operator + +import ( + "context" + "crypto/ecdsa" + "fmt" + + "github.com/AvaProtocol/ap-avs/core/chainio/apconfig" + "github.com/ethereum/go-ethereum/crypto" + + eigensdkecdsa "github.com/Layr-Labs/eigensdk-go/crypto/ecdsa" +) + +type CreateAliasKeyOption struct { + Filename string // full path to the ecdsa json key file + PrivateKey string +} + +// Create or import +func CreateOrImportAliasKey(o CreateAliasKeyOption) { + var err error + var aliasEcdsaPair *ecdsa.PrivateKey + + passphrase := loadECDSAPassword() + + if len(passphrase) == 0 { + fmt.Printf("missing pass phrase. aborted\n") + return + } + + if len(passphrase) <= 12 { + fmt.Printf("Pass pharease is too short, it should have at least 12 character. aborted\n") + return + } + + aliasEcdsaPair, err = eigensdkecdsa.ReadKey(o.Filename, passphrase) + if err == nil { + fmt.Printf("%s key already existed. we won't override the key.\nTo write to a different file, set the `--name` parameter.\nUse `--help` to view parameter detail.\n", o.Filename) + return + } + + if o.PrivateKey == "" { + aliasEcdsaPair, err = crypto.GenerateKey() + if err != nil { + panic(fmt.Errorf("cannot generate key %w", err)) + } + } else { + aliasEcdsaPair, err = crypto.HexToECDSA(o.PrivateKey) + if err != nil { + panic(fmt.Errorf("cannot import provided private key %s with error: %w", o.PrivateKey, err)) + } + } + + if err = eigensdkecdsa.WriteKey(o.Filename, aliasEcdsaPair, passphrase); err != nil { + fmt.Printf("Error writing the file %s: %v\n", o.Filename, err) + return + } + + fmt.Printf("alias key is succesfully written to %s and encrypted with your provider passphrease.\n", o.Filename) +} + +// Declare alias key for the operator +func DeclareAlias(configPath, address string) { + operator, err := NewOperatorFromConfigFile(configPath) + if err != nil { + fmt.Errorf("error creator operator from config: %w", err) + } + + if err = operator.DeclareAlias(address); err != nil { + panic(err) + } +} + +func (o *Operator) DeclareAlias(filepath string) error { + apConfigContract, err := apconfig.GetContract(o.config.EthRpcUrl, o.apConfigAddr) + if err != nil { + panic(fmt.Errorf("cannot create apconfig contract writer: %w", err)) + } + + noSendTxOpts, err := o.txManager.GetNoSendTxOpts() + if err != nil { + return fmt.Errorf("Error creating transaction object %v", err) + } + + passphrase := loadECDSAPassword() + + aliasEcdsaPair, err := eigensdkecdsa.ReadKey(filepath, passphrase) + if err != nil { + return fmt.Errorf("cannot parse the alias ecdsa key file %v", err) + } + + tx, err := apConfigContract.DeclareAlias( + noSendTxOpts, + crypto.PubkeyToAddress(aliasEcdsaPair.PublicKey), + ) + if err != nil { + return fmt.Errorf("Failed to create APConfig.declareAlias transaction %v", err) + } + + ctx := context.Background() + receipt, err := o.txManager.Send(ctx, tx) + if err != nil { + return fmt.Errorf("declareAlias transaction failed %w", err) + } + + if receipt.Status != 1 { + return fmt.Errorf("declareAlias transaction %w reverted", receipt.TxHash.Hex()) + } + + fmt.Printf("succesfully declared an alias for operator %s alias address %s at tx %s ", o.operatorAddr.String(), crypto.PubkeyToAddress(aliasEcdsaPair.PublicKey), receipt.TxHash.Hex()) + return nil +} + +// Remove alias key for the operator +func RemoveAlias(configPath string) { + operator, err := NewOperatorFromConfigFile(configPath) + fmt.Println(configPath) + if err != nil { + fmt.Errorf("error creator operator from config: %w", err) + } + + if err = operator.RemoveAlias(); err != nil { + panic(err) + } +} + +func (o *Operator) RemoveAlias() error { + apConfigContract, err := apconfig.GetContract(o.config.EthRpcUrl, o.apConfigAddr) + if err != nil { + panic(fmt.Errorf("cannot create apconfig contract writer: %w", err)) + } + + if o.signerAddress.Cmp(o.operatorAddr) == 0 { + return fmt.Errorf("not using alias key") + } + + noSendTxOpts, err := o.txManager.GetNoSendTxOpts() + if err != nil { + return fmt.Errorf("Error creating transaction object %v", err) + } + + tx, err := apConfigContract.Undeclare(noSendTxOpts) + if err != nil { + return fmt.Errorf("Failed to create APConfig.declareAlias transaction %v", err) + } + + ctx := context.Background() + receipt, err := o.txManager.Send(ctx, tx) + if err != nil { + return fmt.Errorf("declareAlias transaction failed %w", err) + } + + if receipt.Status != 1 { + return fmt.Errorf("declareAlias transaction %w reverted", receipt.TxHash.Hex()) + } + + fmt.Printf("succesfully remove alias %s for operator %s at tx %s ", o.signerAddress.String(), o.operatorAddr.String(), receipt.TxHash.Hex()) + return nil +} diff --git a/operator/envs.go b/operator/envs.go new file mode 100644 index 0000000..09ebdc0 --- /dev/null +++ b/operator/envs.go @@ -0,0 +1,26 @@ +package operator + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +// Populate configuration based on known env +// TODO: We can fetch this dynamically from aggregator so we can upgrade the +// config without the need to release operator +var ( + mainnetChainID = big.NewInt(0) +) + +func (o *Operator) PopulateKnownConfigByChainID(chainID *big.Int) error { + if chainID.Cmp(mainnetChainID) == 0 { + // TODO: fill in with deployment later on + o.apConfigAddr = common.HexToAddress("0x9c02dfc92eea988902a98919bf4f035e4aaefced") + } else { + // Testnet + o.apConfigAddr = common.HexToAddress("0xb8abbb082ecaae8d1cd68378cf3b060f6f0e07eb") + } + + return nil +} diff --git a/operator/operator.go b/operator/operator.go index e1bc66f..f38ea52 100644 --- a/operator/operator.go +++ b/operator/operator.go @@ -9,6 +9,7 @@ import ( "google.golang.org/grpc" "github.com/AvaProtocol/ap-avs/core/chainio" + "github.com/AvaProtocol/ap-avs/core/chainio/apconfig" "github.com/AvaProtocol/ap-avs/metrics" "github.com/Layr-Labs/eigensdk-go/metrics/collectors/economic" rpccalls "github.com/Layr-Labs/eigensdk-go/metrics/collectors/rpc_calls" @@ -69,6 +70,7 @@ type Operator struct { logger logging.Logger ethClient eth.Client ethWsClient eth.Client + txManager *txmgr.SimpleTxManager // TODO(samlaf): remove both avsWriter and eigenlayerWrite from operator // they are only used for registration, so we should make a special registration package @@ -89,6 +91,9 @@ type Operator struct { // Through the passpharese of operator ecdsa, we can compute the private key operatorEcdsaPrivateKey *ecdsa.PrivateKey + // signerAddress match operatorAddr unless the operator use alias key + signerAddress common.Address + // receive new tasks in this chan (typically from listening to onchain event) newTaskCreatedChan chan *cstaskmanager.ContractAutomationTaskManagerNewTaskCreated // rpc client to send signed task responses to aggregator @@ -96,6 +101,9 @@ type Operator struct { aggregatorConn *grpc.ClientConn // needed when opting in to avs (allow this service manager contract to slash operator) credibleSquaringServiceManagerAddr common.Address + + // contract that hold our configuration. Currently only alias key mapping + apConfigAddr common.Address } func RunWithConfig(configPath string) { @@ -148,7 +156,7 @@ func NewOperatorFromConfig(c OperatorConfig) (*Operator, error) { } ethWsClient, err = eth.NewInstrumentedClient(c.EthWsUrl, rpcCallsCollector) if err != nil { - logger.Errorf("Cannot create ws ethclient", "err", err) + logger.Errorf("Cannot create ws ethclient %s %w", c.EthWsUrl, err) return nil, err } } else { @@ -173,27 +181,24 @@ func NewOperatorFromConfig(c OperatorConfig) (*Operator, error) { logger.Errorf("Cannot parse bls private key: %s err: %w", c.BlsPrivateKeyStorePath, err) return nil, err } - // TODO(samlaf): should we add the chainId to the config instead? - // this way we can prevent creating a signer that signs on mainnet by mistake - // if the config says chainId=5, then we can only create a goerli signer + chainId, err := ethRpcClient.ChainID(context.Background()) if err != nil { logger.Error("Cannot get chainId", "err", err) return nil, err } - ecdsaKeyPassword, ok := os.LookupEnv("OPERATOR_ECDSA_KEY_PASSWORD") - if !ok { - logger.Warnf("OPERATOR_ECDSA_KEY_PASSWORD env var not set. using empty string") - } + ecdsaKeyPassword := loadECDSAPassword() - signerV2, _, err := signerv2.SignerFromConfig(signerv2.Config{ + signerV2, signerAddress, err := signerv2.SignerFromConfig(signerv2.Config{ KeystorePath: c.EcdsaPrivateKeyStorePath, Password: ecdsaKeyPassword, }, chainId) + if err != nil { panic(err) } + chainioConfig := clients.BuildAllConfig{ EthHttpUrl: c.EthRpcUrl, EthWsUrl: c.EthWsUrl, @@ -214,11 +219,11 @@ func NewOperatorFromConfig(c OperatorConfig) (*Operator, error) { if err != nil { panic(err) } - skWallet, err := wallet.NewPrivateKeyWallet(ethRpcClient, signerV2, common.HexToAddress(c.OperatorAddress), logger) + skWallet, err := wallet.NewPrivateKeyWallet(ethRpcClient, signerV2, signerAddress, logger) if err != nil { panic(err) } - txMgr := txmgr.NewSimpleTxManager(skWallet, ethRpcClient, logger, common.HexToAddress(c.OperatorAddress)) + txMgr := txmgr.NewSimpleTxManager(skWallet, ethRpcClient, logger, signerAddress) avsWriter, err := chainio.BuildAvsWriter( txMgr, common.HexToAddress(c.AVSRegistryCoordinatorAddress), @@ -285,6 +290,7 @@ func NewOperatorFromConfig(c OperatorConfig) (*Operator, error) { eigenlayerWriter: sdkClients.ElChainWriter, blsKeypair: blsKeyPair, operatorAddr: common.HexToAddress(c.OperatorAddress), + signerAddress: signerAddress, aggregatorRpcClient: aggregatorRpcClient, aggregatorConn: aggregatorConn, @@ -293,8 +299,12 @@ func NewOperatorFromConfig(c OperatorConfig) (*Operator, error) { credibleSquaringServiceManagerAddr: common.HexToAddress(c.AVSRegistryCoordinatorAddress), operatorId: [32]byte{0}, // this is set below operatorEcdsaPrivateKey: operatorEcdsaPrivateKey, + + txManager: txMgr, } + operator.PopulateKnownConfigByChainID(chainId) + // OperatorId is set in contract during registration so we get it after registering operator. operatorId, err := sdkClients.AvsRegistryChainReader.GetOperatorId(&bind.CallOpts{}, operator.operatorAddr) if err != nil { @@ -305,6 +315,7 @@ func NewOperatorFromConfig(c OperatorConfig) (*Operator, error) { logger.Info("Operator info", "operatorId", operatorId, "operatorAddr", c.OperatorAddress, + "signerAddr", operator.signerAddress, "operatorG1Pubkey", operator.blsKeypair.GetPubKeyG1(), "operatorG2Pubkey", operator.blsKeypair.GetPubKeyG2(), ) @@ -314,6 +325,22 @@ func NewOperatorFromConfig(c OperatorConfig) (*Operator, error) { } func (o *Operator) Start(ctx context.Context) error { + if o.signerAddress.Cmp(o.operatorAddr) != 0 { + // Ensure alias key is correctly bind to operator address + o.logger.Infof("checking operator alias address. operator: %s alias %s", o.operatorAddr, o.signerAddress) + apConfigContract, err := apconfig.GetContract(o.config.EthRpcUrl, o.apConfigAddr) + aliasAddress, err := apConfigContract.GetAlias(nil, o.operatorAddr) + if err != nil { + panic(err) + } + + if o.signerAddress.Cmp(aliasAddress) == 0 { + o.logger.Infof("Confirm operator %s matches alias %s", o.operatorAddr, o.signerAddress) + } else { + panic(fmt.Errorf("ECDSA private key doesn't match operator address")) + } + } + operatorIsRegistered, err := o.avsReader.IsOperatorRegistered(&bind.CallOpts{}, o.operatorAddr) if err != nil { o.logger.Error("Error checking if operator is registered", "err", err) diff --git a/operator/password.go b/operator/password.go new file mode 100644 index 0000000..8d0f0df --- /dev/null +++ b/operator/password.go @@ -0,0 +1,21 @@ +package operator + +import ( + "fmt" + "os" +) + +// lookup and return passphrase from env var. panic to fail fast if a passphrase +// isn't existed in the env +func loadECDSAPassword() string { + passphrase, ok := os.LookupEnv("OPERATOR_ECDSA_KEY_PASSWORD") + if !ok { + panic(fmt.Errorf("missing OPERATOR_ECDSA_KEY_PASSWORD env var")) + } + + if passphrase == "" { + panic("passphrase is empty. pleae make sure you define OPERATOR_ECDSA_KEY_PASSWORD") + } + + return passphrase +}