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" } } ```
This commit is contained in:
parent
62fd2b914a
commit
3c3de23501
|
@ -186,6 +186,9 @@ func init() {
|
||||||
// evm call command
|
// evm call command
|
||||||
AdminClientGeneralPurposeGovernanceEvmCallCmd.Flags().AddFlagSet(generalPurposeGovernanceFlagSet)
|
AdminClientGeneralPurposeGovernanceEvmCallCmd.Flags().AddFlagSet(generalPurposeGovernanceFlagSet)
|
||||||
TemplateCmd.AddCommand(AdminClientGeneralPurposeGovernanceEvmCallCmd)
|
TemplateCmd.AddCommand(AdminClientGeneralPurposeGovernanceEvmCallCmd)
|
||||||
|
// solana call command
|
||||||
|
AdminClientGeneralPurposeGovernanceSolanaCallCmd.Flags().AddFlagSet(generalPurposeGovernanceFlagSet)
|
||||||
|
TemplateCmd.AddCommand(AdminClientGeneralPurposeGovernanceSolanaCallCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
var TemplateCmd = &cobra.Command{
|
var TemplateCmd = &cobra.Command{
|
||||||
|
@ -313,6 +316,12 @@ var AdminClientGeneralPurposeGovernanceEvmCallCmd = &cobra.Command{
|
||||||
Run: runGeneralPurposeGovernanceEvmCallTemplate,
|
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) {
|
func runGuardianSetTemplate(cmd *cobra.Command, args []string) {
|
||||||
// Use deterministic devnet addresses as examples in the template, such that this doubles as a test fixture.
|
// Use deterministic devnet addresses as examples in the template, such that this doubles as a test fixture.
|
||||||
guardians := make([]*nodev1.GuardianSetUpdate_Guardian, *setUpdateNumGuardians)
|
guardians := make([]*nodev1.GuardianSetUpdate_Guardian, *setUpdateNumGuardians)
|
||||||
|
@ -1004,6 +1013,49 @@ func runGeneralPurposeGovernanceEvmCallTemplate(cmd *cobra.Command, args []strin
|
||||||
fmt.Print(string(b))
|
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
|
// parseAddress parses either a hex-encoded address and returns
|
||||||
// a left-padded 32 byte hex string.
|
// a left-padded 32 byte hex string.
|
||||||
func parseAddress(s string) (string, error) {
|
func parseAddress(s string) (string, error) {
|
||||||
|
|
|
@ -599,6 +599,30 @@ func evmCallToVaa(evmCall *nodev1.EvmCall, timestamp time.Time, guardianSetIndex
|
||||||
return v, nil
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
func GovMsgToVaa(message *nodev1.GovernanceMessage, currentSetIndex uint32, timestamp time.Time) (*vaa.VAA, error) {
|
||||||
var (
|
var (
|
||||||
v *vaa.VAA
|
v *vaa.VAA
|
||||||
|
@ -644,6 +668,8 @@ func GovMsgToVaa(message *nodev1.GovernanceMessage, currentSetIndex uint32, time
|
||||||
v, err = wormholeRelayerSetDefaultDeliveryProvider(payload.WormholeRelayerSetDefaultDeliveryProvider, timestamp, currentSetIndex, message.Nonce, message.Sequence)
|
v, err = wormholeRelayerSetDefaultDeliveryProvider(payload.WormholeRelayerSetDefaultDeliveryProvider, timestamp, currentSetIndex, message.Nonce, message.Sequence)
|
||||||
case *nodev1.GovernanceMessage_EvmCall:
|
case *nodev1.GovernanceMessage_EvmCall:
|
||||||
v, err = evmCallToVaa(payload.EvmCall, timestamp, currentSetIndex, message.Nonce, message.Sequence)
|
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:
|
default:
|
||||||
panic(fmt.Sprintf("unsupported VAA type: %T", payload))
|
panic(fmt.Sprintf("unsupported VAA type: %T", payload))
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -120,6 +120,7 @@ message GovernanceMessage {
|
||||||
|
|
||||||
// Generic governance
|
// Generic governance
|
||||||
EvmCall evm_call = 28;
|
EvmCall evm_call = 28;
|
||||||
|
SolanaCall solana_call = 29;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -434,3 +435,15 @@ message EvmCall {
|
||||||
|
|
||||||
string abi_encoded_call = 4;
|
string abi_encoded_call = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SolanaCall represents a generic Solana call that can be executed by the generalized governance contract.
|
||||||
|
message SolanaCall {
|
||||||
|
// ID of the chain where the action should be executed (uint16).
|
||||||
|
uint32 chain_id = 1;
|
||||||
|
|
||||||
|
// Address of the governance contract (solana address)
|
||||||
|
string governance_contract = 2;
|
||||||
|
|
||||||
|
// Address of the governed contract (eth address starting with 0x)
|
||||||
|
string encoded_instruction = 3;
|
||||||
|
}
|
||||||
|
|
|
@ -231,6 +231,16 @@ type (
|
||||||
TargetContract Address
|
TargetContract Address
|
||||||
Payload []byte
|
Payload []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BodyGeneralPurposeGovernanceSolana is a general purpose governance message for Solana chains
|
||||||
|
BodyGeneralPurposeGovernanceSolana struct {
|
||||||
|
ChainID ChainID
|
||||||
|
GovernanceContract Address
|
||||||
|
// NOTE: unlike in EVM, no target contract in the schema here, the
|
||||||
|
// instruction encodes the target contract address (unlike in EVM, where
|
||||||
|
// an abi encoded calldata doesn't include the target contract address)
|
||||||
|
Instruction []byte
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b BodyContractUpgrade) Serialize() []byte {
|
func (b BodyContractUpgrade) Serialize() []byte {
|
||||||
|
@ -427,6 +437,17 @@ func (r BodyGeneralPurposeGovernanceEvm) Serialize() []byte {
|
||||||
return serializeBridgeGovernanceVaa(GeneralPurposeGovernanceModuleStr, GovernanceAction(1), r.ChainID, payload.Bytes())
|
return serializeBridgeGovernanceVaa(GeneralPurposeGovernanceModuleStr, GovernanceAction(1), r.ChainID, payload.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r BodyGeneralPurposeGovernanceSolana) Serialize() []byte {
|
||||||
|
payload := &bytes.Buffer{}
|
||||||
|
payload.Write(r.GovernanceContract[:])
|
||||||
|
// NOTE: unlike in EVM, we don't write the payload here, because we're using
|
||||||
|
// a custom instruction encoding (there is no standard encoding like evm ABI
|
||||||
|
// encoding), generated by an external tool. That tool length-prefixes all
|
||||||
|
// the relevant dynamic fields.
|
||||||
|
payload.Write(r.Instruction)
|
||||||
|
return serializeBridgeGovernanceVaa(GeneralPurposeGovernanceModuleStr, GovernanceAction(2), r.ChainID, payload.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
func EmptyPayloadVaa(module string, actionId GovernanceAction, chainId ChainID) []byte {
|
func EmptyPayloadVaa(module string, actionId GovernanceAction, chainId ChainID) []byte {
|
||||||
return serializeBridgeGovernanceVaa(module, actionId, chainId, []byte{})
|
return serializeBridgeGovernanceVaa(module, actionId, chainId, []byte{})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue