package main import ( "context" "fmt" "github.com/certusone/wormhole/node/pkg/ethereum/abi" "github.com/certusone/wormhole/node/pkg/vaa" "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/crypto" "github.com/ethereum/go-ethereum/ethclient" "github.com/spf13/cobra" "math" "os" "strconv" ) // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "eth", Short: "Wormhole Ethereum Client", } var governanceVAACommand = &cobra.Command{ Use: "execute_governance [VAA]", Short: "Execute a governance VAA", Run: executeGovernance, Args: cobra.ExactArgs(1), } var postMessageCommand = &cobra.Command{ Use: "post_message [NONCE] [NUM_CONFIRMATIONS] [MESSAGE]", Short: "Post a message to wormhole", Run: postMessage, Args: cobra.ExactArgs(3), } var ( contractAddress string rpcUrl string key string ) func init() { rootCmd.PersistentFlags().StringVar(&contractAddress, "contract", "", "Address of the Wormhole contract") rootCmd.PersistentFlags().StringVar(&rpcUrl, "rpc", "", "Ethereum RPC address") rootCmd.PersistentFlags().StringVar(&key, "key", "", "Key to sign the transaction with (hex-encoded)") rootCmd.AddCommand(governanceVAACommand) rootCmd.AddCommand(postMessageCommand) } func postMessage(cmd *cobra.Command, args []string) { nonce, err := strconv.Atoi(args[0]) if err != nil { cmd.PrintErrln("Could not parse nonce", err) os.Exit(1) } if nonce > math.MaxUint32 { cmd.PrintErrln("Nonce must not exceed MaxUint32", err) os.Exit(1) } consistencyLevel, err := strconv.Atoi(args[1]) if err != nil { cmd.PrintErrln("Could not parse confirmation number", err) os.Exit(1) } if consistencyLevel > math.MaxUint8 { cmd.PrintErrln("Confirmation number must not exceed 255", err) os.Exit(1) } message := common.Hex2Bytes(args[2]) ethC, err := getEthClient() if err != nil { cmd.PrintErrln(err) os.Exit(1) } signer, addr, err := getSigner(ethC) if err != nil { cmd.PrintErrln(err) os.Exit(1) } t, err := getTransactor(ethC) if err != nil { cmd.PrintErrln(err) os.Exit(1) } res, err := t.PublishMessage(&bind.TransactOpts{ From: addr, Signer: signer, }, uint32(nonce), message, uint8(consistencyLevel)) if err != nil { cmd.PrintErrln(err) os.Exit(1) } println("Posted tx. Hash:", res.Hash().String()) } func executeGovernance(cmd *cobra.Command, args []string) { ethC, err := getEthClient() if err != nil { cmd.PrintErrln(err) os.Exit(1) } signer, addr, err := getSigner(ethC) if err != nil { cmd.PrintErrln(err) os.Exit(1) } t, err := getTransactor(ethC) if err != nil { cmd.PrintErrln(err) os.Exit(1) } vaaData := common.Hex2Bytes(args[0]) parsedVaa, err := vaa.Unmarshal(vaaData) if err != nil { cmd.PrintErrln("Failed to parse VAA:", err) os.Exit(1) } governanceAction, err := getGovernanceVaaAction(parsedVaa.Payload) if err != nil { cmd.PrintErrln("Failed to parse governance payload:", err) os.Exit(1) } var contractFunction func(opts *bind.TransactOpts, _vm []byte) (*types.Transaction, error) switch governanceAction { case 1: println("Governance Action: ContractUpgrade") contractFunction = t.SubmitContractUpgrade case 2: println("Governance Action: NewGuardianSet") contractFunction = t.SubmitNewGuardianSet case 3: println("Governance Action: SetMessageFee") contractFunction = t.SubmitSetMessageFee case 4: println("Governance Action: TransferFees") contractFunction = t.SubmitTransferFees default: cmd.PrintErrln("Unknow governance action") os.Exit(1) } res, err := contractFunction(&bind.TransactOpts{ From: addr, Signer: signer, }, vaaData) if err != nil { cmd.PrintErrln(err) os.Exit(1) } println("Posted tx. Hash:", res.Hash().String()) } func getEthClient() (*ethclient.Client, error) { return ethclient.Dial(rpcUrl) } func getSigner(ethC *ethclient.Client) (func(address common.Address, transaction *types.Transaction) (*types.Transaction, error), common.Address, error) { cID, err := ethC.ChainID(context.Background()) if err != nil { panic(err) } keyBytes := common.Hex2Bytes(key) key, err := crypto.ToECDSA(keyBytes) if err != nil { return nil, common.Address{}, err } return func(address common.Address, transaction *types.Transaction) (*types.Transaction, error) { return types.SignTx(transaction, types.NewEIP155Signer(cID), key) }, crypto.PubkeyToAddress(key.PublicKey), nil } func getTransactor(ethC *ethclient.Client) (*abi.AbiTransactor, error) { addr := common.HexToAddress(contractAddress) emptyAddr := common.Address{} if addr == emptyAddr { return nil, fmt.Errorf("invalid contract address") } t, err := abi.NewAbiTransactor(addr, ethC) if err != nil { return nil, err } return t, nil } func getGovernanceVaaAction(payload []byte) (uint8, error) { if len(payload) < 32+2+1 { return 0, fmt.Errorf("VAA payload does not contain a governance header") } return payload[32], nil } func main() { if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } }