From 30c1fa1279f020e4e00b428ecc432802ce9bcbd8 Mon Sep 17 00:00:00 2001 From: matteo Date: Thu, 21 May 2026 00:58:14 +0200 Subject: [PATCH 1/2] chore: workflows update --- .github/workflows/ci.yml | 2 +- .github/workflows/fast-forward.yml | 4 ++-- .github/workflows/release-plz.yml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3315cfbb..e05c30a2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v6 - - uses: dorny/paths-filter@v3 + - uses: dorny/paths-filter@v4 id: filter with: filters: | diff --git a/.github/workflows/fast-forward.yml b/.github/workflows/fast-forward.yml index f299fe7b..22eb7371 100644 --- a/.github/workflows/fast-forward.yml +++ b/.github/workflows/fast-forward.yml @@ -6,7 +6,7 @@ jobs: fast-forward: # Only run if the comment contains the /fast-forward command. if: ${{ contains(github.event.comment.body, '/fast-forward') - && github.event.issue.pull_request }} + && github.event.issue.pull_request }} runs-on: ubuntu-latest permissions: @@ -17,7 +17,7 @@ jobs: steps: # Generating a GitHub token, so that fast-forwards can trigger actions workflows. - name: Generate GitHub token - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@v3 id: generate-token with: app-id: ${{ secrets.RELEASE_PLZ_APP_ID }} diff --git a/.github/workflows/release-plz.yml b/.github/workflows/release-plz.yml index 5aa00a8e..fbacc474 100644 --- a/.github/workflows/release-plz.yml +++ b/.github/workflows/release-plz.yml @@ -20,7 +20,7 @@ jobs: # Generating a GitHub token, so that PRs and tags created by # the release-plz-action can trigger actions workflows. - name: Generate GitHub token - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@v3 id: generate-token with: app-id: ${{ secrets.RELEASE_PLZ_APP_ID }} @@ -57,7 +57,7 @@ jobs: # Generating a GitHub token, so that PRs and tags created by # the release-plz-action can trigger actions workflows. - name: Generate GitHub token - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@v3 id: generate-token with: app-id: ${{ secrets.RELEASE_PLZ_APP_ID }} From 95dd0d40b0be4e942a51591a5950c757c6650e5f Mon Sep 17 00:00:00 2001 From: matteo Date: Thu, 21 May 2026 01:01:55 +0200 Subject: [PATCH 2/2] feat(af-move-type): add AnyT --- crates/af-move-type/src/any.rs | 150 +++++++++++++++++++++++++++++++++ crates/af-move-type/src/lib.rs | 1 + 2 files changed, 151 insertions(+) create mode 100644 crates/af-move-type/src/any.rs diff --git a/crates/af-move-type/src/any.rs b/crates/af-move-type/src/any.rs new file mode 100644 index 00000000..b59664fa --- /dev/null +++ b/crates/af-move-type/src/any.rs @@ -0,0 +1,150 @@ +//! Permissive type marker that accepts any Move type in its slot. + +use std::str::FromStr; + +use af_sui_types::TypeTag; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use crate::{MoveType, ParseTypeTagError, TypeTagError}; + +/// Generic type that accepts **any** Move type argument in its slot, +/// including phantom-paramed generics such as `VENDOR` as well as +/// non-struct types (primitives, vectors). +/// +/// Where [`crate::otw::Otw`] requires `n_types_expected == 0` and therefore +/// rejects phantom-paramed arguments, `AnyT` performs no validation — its +/// associated [`AnyTTypeTag`] simply captures the raw [`TypeTag`] so it +/// can be read back unchanged. +/// +/// `AnyT` is intended for **phantom slots only** in generic Move types +/// (e.g. `AuthorityCap`). It is never instantiated as a real +/// value; its [`MoveType`] impl exists so the derive-generated parsers can +/// thread phantom-paramed type arguments through unchanged. +/// +/// Note: `AnyT` deliberately does **not** implement [`MoveStruct`] — +/// `MoveStructTag: TryFrom` is incompatible with carrying an +/// arbitrary [`TypeTag`] (which may be non-struct). Use [`crate::otw::Otw`] +/// when you need a `MoveStruct`-bounded slot type. +/// +/// Unlike `Otw`, `AnyT` does **not** implement [`crate::StaticTypeTag`] or +/// its `Static*` siblings: it has no statically known type tag, by design. +/// Use it on the runtime decode path (e.g. +/// [`MoveInstance::from_raw_type`](crate::MoveInstance::from_raw_type)). +/// +/// [`MoveStruct`]: crate::MoveStruct +#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq, Hash)] +pub struct AnyT { + dummy_field: bool, +} + +impl AnyT { + pub fn new() -> Self { + Self::default() + } +} + +impl std::fmt::Display for AnyT { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "AnyT") + } +} + +/// `TypeTag` companion for [`AnyT`]. Wraps the raw [`TypeTag`] from the slot +/// with **no** validation of variant, address, module, name, or type-param +/// count. +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct AnyTTypeTag(pub TypeTag); + +impl From for TypeTag { + fn from(value: AnyTTypeTag) -> Self { + value.0 + } +} + +impl TryFrom for AnyTTypeTag { + type Error = TypeTagError; + + fn try_from(value: TypeTag) -> Result { + Ok(Self(value)) + } +} + +impl FromStr for AnyTTypeTag { + type Err = ParseTypeTagError; + + fn from_str(s: &str) -> Result { + let tag: TypeTag = s.parse()?; + Ok(Self(tag)) + } +} + +impl std::fmt::Display for AnyTTypeTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl Serialize for AnyTTypeTag { + fn serialize(&self, ser: S) -> Result { + ser.collect_str(&self.0) + } +} + +impl<'de> Deserialize<'de> for AnyTTypeTag { + fn deserialize>(de: D) -> Result { + use serde::de::Error as _; + let s = String::deserialize(de)?; + s.parse().map_err(D::Error::custom) + } +} + +impl MoveType for AnyT { + type TypeTag = AnyTTypeTag; +} + +#[cfg(test)] +mod tests { + use super::*; + + /// `AnyTTypeTag` accepts a struct tag whose own type params carry + /// phantom-paramed structs — the exact case `Otw` rejects. + #[test] + fn accepts_phantom_paramed_struct_tag() { + let raw: TypeTag = "0x1::authority::AuthorityCap<0x1::authority::VENDOR<0x2::aftermath::AFTERMATH>, 0x1::authority::ASSISTANT>" + .parse() + .unwrap(); + let wrapped = AnyTTypeTag::try_from(raw.clone()).unwrap(); + let back: TypeTag = wrapped.into(); + assert_eq!(back, raw); + } + + /// `AnyTTypeTag` is permissive over `TypeTag` variants — not just + /// `Struct(_)` — unlike a `MoveStruct`'s derived companion. + #[test] + fn accepts_non_struct_type_tag() { + for raw in [TypeTag::U64, TypeTag::Bool, TypeTag::Address] { + let wrapped = AnyTTypeTag::try_from(raw.clone()).unwrap(); + assert_eq!(TypeTag::from(wrapped), raw); + } + } + + /// `Display` / `FromStr` round-trip through the canonical TypeTag form. + #[test] + fn display_fromstr_roundtrip() { + let raw: TypeTag = "0x1::a::B<0x2::c::D<0x3::e::F>>".parse().unwrap(); + let wrapped = AnyTTypeTag(raw.clone()); + let s = wrapped.to_string(); + let parsed = AnyTTypeTag::from_str(&s).unwrap(); + assert_eq!(parsed.0, raw); + } + + /// `Serialize` / `Deserialize` round-trip via JSON. + #[test] + fn serde_roundtrip_json() { + let raw: TypeTag = "0x1::a::B<0x2::c::D<0x3::e::F>>".parse().unwrap(); + let wrapped = AnyTTypeTag(raw.clone()); + let json = serde_json::to_string(&wrapped).unwrap(); + let back: AnyTTypeTag = serde_json::from_str(&json).unwrap(); + assert_eq!(back.0, raw); + } +} diff --git a/crates/af-move-type/src/lib.rs b/crates/af-move-type/src/lib.rs index e543553d..82f9a9bb 100644 --- a/crates/af-move-type/src/lib.rs +++ b/crates/af-move-type/src/lib.rs @@ -21,6 +21,7 @@ use af_sui_types::u256::U256; use af_sui_types::{Address, Identifier, StructTag, TypeTag}; use serde::{Deserialize, Serialize}; +pub mod any; #[doc(hidden)] pub mod external; pub mod otw;