فهرست منبع

sdk: add new governance VAA for IbcReceiverUpdateChainConnection (#2590)

* sdk: add new governance VAA for IbcReceiverUpdateChainConnection

* Enforce connectionId to be 64 bytes, add tests in rust sdk

* Update PrependBufferBytesFixed to LeftPadBytes, add template function + command for IBC governance VAA

* Add >64 length check for ibcReceiverUpdateChainConnectionConnectionId in runIbcReceiverUpdateChainConnectionTemplate command

* Update naming of governance VAA to reflect new mapping of channelId -> chainId

* Add TargetChainID to admin commands

* Node: Add IBC update channel to admin verify cmd

---------

Co-authored-by: Bruce Riley <briley@jumptrading.com>
Nikhil Suri 2 سال پیش
والد
کامیت
ee7d0765f9

+ 34 - 0
node/cmd/guardiand/adminserver.go

@@ -360,6 +360,38 @@ func circleIntegrationUpgradeContractImplementation(req *nodev1.CircleIntegratio
 	return v, nil
 }
 
+func ibcReceiverUpdateChannelChain(
+	req *nodev1.IbcReceiverUpdateChannelChain,
+	timestamp time.Time,
+	guardianSetIndex uint32,
+	nonce uint32,
+	sequence uint64,
+) (*vaa.VAA, error) {
+	// validate parameters
+	if req.TargetChainId > math.MaxUint16 {
+		return nil, fmt.Errorf("invalid target chain id, must be <= %d", math.MaxUint16)
+	}
+
+	if req.ChainId > math.MaxUint16 {
+		return nil, fmt.Errorf("invalid chain id, must be <= %d", math.MaxUint16)
+	}
+
+	if len(req.ChannelId) > 64 {
+		return nil, fmt.Errorf("invalid channel ID length, must be <= 64")
+	}
+	channelId := vaa.LeftPadIbcChannelId(req.ChannelId)
+
+	// create governance VAA
+	v := vaa.CreateGovernanceVAA(timestamp, nonce, sequence, guardianSetIndex,
+		vaa.BodyIbcReceiverUpdateChannelChain{
+			TargetChainId: vaa.ChainID(req.TargetChainId),
+			ChannelId:     channelId,
+			ChainId:       vaa.ChainID(req.ChainId),
+		}.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()))
 
@@ -396,6 +428,8 @@ func (s *nodePrivilegedService) InjectGovernanceVAA(ctx context.Context, req *no
 			v, err = circleIntegrationRegisterEmitterAndDomain(payload.CircleIntegrationRegisterEmitterAndDomain, timestamp, req.CurrentSetIndex, message.Nonce, message.Sequence)
 		case *nodev1.GovernanceMessage_CircleIntegrationUpgradeContractImplementation:
 			v, err = circleIntegrationUpgradeContractImplementation(payload.CircleIntegrationUpgradeContractImplementation, timestamp, req.CurrentSetIndex, message.Nonce, message.Sequence)
+		case *nodev1.GovernanceMessage_IbcReceiverUpdateChannelChain:
+			v, err = ibcReceiverUpdateChannelChain(payload.IbcReceiverUpdateChannelChain, timestamp, req.CurrentSetIndex, message.Nonce, message.Sequence)
 		default:
 			panic(fmt.Sprintf("unsupported VAA type: %T", payload))
 		}

+ 66 - 0
node/cmd/guardiand/admintemplate.go

@@ -38,6 +38,10 @@ var circleIntegrationForeignEmitterAddress *string
 var circleIntegrationCircleDomain *string
 var circleIntegrationNewImplementationAddress *string
 
+var ibcReceiverUpdateChannelChainTargetChainId *string
+var ibcReceiverUpdateChannelChainChannelId *string
+var ibcReceiverUpdateChannelChainChainId *string
+
 func init() {
 	governanceFlagSet := pflag.NewFlagSet("governance", pflag.ExitOnError)
 	chainID = governanceFlagSet.String("chain-id", "", "Chain ID")
@@ -91,6 +95,14 @@ func init() {
 	AdminClientCircleIntegrationUpgradeContractImplementationCmd.Flags().AddFlagSet(circleIntegrationChainIDFlagSet)
 	AdminClientCircleIntegrationUpgradeContractImplementationCmd.Flags().AddFlagSet(circleIntegrationUpgradeContractImplementationFlagSet)
 	TemplateCmd.AddCommand(AdminClientCircleIntegrationUpgradeContractImplementationCmd)
+
+	// flags for the ibc-receiver-update-channel-chain command
+	ibcReceiverUpdateChannelChainFlagSet := pflag.NewFlagSet("ibc-mapping", pflag.ExitOnError)
+	ibcReceiverUpdateChannelChainTargetChainId = ibcReceiverUpdateChannelChainFlagSet.String("target-chain-id", "", "Target Chain ID for the governance VAA")
+	ibcReceiverUpdateChannelChainChannelId = ibcReceiverUpdateChannelChainFlagSet.String("channel-id", "", "IBC Channel ID on Wormchain")
+	ibcReceiverUpdateChannelChainChainId = ibcReceiverUpdateChannelChainFlagSet.String("chain-id", "", "IBC Chain ID that the channel ID corresponds to")
+	AdminClientIbcReceiverUpdateChannelChainCmd.Flags().AddFlagSet(ibcReceiverUpdateChannelChainFlagSet)
+	TemplateCmd.AddCommand(AdminClientIbcReceiverUpdateChannelChainCmd)
 }
 
 var TemplateCmd = &cobra.Command{
@@ -145,6 +157,12 @@ var AdminClientCircleIntegrationUpgradeContractImplementationCmd = &cobra.Comman
 	Run:   runCircleIntegrationUpgradeContractImplementationTemplate,
 }
 
+var AdminClientIbcReceiverUpdateChannelChainCmd = &cobra.Command{
+	Use:   "ibc-receiver-update-channel-chain",
+	Short: "Generate an empty ibc receiver channelId to chainId mapping update template at specified path",
+	Run:   runIbcReceiverUpdateChannelChainTemplate,
+}
+
 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)
@@ -454,6 +472,54 @@ func runCircleIntegrationUpgradeContractImplementationTemplate(cmd *cobra.Comman
 	fmt.Print(string(b))
 }
 
+func runIbcReceiverUpdateChannelChainTemplate(cmd *cobra.Command, args []string) {
+	if *ibcReceiverUpdateChannelChainTargetChainId == "" {
+		log.Fatal("--target-chain-id must be specified")
+	}
+	targetChainId, err := parseChainID(*ibcReceiverUpdateChannelChainTargetChainId)
+	if err != nil {
+		log.Fatal("failed to parse chain id: ", err)
+	}
+
+	if *ibcReceiverUpdateChannelChainChannelId == "" {
+		log.Fatal("--channel-id must be specified")
+	}
+	if len(*ibcReceiverUpdateChannelChainChannelId) > 64 {
+		log.Fatal("invalid channel id length, must be <= 64")
+	}
+
+	if *ibcReceiverUpdateChannelChainChainId == "" {
+		log.Fatal("--chain-id must be specified")
+	}
+	chainId, err := parseChainID(*ibcReceiverUpdateChannelChainChainId)
+	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_IbcReceiverUpdateChannelChain{
+					IbcReceiverUpdateChannelChain: &nodev1.IbcReceiverUpdateChannelChain{
+						TargetChainId: uint32(targetChainId),
+						ChannelId:     *ibcReceiverUpdateChannelChainChannelId,
+						ChainId:       uint32(chainId),
+					},
+				},
+			},
+		},
+	}
+
+	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) {

+ 2 - 0
node/cmd/guardiand/adminverify.go

@@ -67,6 +67,8 @@ func runGovernanceVAAVerify(cmd *cobra.Command, args []string) {
 			v, err = circleIntegrationRegisterEmitterAndDomain(payload.CircleIntegrationRegisterEmitterAndDomain, timestamp, req.CurrentSetIndex, message.Nonce, message.Sequence)
 		case *nodev1.GovernanceMessage_CircleIntegrationUpgradeContractImplementation:
 			v, err = circleIntegrationUpgradeContractImplementation(payload.CircleIntegrationUpgradeContractImplementation, timestamp, req.CurrentSetIndex, message.Nonce, message.Sequence)
+		case *nodev1.GovernanceMessage_IbcReceiverUpdateChannelChain:
+			v, err = ibcReceiverUpdateChannelChain(payload.IbcReceiverUpdateChannelChain, timestamp, req.CurrentSetIndex, message.Nonce, message.Sequence)
 		default:
 			panic(fmt.Sprintf("unsupported VAA type: %T", payload))
 		}

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 445 - 348
node/pkg/proto/node/v1/node.pb.go


+ 12 - 0
proto/node/v1/node.proto

@@ -100,6 +100,9 @@ message GovernanceMessage {
     CircleIntegrationUpdateWormholeFinality circle_integration_update_wormhole_finality = 18;
     CircleIntegrationRegisterEmitterAndDomain circle_integration_register_emitter_and_domain = 19;
     CircleIntegrationUpgradeContractImplementation circle_integration_upgrade_contract_implementation = 20;
+
+    // IBC Receiver Integration
+    IbcReceiverUpdateChannelChain ibc_receiver_update_channel_chain = 21;
   }
 }
 
@@ -236,6 +239,15 @@ message CircleIntegrationUpgradeContractImplementation {
   uint32 target_chain_id = 2;
 }
 
+message IbcReceiverUpdateChannelChain {
+  // Chain ID that this governance VAA should be redeemed on
+  uint32 target_chain_id = 1;
+  // IBC channel ID
+  string channel_id = 2;
+  // ChainID corresponding to the IBC channel
+  uint32 chain_id = 3;
+}
+
 message FindMissingMessagesRequest {
   // Emitter chain ID to iterate.
   uint32 emitter_chain = 1;

+ 331 - 0
sdk/rust/core/src/ibc_receiver.rs

@@ -0,0 +1,331 @@
+use serde::{Deserialize, Serialize};
+
+use crate::Chain;
+
+/// Represents a governance action targeted at the wormchain ibc receiver contract.
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum Action {
+    #[serde(rename = "1")]
+    UpdateChannelChain {
+        // an existing IBC channel ID
+        #[serde(with = "crate::serde_array")]
+        channel_id: [u8; 64],
+        // the chain associated with this IBC channel_id
+        chain_id: Chain,
+    },
+}
+
+// MODULE = "IbcReceiver"
+pub const MODULE: [u8; 32] = *b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00IbcReceiver";
+
+/// Represents the payload for a governance VAA targeted at the wormchain ibc receiver contract.
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct GovernancePacket {
+    /// Describes the chain on which the governance action should be carried out.
+    pub chain: Chain,
+
+    /// The actual governance action to be carried out.
+    pub action: Action,
+}
+
+mod governance_packet_impl {
+    use std::fmt;
+
+    use serde::{
+        de::{Error, MapAccess, SeqAccess, Visitor},
+        ser::SerializeStruct,
+        Deserialize, Deserializer, Serialize, Serializer,
+    };
+
+    use crate::{
+        ibc_receiver::{Action, GovernancePacket, MODULE},
+        Chain,
+    };
+
+    struct Module;
+
+    impl Serialize for Module {
+        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+        where
+            S: Serializer,
+        {
+            MODULE.serialize(serializer)
+        }
+    }
+
+    impl<'de> Deserialize<'de> for Module {
+        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+        where
+            D: Deserializer<'de>,
+        {
+            let arr = <[u8; 32]>::deserialize(deserializer)?;
+
+            if arr == MODULE {
+                Ok(Module)
+            } else {
+                Err(Error::custom(
+                    "invalid governance module, expected \"IbcReceiver\"",
+                ))
+            }
+        }
+    }
+
+    // governance actions
+    #[derive(Serialize, Deserialize)]
+    struct UpdateChannelChain {
+        #[serde(with = "crate::serde_array")]
+        channel_id: [u8; 64],
+        chain_id: Chain,
+    }
+
+    impl Serialize for GovernancePacket {
+        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+        where
+            S: Serializer,
+        {
+            let mut seq = serializer.serialize_struct("GovernancePacket", 4)?;
+            seq.serialize_field("module", &Module)?;
+
+            // The wire format encodes the action before the chain and then appends the actual
+            // action payload.
+            match self.action.clone() {
+                Action::UpdateChannelChain {
+                    channel_id,
+                    chain_id,
+                } => {
+                    seq.serialize_field("action", &1u8)?;
+                    seq.serialize_field("chain", &self.chain)?;
+                    seq.serialize_field(
+                        "payload",
+                        &UpdateChannelChain {
+                            channel_id,
+                            chain_id,
+                        },
+                    )?;
+                }
+            }
+
+            seq.end()
+        }
+    }
+
+    struct GovernancePacketVisitor;
+
+    impl<'de> Visitor<'de> for GovernancePacketVisitor {
+        type Value = GovernancePacket;
+
+        fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
+            f.write_str("struct GovernancePacket")
+        }
+
+        #[inline]
+        fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
+        where
+            A: SeqAccess<'de>,
+        {
+            static EXPECTING: &str = "struct GovernancePacket with 4 elements";
+
+            let _: Module = seq
+                .next_element()?
+                .ok_or_else(|| Error::invalid_length(0, &EXPECTING))?;
+            let act: u8 = seq
+                .next_element()?
+                .ok_or_else(|| Error::invalid_length(1, &EXPECTING))?;
+            let chain = seq
+                .next_element()?
+                .ok_or_else(|| Error::invalid_length(2, &EXPECTING))?;
+
+            let action = match act {
+                1 => {
+                    let UpdateChannelChain {
+                        channel_id,
+                        chain_id,
+                    } = seq
+                        .next_element()?
+                        .ok_or_else(|| Error::invalid_length(3, &EXPECTING))?;
+
+                    Action::UpdateChannelChain {
+                        channel_id,
+                        chain_id,
+                    }
+                }
+                v => {
+                    return Err(Error::custom(format_args!(
+                        "invalid value: {v}, expected 1"
+                    )))
+                }
+            };
+
+            Ok(GovernancePacket { chain, action })
+        }
+
+        fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
+        where
+            A: MapAccess<'de>,
+        {
+            #[derive(Serialize, Deserialize)]
+            #[serde(rename_all = "snake_case")]
+            enum Field {
+                Module,
+                Action,
+                Chain,
+                Payload,
+            }
+
+            let mut module = None;
+            let mut chain = None;
+            let mut action = None;
+            let mut payload = None;
+
+            while let Some(key) = map.next_key::<Field>()? {
+                match key {
+                    Field::Module => {
+                        if module.is_some() {
+                            return Err(Error::duplicate_field("module"));
+                        }
+
+                        module = map.next_value::<Module>().map(Some)?;
+                    }
+                    Field::Action => {
+                        if action.is_some() {
+                            return Err(Error::duplicate_field("action"));
+                        }
+
+                        action = map.next_value::<u8>().map(Some)?;
+                    }
+                    Field::Chain => {
+                        if chain.is_some() {
+                            return Err(Error::duplicate_field("chain"));
+                        }
+
+                        chain = map.next_value().map(Some)?;
+                    }
+                    Field::Payload => {
+                        if payload.is_some() {
+                            return Err(Error::duplicate_field("payload"));
+                        }
+
+                        let a = action.as_ref().copied().ok_or_else(|| {
+                            Error::custom("`action` must be known before deserializing `payload`")
+                        })?;
+
+                        let p = match a {
+                            1 => {
+                                let UpdateChannelChain {
+                                    channel_id,
+                                    chain_id,
+                                } = map.next_value()?;
+
+                                Action::UpdateChannelChain {
+                                    channel_id,
+                                    chain_id,
+                                }
+                            }
+                            v => {
+                                return Err(Error::custom(format_args!(
+                                    "invalid action: {v}, expected one of: 1, 2"
+                                )))
+                            }
+                        };
+
+                        payload = Some(p);
+                    }
+                }
+            }
+
+            let _ = module.ok_or_else(|| Error::missing_field("module"))?;
+            let chain = chain.ok_or_else(|| Error::missing_field("chain"))?;
+            let action = payload.ok_or_else(|| Error::missing_field("payload"))?;
+
+            Ok(GovernancePacket { chain, action })
+        }
+    }
+
+    impl<'de> Deserialize<'de> for GovernancePacket {
+        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+        where
+            D: Deserializer<'de>,
+        {
+            const FIELDS: &[&str] = &["module", "action", "chain", "payload"];
+            deserializer.deserialize_struct("GovernancePacket", FIELDS, GovernancePacketVisitor)
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use crate::{vaa::Signature, Chain, Vaa, GOVERNANCE_EMITTER};
+
+    use super::{Action, GovernancePacket};
+
+    #[test]
+    fn happy_path() {
+        let buf = [
+            // version
+            0x01, // guardian set index
+            0x00, // signatures
+            0x00, 0x00, 0x00, 0x01, 0x00, 0xb0, 0x72, 0x50, 0x5b, 0x5b, 0x99, 0x9c, 0x1d, 0x08,
+            0x90, 0x5c, 0x02, 0xe2, 0xb6, 0xb2, 0x83, 0x2e, 0xf7, 0x2c, 0x0b, 0xa6, 0xc8, 0xdb,
+            0x4f, 0x77, 0xfe, 0x45, 0x7e, 0xf2, 0xb3, 0xd0, 0x53, 0x41, 0x0b, 0x1e, 0x92, 0xa9,
+            0x19, 0x4d, 0x92, 0x10, 0xdf, 0x24, 0xd9, 0x87, 0xac, 0x83, 0xd7, 0xb6, 0xf0, 0xc2,
+            0x1c, 0xe9, 0x0f, 0x8b, 0xc1, 0x86, 0x9d, 0xe0, 0x89, 0x8b, 0xda, 0x7e, 0x98, 0x01,
+            // timestamp
+            0x00, 0x00, 0x00, 0x01, // nonce
+            0x00, 0x00, 0x00, 0x01, // emitter chain
+            0x00, 0x01, // emitter address
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x04, // sequence
+            0x00, 0x00, 0x00, 0x00, 0x01, 0x3c, 0x1b, 0xfa, // consistency
+            0x00, //  module = "IbcReceiver"
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x62, 0x63, 0x52, 0x65, 0x63, 0x65,
+            0x69, 0x76, 0x65, 0x72, // action (IbcReceiverActionUpdateChannelChain)
+            0x01, // target chain_id (unset)
+            0x00, 0x00, // IBC channel_id for the mapping ("channel-0")
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63,
+            0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x2d, 0x30, // IBC chain_id for the mapping
+            0x00, 0x13,
+        ];
+
+        let channel_id_bytes: [u8; 64] =
+            *b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00channel-0";
+
+        let vaa = Vaa {
+            version: 1,
+            guardian_set_index: 0,
+            signatures: vec![Signature {
+                index: 0,
+                signature: [
+                    0xb0, 0x72, 0x50, 0x5b, 0x5b, 0x99, 0x9c, 0x1d, 0x08, 0x90, 0x5c, 0x02, 0xe2,
+                    0xb6, 0xb2, 0x83, 0x2e, 0xf7, 0x2c, 0x0b, 0xa6, 0xc8, 0xdb, 0x4f, 0x77, 0xfe,
+                    0x45, 0x7e, 0xf2, 0xb3, 0xd0, 0x53, 0x41, 0x0b, 0x1e, 0x92, 0xa9, 0x19, 0x4d,
+                    0x92, 0x10, 0xdf, 0x24, 0xd9, 0x87, 0xac, 0x83, 0xd7, 0xb6, 0xf0, 0xc2, 0x1c,
+                    0xe9, 0x0f, 0x8b, 0xc1, 0x86, 0x9d, 0xe0, 0x89, 0x8b, 0xda, 0x7e, 0x98, 0x01,
+                ],
+            }],
+            timestamp: 1,
+            nonce: 1,
+            emitter_chain: Chain::Solana,
+            emitter_address: GOVERNANCE_EMITTER,
+            sequence: 20_716_538,
+            consistency_level: 0,
+            payload: GovernancePacket {
+                chain: Chain::Any,
+                action: Action::UpdateChannelChain {
+                    channel_id: channel_id_bytes,
+                    chain_id: Chain::Injective,
+                },
+            },
+        };
+
+        assert_eq!(buf.as_ref(), &serde_wormhole::to_vec(&vaa).unwrap());
+        assert_eq!(vaa, serde_wormhole::from_slice(&buf).unwrap());
+
+        let encoded = serde_json::to_string(&vaa).unwrap();
+        assert_eq!(vaa, serde_json::from_str(&encoded).unwrap());
+    }
+}

+ 1 - 0
sdk/rust/core/src/lib.rs

@@ -19,6 +19,7 @@ pub mod accountant;
 mod arraystring;
 mod chain;
 pub mod core;
+pub mod ibc_receiver;
 pub mod nft;
 mod serde_array;
 pub mod token;

+ 61 - 11
sdk/vaa/payloads.go

@@ -3,6 +3,7 @@ package vaa
 import (
 	"bytes"
 	"encoding/binary"
+	"fmt"
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/holiman/uint256"
@@ -23,6 +24,14 @@ var CircleIntegrationModule = [32]byte{
 }
 var CircleIntegrationModuleStr = string(CircleIntegrationModule[:])
 
+// WasmdModule is the identifier of the Wormchain ibc_receiver contract module (which is used for governance messages)
+// It is the hex representation of "IbcReceiver" left padded with zeroes.
+var IbcReceiverModule = [32]byte{
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x62, 0x63, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72,
+}
+var IbcReceiverModuleStr = string(IbcReceiverModule[:])
+
 type GovernanceAction uint8
 
 var (
@@ -51,6 +60,9 @@ var (
 	CircleIntegrationActionUpdateWormholeFinality        GovernanceAction = 1
 	CircleIntegrationActionRegisterEmitterAndDomain      GovernanceAction = 2
 	CircleIntegrationActionUpgradeContractImplementation GovernanceAction = 3
+
+	// Ibc Receiver governance actions
+	IbcReceiverActionUpdateChannelChain GovernanceAction = 1
 )
 
 type (
@@ -127,6 +139,17 @@ type (
 		TargetChainID            ChainID
 		NewImplementationAddress [32]byte
 	}
+
+	// BodyIbcReceiverUpdateChannelChain is a governance message to update the ibc channel_id -> chain_id mapping in the ibc_receiver contract
+	BodyIbcReceiverUpdateChannelChain struct {
+		// The chain that this governance VAA should be redeemed on
+		TargetChainId ChainID
+
+		// This should follow the IBC channel identifier standard: https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements#paths-identifiers-separators
+		// If the identifier string is shorter than 64 bytes, the correct number of 0x00 bytes should be prepended.
+		ChannelId [64]byte
+		ChainId   ChainID
+	}
 )
 
 func (b BodyContractUpgrade) Serialize() []byte {
@@ -228,18 +251,15 @@ func (r BodyCircleIntegrationUpgradeContractImplementation) Serialize() []byte {
 	return serializeBridgeGovernanceVaa(CircleIntegrationModuleStr, CircleIntegrationActionUpgradeContractImplementation, r.TargetChainID, payload.Bytes())
 }
 
-func serializeBridgeGovernanceVaa(module string, actionId GovernanceAction, chainId ChainID, payload []byte) []byte {
-	if len(module) > 32 {
-		panic("module longer than 32 byte")
-	}
-
-	buf := &bytes.Buffer{}
+func (r BodyIbcReceiverUpdateChannelChain) Serialize() []byte {
+	payload := &bytes.Buffer{}
+	payload.Write(r.ChannelId[:])
+	MustWrite(payload, binary.BigEndian, r.ChainId)
+	return serializeBridgeGovernanceVaa(IbcReceiverModuleStr, IbcReceiverActionUpdateChannelChain, r.TargetChainId, payload.Bytes())
+}
 
-	// Write token bridge header
-	for i := 0; i < (32 - len(module)); i++ {
-		buf.WriteByte(0x00)
-	}
-	buf.Write([]byte(module))
+func serializeBridgeGovernanceVaa(module string, actionId GovernanceAction, chainId ChainID, payload []byte) []byte {
+	buf := LeftPadBytes(module, 32)
 	// Write action ID
 	MustWrite(buf, binary.BigEndian, actionId)
 	// Write target chain
@@ -249,3 +269,33 @@ func serializeBridgeGovernanceVaa(module string, actionId GovernanceAction, chai
 
 	return buf.Bytes()
 }
+
+func LeftPadIbcChannelId(channelId string) [64]byte {
+	channelIdBuf := LeftPadBytes(channelId, 64)
+	var channelIdIdLeftPadded [64]byte
+	copy(channelIdIdLeftPadded[:], channelIdBuf.Bytes())
+	return channelIdIdLeftPadded
+}
+
+// Prepends 0x00 bytes to the payload buffer, up to a size of `length`
+func LeftPadBytes(payload string, length int) *bytes.Buffer {
+	if length < 0 {
+		panic("cannot prepend bytes to a negative length buffer")
+	}
+
+	if len(payload) > length {
+		panic(fmt.Sprintf("payload longer than %d bytes", length))
+	}
+
+	buf := &bytes.Buffer{}
+
+	// Prepend correct number of 0x00 bytes to the payload slice
+	for i := 0; i < (length - len(payload)); i++ {
+		buf.WriteByte(0x00)
+	}
+
+	// add the payload slice
+	buf.Write([]byte(payload))
+
+	return buf
+}

+ 14 - 1
sdk/vaa/payloads_test.go

@@ -84,7 +84,7 @@ func TestBodyTokenBridgeRegisterChainSerialize(t *testing.T) {
 			name:     "panic_at_the_disco!",
 			panic:    true,
 			object:   BodyTokenBridgeRegisterChain{Module: "123456789012345678901234567890123", ChainID: 1, EmitterAddress: addr},
-			expected: "module longer than 32 byte",
+			expected: "payload longer than 32 bytes",
 		},
 	}
 	for _, testCase := range tests {
@@ -149,3 +149,16 @@ func TestBodyCircleIntegrationUpgradeContractImplementationSerialize(t *testing.
 	}
 	assert.Equal(t, expected, hex.EncodeToString(bodyCircleIntegrationUpgradeContractImplementation.Serialize()))
 }
+
+func TestBodyIbcReceiverUpdateChannelChain(t *testing.T) {
+	expected := "0000000000000000000000000000000000000000004962635265636569766572010c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006368616e6e656c2d300013"
+
+	channelId := LeftPadIbcChannelId("channel-0")
+
+	bodyIbcReceiverUpdateChannelChain := BodyIbcReceiverUpdateChannelChain{
+		TargetChainId: ChainIDWormchain,
+		ChannelId:     channelId,
+		ChainId:       ChainIDInjective,
+	}
+	assert.Equal(t, expected, hex.EncodeToString(bodyIbcReceiverUpdateChannelChain.Serialize()))
+}

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است