Skip to content

Commit 9620fca

Browse files
authored
node: generalised governance (wormhole-foundation#3895)
* node/admin: add generalised EVM call governance handler Handles governance requests of the form: ``` current_set_index: 4 messages: { sequence: 4513077582118919631 nonce: 2809988562 evm_call: { chain_id: 3 governance_contract: "0xD8E4C2DbDd2e2bd8F1336EA691dBFF6952B1a6eB" target_contract: "0xF890982f9310df57d00f659cf4fd87e65adEd8d7" abi_encoded_call: "6497f75a000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000f890982f9310df57d00f659cf4fd87e65aded8d70000000000000000000000000000000000000000000000000000000000000140bebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebe000000000000000000000000000000000000000000000000000000000000000268690000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004beefface00000000000000000000000000000000000000000000000000000000" } } ``` * node/admin: add admin template for evm governance call * node/admin: add generalised Solana call governance handler handles governance requests of the form ``` current_set_index: 4 messages: { sequence: 4513077582118919631 nonce: 2809988562 solana_call: { chain_id: 3 governance_contract: "3u8hJUVTA4jH1wYAyUur7FFZVQ8H635K3tSHHF4ssjQ5" encoded_instruction: "BEEFFACE" } } ``` * node/admin: check address lengths and fix typo in governance handler * node/admin: better error handling and fix comments * sdk/vaa: add constants for general purpose governance actions
1 parent 35f0b34 commit 9620fca

File tree

5 files changed

+912
-418
lines changed

5 files changed

+912
-418
lines changed

node/cmd/guardiand/admintemplate.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ var ibcUpdateChannelChainChainId *string
6060
var recoverChainIdEvmChainId *string
6161
var recoverChainIdNewChainId *string
6262

63+
var governanceContractAddress *string
64+
var governanceTargetAddress *string
65+
var governanceTargetChain *string
66+
var governanceCallData *string
67+
6368
func init() {
6469
governanceFlagSet := pflag.NewFlagSet("governance", pflag.ExitOnError)
6570
chainID = governanceFlagSet.String("chain-id", "", "Chain ID")
@@ -171,6 +176,19 @@ func init() {
171176
AdminClientRecoverChainIdCmd.Flags().AddFlagSet(recoverChainIdFlagSet)
172177
AdminClientRecoverChainIdCmd.Flags().AddFlagSet(moduleFlagSet)
173178
TemplateCmd.AddCommand(AdminClientRecoverChainIdCmd)
179+
180+
// flags for general-purpose governance call command
181+
generalPurposeGovernanceFlagSet := pflag.NewFlagSet("general-purpose-governance", pflag.ExitOnError)
182+
governanceContractAddress = generalPurposeGovernanceFlagSet.String("governance-contract", "", "Governance contract address")
183+
governanceTargetAddress = generalPurposeGovernanceFlagSet.String("target-address", "", "Address of the governed contract")
184+
governanceCallData = generalPurposeGovernanceFlagSet.String("call-data", "", "calldata")
185+
governanceTargetChain = generalPurposeGovernanceFlagSet.String("chain-id", "", "Chain ID")
186+
// evm call command
187+
AdminClientGeneralPurposeGovernanceEvmCallCmd.Flags().AddFlagSet(generalPurposeGovernanceFlagSet)
188+
TemplateCmd.AddCommand(AdminClientGeneralPurposeGovernanceEvmCallCmd)
189+
// solana call command
190+
AdminClientGeneralPurposeGovernanceSolanaCallCmd.Flags().AddFlagSet(generalPurposeGovernanceFlagSet)
191+
TemplateCmd.AddCommand(AdminClientGeneralPurposeGovernanceSolanaCallCmd)
174192
}
175193

176194
var TemplateCmd = &cobra.Command{
@@ -292,6 +310,18 @@ var AdminClientWormholeRelayerSetDefaultDeliveryProviderCmd = &cobra.Command{
292310
Run: runWormholeRelayerSetDefaultDeliveryProviderTemplate,
293311
}
294312

313+
var AdminClientGeneralPurposeGovernanceEvmCallCmd = &cobra.Command{
314+
Use: "governance-evm-call",
315+
Short: "Generate a 'general purpose evm governance call' template for specified chain and address",
316+
Run: runGeneralPurposeGovernanceEvmCallTemplate,
317+
}
318+
319+
var AdminClientGeneralPurposeGovernanceSolanaCallCmd = &cobra.Command{
320+
Use: "governance-solana-call",
321+
Short: "Generate a 'general purpose solana governance call' template for specified chain and address",
322+
Run: runGeneralPurposeGovernanceSolanaCallTemplate,
323+
}
324+
295325
func runGuardianSetTemplate(cmd *cobra.Command, args []string) {
296326
// Use deterministic devnet addresses as examples in the template, such that this doubles as a test fixture.
297327
guardians := make([]*nodev1.GuardianSetUpdate_Guardian, *setUpdateNumGuardians)
@@ -932,6 +962,100 @@ func runWormholeRelayerSetDefaultDeliveryProviderTemplate(cmd *cobra.Command, ar
932962
fmt.Print(string(b))
933963
}
934964

965+
func runGeneralPurposeGovernanceEvmCallTemplate(cmd *cobra.Command, args []string) {
966+
if *governanceTargetAddress == "" {
967+
log.Fatal("--target-address must be specified")
968+
}
969+
if !common.IsHexAddress(*governanceTargetAddress) {
970+
log.Fatal("invalid target address")
971+
}
972+
governanceTargetAddress := common.HexToAddress(*governanceTargetAddress).Hex()
973+
if *governanceCallData == "" {
974+
log.Fatal("--call-data must be specified")
975+
}
976+
if *governanceContractAddress == "" {
977+
log.Fatal("--governance-contract must be specified")
978+
}
979+
if !common.IsHexAddress(*governanceContractAddress) {
980+
log.Fatal("invalid governance contract address")
981+
}
982+
governanceContractAddress := common.HexToAddress(*governanceContractAddress).Hex()
983+
if *governanceTargetChain == "" {
984+
log.Fatal("--chain-id must be specified")
985+
}
986+
chainID, err := parseChainID(*governanceTargetChain)
987+
if err != nil {
988+
log.Fatal("failed to parse chain id: ", err)
989+
}
990+
991+
m := &nodev1.InjectGovernanceVAARequest{
992+
CurrentSetIndex: uint32(*templateGuardianIndex),
993+
Messages: []*nodev1.GovernanceMessage{
994+
{
995+
Sequence: rand.Uint64(),
996+
Nonce: rand.Uint32(),
997+
Payload: &nodev1.GovernanceMessage_EvmCall{
998+
EvmCall: &nodev1.EvmCall{
999+
ChainId: uint32(chainID),
1000+
GovernanceContract: governanceContractAddress,
1001+
TargetContract: governanceTargetAddress,
1002+
AbiEncodedCall: *governanceCallData,
1003+
},
1004+
},
1005+
},
1006+
},
1007+
}
1008+
1009+
b, err := prototext.MarshalOptions{Multiline: true}.Marshal(m)
1010+
if err != nil {
1011+
panic(err)
1012+
}
1013+
fmt.Print(string(b))
1014+
}
1015+
1016+
func runGeneralPurposeGovernanceSolanaCallTemplate(cmd *cobra.Command, args []string) {
1017+
if *governanceCallData == "" {
1018+
log.Fatal("--call-data must be specified")
1019+
}
1020+
if *governanceContractAddress == "" {
1021+
log.Fatal("--governance-contract must be specified")
1022+
}
1023+
_, err := base58.Decode(*governanceContractAddress)
1024+
if err != nil {
1025+
log.Fatal("invalid base58 governance contract address")
1026+
}
1027+
if *governanceTargetChain == "" {
1028+
log.Fatal("--chain-id must be specified")
1029+
}
1030+
chainID, err := parseChainID(*governanceTargetChain)
1031+
if err != nil {
1032+
log.Fatal("failed to parse chain id: ", err)
1033+
}
1034+
1035+
m := &nodev1.InjectGovernanceVAARequest{
1036+
CurrentSetIndex: uint32(*templateGuardianIndex),
1037+
Messages: []*nodev1.GovernanceMessage{
1038+
{
1039+
Sequence: rand.Uint64(),
1040+
Nonce: rand.Uint32(),
1041+
Payload: &nodev1.GovernanceMessage_SolanaCall{
1042+
SolanaCall: &nodev1.SolanaCall{
1043+
ChainId: uint32(chainID),
1044+
GovernanceContract: *governanceContractAddress,
1045+
EncodedInstruction: *governanceCallData,
1046+
},
1047+
},
1048+
},
1049+
},
1050+
}
1051+
1052+
b, err := prototext.MarshalOptions{Multiline: true}.Marshal(m)
1053+
if err != nil {
1054+
panic(err)
1055+
}
1056+
fmt.Print(string(b))
1057+
}
1058+
9351059
// parseAddress parses either a hex-encoded address and returns
9361060
// a left-padded 32 byte hex string.
9371061
func parseAddress(s string) (string, error) {

node/pkg/adminrpc/adminserver.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,58 @@ func wormholeRelayerSetDefaultDeliveryProvider(req *nodev1.WormholeRelayerSetDef
577577
return v, nil
578578
}
579579

580+
func evmCallToVaa(evmCall *nodev1.EvmCall, timestamp time.Time, guardianSetIndex, nonce uint32, sequence uint64) (*vaa.VAA, error) {
581+
governanceContract := ethcommon.HexToAddress(evmCall.GovernanceContract)
582+
targetContract := ethcommon.HexToAddress(evmCall.TargetContract)
583+
584+
payload, err := hex.DecodeString(evmCall.AbiEncodedCall)
585+
if err != nil {
586+
return nil, fmt.Errorf("failed to decode ABI encoded call: %w", err)
587+
}
588+
589+
body, err := vaa.BodyGeneralPurposeGovernanceEvm{
590+
ChainID: vaa.ChainID(evmCall.ChainId),
591+
GovernanceContract: governanceContract,
592+
TargetContract: targetContract,
593+
Payload: payload,
594+
}.Serialize()
595+
596+
if err != nil {
597+
return nil, fmt.Errorf("failed to serialize governance body: %w", err)
598+
}
599+
600+
v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex, body)
601+
602+
return v, nil
603+
}
604+
605+
func solanaCallToVaa(solanaCall *nodev1.SolanaCall, timestamp time.Time, guardianSetIndex, nonce uint32, sequence uint64) (*vaa.VAA, error) {
606+
address, err := base58.Decode(solanaCall.GovernanceContract)
607+
if err != nil {
608+
return nil, fmt.Errorf("failed to decode base58 governance contract address: %w", err)
609+
}
610+
if len(address) != 32 {
611+
return nil, errors.New("invalid governance contract address length (expected 32 bytes)")
612+
}
613+
614+
var governanceContract [32]byte
615+
copy(governanceContract[:], address)
616+
617+
instruction, err := hex.DecodeString(solanaCall.EncodedInstruction)
618+
if err != nil {
619+
return nil, fmt.Errorf("failed to decode instruction: %w", err)
620+
}
621+
622+
v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex,
623+
vaa.BodyGeneralPurposeGovernanceSolana{
624+
ChainID: vaa.ChainID(solanaCall.ChainId),
625+
GovernanceContract: governanceContract,
626+
Instruction: instruction,
627+
}.Serialize())
628+
629+
return v, nil
630+
}
631+
580632
func GovMsgToVaa(message *nodev1.GovernanceMessage, currentSetIndex uint32, timestamp time.Time) (*vaa.VAA, error) {
581633
var (
582634
v *vaa.VAA
@@ -620,6 +672,10 @@ func GovMsgToVaa(message *nodev1.GovernanceMessage, currentSetIndex uint32, time
620672
v, err = ibcUpdateChannelChain(payload.IbcUpdateChannelChain, timestamp, currentSetIndex, message.Nonce, message.Sequence)
621673
case *nodev1.GovernanceMessage_WormholeRelayerSetDefaultDeliveryProvider:
622674
v, err = wormholeRelayerSetDefaultDeliveryProvider(payload.WormholeRelayerSetDefaultDeliveryProvider, timestamp, currentSetIndex, message.Nonce, message.Sequence)
675+
case *nodev1.GovernanceMessage_EvmCall:
676+
v, err = evmCallToVaa(payload.EvmCall, timestamp, currentSetIndex, message.Nonce, message.Sequence)
677+
case *nodev1.GovernanceMessage_SolanaCall:
678+
v, err = solanaCallToVaa(payload.SolanaCall, timestamp, currentSetIndex, message.Nonce, message.Sequence)
623679
default:
624680
panic(fmt.Sprintf("unsupported VAA type: %T", payload))
625681
}

0 commit comments

Comments
 (0)