wormhole/clients/eth/main.go

219 lines
5.1 KiB
Go

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)
}
}