Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

node: generalised governance #3895

Merged
merged 6 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions node/cmd/guardiand/admintemplate.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ var ibcUpdateChannelChainChainId *string
var recoverChainIdEvmChainId *string
var recoverChainIdNewChainId *string

var governanceContractAddress *string
var governanceTargetAddress *string
var governanceTargetChain *string
var governanceCallData *string

func init() {
governanceFlagSet := pflag.NewFlagSet("governance", pflag.ExitOnError)
chainID = governanceFlagSet.String("chain-id", "", "Chain ID")
Expand Down Expand Up @@ -171,6 +176,19 @@ func init() {
AdminClientRecoverChainIdCmd.Flags().AddFlagSet(recoverChainIdFlagSet)
AdminClientRecoverChainIdCmd.Flags().AddFlagSet(moduleFlagSet)
TemplateCmd.AddCommand(AdminClientRecoverChainIdCmd)

// flags for general-purpose governance call command
generalPurposeGovernanceFlagSet := pflag.NewFlagSet("general-purpose-governance", pflag.ExitOnError)
governanceContractAddress = generalPurposeGovernanceFlagSet.String("governance-contract", "", "Governance contract address")
governanceTargetAddress = generalPurposeGovernanceFlagSet.String("target-address", "", "Address of the governed contract")
governanceCallData = generalPurposeGovernanceFlagSet.String("call-data", "", "calldata")
governanceTargetChain = generalPurposeGovernanceFlagSet.String("chain-id", "", "Chain ID")
// evm call command
AdminClientGeneralPurposeGovernanceEvmCallCmd.Flags().AddFlagSet(generalPurposeGovernanceFlagSet)
TemplateCmd.AddCommand(AdminClientGeneralPurposeGovernanceEvmCallCmd)
// solana call command
AdminClientGeneralPurposeGovernanceSolanaCallCmd.Flags().AddFlagSet(generalPurposeGovernanceFlagSet)
TemplateCmd.AddCommand(AdminClientGeneralPurposeGovernanceSolanaCallCmd)
}

var TemplateCmd = &cobra.Command{
Expand Down Expand Up @@ -292,6 +310,18 @@ var AdminClientWormholeRelayerSetDefaultDeliveryProviderCmd = &cobra.Command{
Run: runWormholeRelayerSetDefaultDeliveryProviderTemplate,
}

var AdminClientGeneralPurposeGovernanceEvmCallCmd = &cobra.Command{
Use: "governance-evm-call",
Short: "Generate a 'general purpose evm governance call' template for specified chain and address",
Run: runGeneralPurposeGovernanceEvmCallTemplate,
}

var AdminClientGeneralPurposeGovernanceSolanaCallCmd = &cobra.Command{
Use: "governance-solana-call",
Short: "Generate a 'general purpose solana governance call' template for specified chain and address",
Run: runGeneralPurposeGovernanceSolanaCallTemplate,
}

func runGuardianSetTemplate(cmd *cobra.Command, args []string) {
// Use deterministic devnet addresses as examples in the template, such that this doubles as a test fixture.
guardians := make([]*nodev1.GuardianSetUpdate_Guardian, *setUpdateNumGuardians)
Expand Down Expand Up @@ -932,6 +962,100 @@ func runWormholeRelayerSetDefaultDeliveryProviderTemplate(cmd *cobra.Command, ar
fmt.Print(string(b))
}

func runGeneralPurposeGovernanceEvmCallTemplate(cmd *cobra.Command, args []string) {
if *governanceTargetAddress == "" {
log.Fatal("--target-address must be specified")
}
if !common.IsHexAddress(*governanceTargetAddress) {
log.Fatal("invalid target address")
}
governanceTargetAddress := common.HexToAddress(*governanceTargetAddress).Hex()
Comment on lines +966 to +972
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we not using parseAddress in these templates?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because parseAddress attempts to parse any 32 byte address-like thing into a (left-padded) 32 byte address

func parseAddress(s string) (string, error) {
// try base58
b, err := base58.Decode(s)
if err == nil {
return leftPadAddress(b)
}
// try bech32
_, b, err = bech32.Decode(s)
if err == nil {
return leftPadAddress(b)
}
// try hex
if len(s) > 2 && strings.ToLower(s[:2]) == "0x" {
s = s[2:]
}
a, err := hex.DecodeString(s)
if err != nil {
return "", fmt.Errorf("invalid hex address: %v", err)
}
return leftPadAddress(a)
}

in this case, we only care about EVM addresses, so we can be a bit more specific

if *governanceCallData == "" {
log.Fatal("--call-data must be specified")
}
if *governanceContractAddress == "" {
log.Fatal("--governance-contract must be specified")
}
if !common.IsHexAddress(*governanceContractAddress) {
log.Fatal("invalid governance contract address")
}
governanceContractAddress := common.HexToAddress(*governanceContractAddress).Hex()
if *governanceTargetChain == "" {
log.Fatal("--chain-id must be specified")
}
chainID, err := parseChainID(*governanceTargetChain)
if err != nil {
log.Fatal("failed to parse chain id: ", err)
}

m := &nodev1.InjectGovernanceVAARequest{
CurrentSetIndex: uint32(*templateGuardianIndex),
Messages: []*nodev1.GovernanceMessage{
{
Sequence: rand.Uint64(),
Nonce: rand.Uint32(),
Payload: &nodev1.GovernanceMessage_EvmCall{
EvmCall: &nodev1.EvmCall{
ChainId: uint32(chainID),
GovernanceContract: governanceContractAddress,
TargetContract: governanceTargetAddress,
AbiEncodedCall: *governanceCallData,
},
},
},
},
}

b, err := prototext.MarshalOptions{Multiline: true}.Marshal(m)
if err != nil {
panic(err)
}
fmt.Print(string(b))
}

func runGeneralPurposeGovernanceSolanaCallTemplate(cmd *cobra.Command, args []string) {
if *governanceCallData == "" {
log.Fatal("--call-data must be specified")
}
if *governanceContractAddress == "" {
log.Fatal("--governance-contract must be specified")
}
_, err := base58.Decode(*governanceContractAddress)
if err != nil {
log.Fatal("invalid base58 governance contract address")
}
if *governanceTargetChain == "" {
log.Fatal("--chain-id must be specified")
}
chainID, err := parseChainID(*governanceTargetChain)
if err != nil {
log.Fatal("failed to parse chain id: ", err)
}

m := &nodev1.InjectGovernanceVAARequest{
CurrentSetIndex: uint32(*templateGuardianIndex),
Messages: []*nodev1.GovernanceMessage{
{
Sequence: rand.Uint64(),
Nonce: rand.Uint32(),
Payload: &nodev1.GovernanceMessage_SolanaCall{
SolanaCall: &nodev1.SolanaCall{
ChainId: uint32(chainID),
GovernanceContract: *governanceContractAddress,
EncodedInstruction: *governanceCallData,
},
},
},
},
}

b, err := prototext.MarshalOptions{Multiline: true}.Marshal(m)
if err != nil {
panic(err)
}
fmt.Print(string(b))
}

// parseAddress parses either a hex-encoded address and returns
// a left-padded 32 byte hex string.
func parseAddress(s string) (string, error) {
Expand Down
56 changes: 56 additions & 0 deletions node/pkg/adminrpc/adminserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,58 @@ func wormholeRelayerSetDefaultDeliveryProvider(req *nodev1.WormholeRelayerSetDef
return v, nil
}

func evmCallToVaa(evmCall *nodev1.EvmCall, timestamp time.Time, guardianSetIndex, nonce uint32, sequence uint64) (*vaa.VAA, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a check in here to enforce that chain id doesn't exceed max uint16?

governanceContract := ethcommon.HexToAddress(evmCall.GovernanceContract)
targetContract := ethcommon.HexToAddress(evmCall.TargetContract)

payload, err := hex.DecodeString(evmCall.AbiEncodedCall)
if err != nil {
return nil, fmt.Errorf("failed to decode ABI encoded call: %w", err)
}

body, err := vaa.BodyGeneralPurposeGovernanceEvm{
ChainID: vaa.ChainID(evmCall.ChainId),
GovernanceContract: governanceContract,
TargetContract: targetContract,
Payload: payload,
}.Serialize()

if err != nil {
return nil, fmt.Errorf("failed to serialize governance body: %w", err)
}

v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex, body)

return v, nil
}

func solanaCallToVaa(solanaCall *nodev1.SolanaCall, timestamp time.Time, guardianSetIndex, nonce uint32, sequence uint64) (*vaa.VAA, error) {
address, err := base58.Decode(solanaCall.GovernanceContract)
if err != nil {
return nil, fmt.Errorf("failed to decode base58 governance contract address: %w", err)
}
if len(address) != 32 {
return nil, errors.New("invalid governance contract address length (expected 32 bytes)")
}

var governanceContract [32]byte
copy(governanceContract[:], address)

instruction, err := hex.DecodeString(solanaCall.EncodedInstruction)
if err != nil {
return nil, fmt.Errorf("failed to decode instruction: %w", err)
}

v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex,
vaa.BodyGeneralPurposeGovernanceSolana{
ChainID: vaa.ChainID(solanaCall.ChainId),
GovernanceContract: governanceContract,
Instruction: instruction,
}.Serialize())

return v, nil
}

func GovMsgToVaa(message *nodev1.GovernanceMessage, currentSetIndex uint32, timestamp time.Time) (*vaa.VAA, error) {
var (
v *vaa.VAA
Expand Down Expand Up @@ -620,6 +672,10 @@ func GovMsgToVaa(message *nodev1.GovernanceMessage, currentSetIndex uint32, time
v, err = ibcUpdateChannelChain(payload.IbcUpdateChannelChain, timestamp, currentSetIndex, message.Nonce, message.Sequence)
case *nodev1.GovernanceMessage_WormholeRelayerSetDefaultDeliveryProvider:
v, err = wormholeRelayerSetDefaultDeliveryProvider(payload.WormholeRelayerSetDefaultDeliveryProvider, timestamp, currentSetIndex, message.Nonce, message.Sequence)
case *nodev1.GovernanceMessage_EvmCall:
v, err = evmCallToVaa(payload.EvmCall, timestamp, currentSetIndex, message.Nonce, message.Sequence)
case *nodev1.GovernanceMessage_SolanaCall:
v, err = solanaCallToVaa(payload.SolanaCall, timestamp, currentSetIndex, message.Nonce, message.Sequence)
default:
panic(fmt.Sprintf("unsupported VAA type: %T", payload))
}
Expand Down
Loading
Loading