Add Terra and Ethereum Wormhole CLI
Change-Id: I35aa7a801abf77a2e35faa192e685ce738869624
This commit is contained in:
parent
24b15bba9c
commit
393b522f76
|
@ -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
|
File diff suppressed because it is too large
Load Diff
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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())
|
Loading…
Reference in New Issue