diff --git a/node/cmd/guardiand/adminserver.go b/node/cmd/guardiand/adminserver.go index 80b26e673..f40426d38 100644 --- a/node/cmd/guardiand/adminserver.go +++ b/node/cmd/guardiand/adminserver.go @@ -122,6 +122,35 @@ func tokenBridgeRegisterChain(req *nodev1.BridgeRegisterChain, guardianSetIndex return v, nil } +// tokenBridgeUpgradeContract converts a nodev1.TokenBridgeRegisterChain message to its canonical VAA representation. +// Returns an error if the data is invalid. +func tokenBridgeUpgradeContract(req *nodev1.BridgeUpgradeContract, guardianSetIndex uint32, nonce uint32, sequence uint64) (*vaa.VAA, error) { + if req.TargetChainId > math.MaxUint16 { + return nil, errors.New("invalid target_chain_id") + } + + b, err := hex.DecodeString(req.NewContract) + if err != nil { + return nil, errors.New("invalid new contract address (expected hex)") + } + + if len(b) != 32 { + return nil, errors.New("invalid new contract address (expected 32 bytes)") + } + + newContract := vaa.Address{} + copy(newContract[:], b) + + v := vaa.CreateGovernanceVAA(nonce, sequence, guardianSetIndex, + vaa.BodyTokenBridgeUpgradeContract{ + Module: req.Module, + TargetChainID: vaa.ChainID(req.TargetChainId), + NewContract: newContract, + }.Serialize()) + + return v, nil +} + func (s *nodePrivilegedService) InjectGovernanceVAA(ctx context.Context, req *nodev1.InjectGovernanceVAARequest) (*nodev1.InjectGovernanceVAAResponse, error) { s.logger.Info("governance VAA injected via admin socket", zap.String("request", req.String())) @@ -136,6 +165,8 @@ func (s *nodePrivilegedService) InjectGovernanceVAA(ctx context.Context, req *no v, err = adminContractUpgradeToVAA(payload.ContractUpgrade, req.CurrentSetIndex, req.Nonce, req.Sequence) case *nodev1.InjectGovernanceVAARequest_BridgeRegisterChain: v, err = tokenBridgeRegisterChain(payload.BridgeRegisterChain, req.CurrentSetIndex, req.Nonce, req.Sequence) + case *nodev1.InjectGovernanceVAARequest_BridgeContractUpgrade: + v, err = tokenBridgeUpgradeContract(payload.BridgeContractUpgrade, req.CurrentSetIndex, req.Nonce, req.Sequence) default: panic(fmt.Sprintf("unsupported VAA type: %T", payload)) } diff --git a/node/cmd/guardiand/admintemplate.go b/node/cmd/guardiand/admintemplate.go index 73135b2b9..dc72cd900 100644 --- a/node/cmd/guardiand/admintemplate.go +++ b/node/cmd/guardiand/admintemplate.go @@ -24,6 +24,7 @@ func init() { TemplateCmd.AddCommand(AdminClientGuardianSetTemplateCmd) TemplateCmd.AddCommand(AdminClientContractUpgradeTemplateCmd) TemplateCmd.AddCommand(AdminClientTokenBridgeRegisterChainCmd) + TemplateCmd.AddCommand(AdminClientTokenBridgeUpgradeContractCmd) } var TemplateCmd = &cobra.Command{ @@ -52,6 +53,13 @@ var AdminClientTokenBridgeRegisterChainCmd = &cobra.Command{ Args: cobra.ExactArgs(1), } +var AdminClientTokenBridgeUpgradeContractCmd = &cobra.Command{ + Use: "token-bridge-upgrade-contract [FILENAME]", + Short: "Generate an empty token bridge contract upgrade template at specified path (offline)", + Run: runTokenBridgeUpgradeContractTemplate, + Args: cobra.ExactArgs(1), +} + func runGuardianSetTemplate(cmd *cobra.Command, args []string) { path := args[0] @@ -136,3 +144,30 @@ func runTokenBridgeRegisterChainTemplate(cmd *cobra.Command, args []string) { log.Fatal(err) } } + +func runTokenBridgeUpgradeContractTemplate(cmd *cobra.Command, args []string) { + path := args[0] + + m := &nodev1.InjectGovernanceVAARequest{ + CurrentSetIndex: uint32(*templateGuardianIndex), + Sequence: rand.Uint64(), + Nonce: rand.Uint32(), + Payload: &nodev1.InjectGovernanceVAARequest_BridgeContractUpgrade{ + BridgeContractUpgrade: &nodev1.BridgeUpgradeContract{ + Module: "TokenBridge", + TargetChainId: 5, + NewContract: "0000000000000000000000000290FB167208Af455bB137780163b7B7a9a10C16", + }, + }, + } + + b, err := prototext.MarshalOptions{Multiline: true}.Marshal(m) + if err != nil { + panic(err) + } + + err = ioutil.WriteFile(path, b, 0640) + if err != nil { + log.Fatal(err) + } +} diff --git a/node/pkg/vaa/payloads.go b/node/pkg/vaa/payloads.go index acec62ea5..fcd56394b 100644 --- a/node/pkg/vaa/payloads.go +++ b/node/pkg/vaa/payloads.go @@ -28,6 +28,13 @@ type ( ChainID ChainID EmitterAddress Address } + + // BodyTokenBridgeUpgradeContract is a governance message to upgrade the token bridge. + BodyTokenBridgeUpgradeContract struct { + Module string + TargetChainID ChainID + NewContract Address + } ) func (b BodyContractUpgrade) Serialize() []byte { @@ -87,3 +94,25 @@ func (r BodyTokenBridgeRegisterChain) Serialize() []byte { return buf.Bytes() } + +func (r BodyTokenBridgeUpgradeContract) Serialize() []byte { + if len(r.Module) > 32 { + panic("module longer than 32 byte") + } + + buf := &bytes.Buffer{} + + // Write token bridge header + for i := 0; i < (32 - len(r.Module)); i++ { + buf.WriteByte(0x00) + } + buf.Write([]byte(r.Module)) + // Write action ID + MustWrite(buf, binary.BigEndian, uint8(2)) + // Write target chain + MustWrite(buf, binary.BigEndian, r.TargetChainID) + // Write emitter address of chain to be registered + buf.Write(r.NewContract[:]) + + return buf.Bytes() +} diff --git a/proto/node/v1/node.proto b/proto/node/v1/node.proto index 0bf05ce78..5bd17f57e 100644 --- a/proto/node/v1/node.proto +++ b/proto/node/v1/node.proto @@ -46,6 +46,7 @@ message InjectGovernanceVAARequest { // Token bridge and NFT module BridgeRegisterChain bridge_register_chain = 12; + BridgeUpgradeContract bridge_contract_upgrade = 13; } } @@ -97,6 +98,17 @@ message ContractUpgrade { bytes new_contract = 2; } +message BridgeUpgradeContract { + // Module identifier of the token or NFT bridge (typically "TokenBridge" or "NFTBridge"). + string module = 1; + + // ID of the chain where the bridge contract should be updated (uint16). + uint32 target_chain_id = 2; + + // Address of the new program/contract. + string new_contract = 3; +} + message FindMissingMessagesRequest { // Emitter chain ID to iterate. uint32 emitter_chain = 1;