Add Terra and Ethereum Wormhole CLI

Change-Id: I35aa7a801abf77a2e35faa192e685ce738869624
This commit is contained in:
Hendrik Hofstadt 2021-07-22 18:29:05 +02:00
parent 24b15bba9c
commit 393b522f76
5 changed files with 1937 additions and 0 deletions

11
clients/eth/go.mod Normal file
View File

@ -0,0 +1,11 @@
module github.com/certusone/wormhole/clients/eth
go 1.16
require (
github.com/certusone/wormhole/bridge v0.0.0-20210722131135-a191017d22d0
github.com/ethereum/go-ethereum v1.10.6
github.com/spf13/cobra v1.1.1
)
replace github.com/certusone/wormhole/bridge => ../../bridge

1593
clients/eth/go.sum Normal file

File diff suppressed because it is too large Load Diff

218
clients/eth/main.go Normal file
View File

@ -0,0 +1,218 @@
package main
import (
"context"
"fmt"
"github.com/certusone/wormhole/bridge/pkg/ethereum/abi"
"github.com/certusone/wormhole/bridge/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)
}
}

115
clients/terra/main.py Normal file
View File

@ -0,0 +1,115 @@
import argparse
from terra_sdk.client.lcd import AsyncLCDClient
import asyncio
from terra_sdk.core.wasm import (
MsgExecuteContract,
)
from terra_sdk.key.mnemonic import MnemonicKey
import base64
def add_default_args(parser):
parser.add_argument('--rpc', required=True, help='Terra lcd address')
parser.add_argument('--chain-id', dest="chain_id", required=True, help='Chain ID')
parser.add_argument('--mnemonic', dest="mnemonic", required=True, help='Mnemonic of the wallet to be used')
parser.add_argument('--contract', dest="contract", required=True, help='Address of the Wormhole contract')
parser = argparse.ArgumentParser(prog='terra_cli')
subparsers = parser.add_subparsers(help='sub-command help', dest="command")
gov_parser = subparsers.add_parser('execute_governance', help='Execute a governance VAA')
add_default_args(gov_parser)
gov_parser.add_argument('vaa', help='Hex encoded VAA')
post_parser = subparsers.add_parser('post_message', help='Publish a message over the wormhole')
add_default_args(post_parser)
post_parser.add_argument('nonce', help='Nonce of the message', type=int)
post_parser.add_argument('message', help='Hex-encoded message')
gas_prices = {
"uluna": "0.15",
"usdr": "0.1018",
"uusd": "0.15",
"ukrw": "178.05",
"umnt": "431.6259",
"ueur": "0.125",
"ucny": "0.97",
"ujpy": "16",
"ugbp": "0.11",
"uinr": "11",
"ucad": "0.19",
"uchf": "0.13",
"uaud": "0.19",
"usgd": "0.2",
}
async def sign_and_broadcast(sequence, deployer, terra, *msgs):
tx = await deployer.create_and_sign_tx(
msgs=msgs, fee_denoms=["ukrw", "uusd", "uluna"], sequence=sequence, gas_adjustment=1.4,
)
result = await terra.tx.broadcast(tx)
sequence += 1
if result.is_tx_error():
raise RuntimeError(result.raw_log)
return result
class ContractQuerier:
def __init__(self, address, terra):
self.address = address
self.terra = terra
def __getattr__(self, item):
async def result_fxn(**kwargs):
return await self.terra.wasm.contract_query(self.address, {item: kwargs})
return result_fxn
class Contract:
def __init__(self, address, deployer, terra, sequence):
self.address = address
self.deployer = deployer
self.terra = terra
self.sequence = sequence
def __getattr__(self, item):
async def result_fxn(coins=None, **kwargs):
execute = MsgExecuteContract(
self.deployer.key.acc_address, self.address, {item: kwargs}, coins=coins
)
return await sign_and_broadcast(self.sequence, self.deployer, self.terra, execute)
return result_fxn
@property
def query(self):
return ContractQuerier(self.address, self.terra)
async def main():
args = parser.parse_args()
async with AsyncLCDClient(
args.rpc, args.chain_id, gas_prices=gas_prices, loop=asyncio.get_event_loop()
) as terra:
deployer = terra.wallet(MnemonicKey(
mnemonic=args.mnemonic))
sequence = await deployer.sequence()
wormhole = Contract(args.contract, deployer, terra, sequence)
res = dict()
if args.command == "execute_governance":
res = await wormhole.submit_v_a_a(vaa=base64.b64encode(bytes.fromhex(args.vaa)).decode("utf-8"))
elif args.command == "post_message":
state = await wormhole.query.get_state()
fee = state["fee"]
res = await wormhole.post_message(nonce=args.nonce,
message=base64.b64encode(bytes.fromhex(args.message)).decode("utf-8"),
coins={fee["denom"]: fee["amount"]})
print(res.logs[0].events_by_type["from_contract"])
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())