Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "package_upgrade_base"
version = "0.0.1"
edition = "2024"

[addresses]
base_addr = "0x0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) Mysten Labs, Inc.
// Modifications Copyright (c) 2026 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

module base_addr::base {
#[view]
public fun return_0(): u64 { 0 }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "package_upgrade_base"
version = "0.0.1"
edition = "2024"

[addresses]
base_addr = "0x0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright (c) Mysten Labs, Inc.
// Modifications Copyright (c) 2026 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

module base_addr::base {
public fun return_0(): u64 { 0 }
}
17 changes: 17 additions & 0 deletions crates/iota-core/src/unit_tests/move_package_upgrade_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,23 @@ async fn test_upgrade_incompatible() {
)
}

#[tokio::test]
async fn test_upgrade_cannot_remove_view_attribute() {
let mut runner = UpgradeStateRunner::new("move_upgrade/view_base").await;

let (digest, modules) = build_upgrade_test_modules("view_removed");
let effects = runner
.upgrade(UpgradePolicy::COMPATIBLE, digest, modules, vec![])
.await;

assert_eq!(
effects.into_status().unwrap_err().0,
ExecutionFailureStatus::PackageUpgradeError {
upgrade_error: PackageUpgradeError::IncompatibleUpgrade,
},
)
}

#[tokio::test]
async fn test_upgrade_package_incorrect_digest() {
let mut runner = UpgradeStateRunner::new("move_upgrade/base").await;
Expand Down
7 changes: 7 additions & 0 deletions crates/iota-move-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use iota_types::{
move_package::{
FnInfo, FnInfoKey, FnInfoMap, IotaAttribute, MovePackage, RuntimeModuleMetadata,
RuntimeModuleMetadataWrapper, get_authenticator_version_from_fun,
is_view_function_from_fn_info,
},
};
use iota_verifier::verifier as iota_bytecode_verifier;
Expand Down Expand Up @@ -164,6 +165,7 @@ impl BuildConfig {
let fn_name = s.as_str().to_string();
let is_test = mod_is_test || info.attributes.is_test_or_test_only();
let authenticator_version = info.attributes.get_authenticator();
let is_view = info.attributes.is_view();
fn_info_map.insert(
FnInfoKey {
fn_name,
Expand All @@ -173,6 +175,7 @@ impl BuildConfig {
FnInfo {
is_test,
authenticator_version,
is_view,
},
);
}
Expand Down Expand Up @@ -382,6 +385,10 @@ fn fill_metadata(package: &mut MoveCompiledPackage, fn_info_map: &FnInfoMap) ->
IotaAttribute::authenticator_attribute(version),
);
};
if is_view_function_from_fn_info(fn_name, module, fn_info_map) {
runtime_metadata
.add_function_attribute(fn_name.to_string(), IotaAttribute::view_attribute());
}
}
if !runtime_metadata.is_empty() {
module.metadata.push(move_core_types::metadata::Metadata {
Expand Down
6 changes: 6 additions & 0 deletions crates/iota-transactional-test-runner/src/test_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2943,6 +2943,12 @@ fn fill_metadata(modules: &mut [MaybeNamedCompiledModule]) {
IotaAttribute::authenticator_attribute(version),
);
}
if info.attributes.is_view() {
runtime_metadata.add_function_attribute(
name.as_str().to_owned(),
IotaAttribute::view_attribute(),
);
}
}
}

Expand Down
28 changes: 28 additions & 0 deletions crates/iota-types/src/move_package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ pub struct FnInfo {
/// If set, function was marked to represent authenticator function of
/// given version.
pub authenticator_version: Option<u8>,
pub is_view: bool,
}

#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
Expand Down Expand Up @@ -700,11 +701,33 @@ pub fn get_authenticator_version_from_fun(
Some(FnInfo {
is_test: _,
authenticator_version: Some(v),
is_view: _,
}) => Some(*v),
_ => None,
}
}

/// Returns true if a function is marked as a view function.
pub fn is_view_function_from_fn_info(
name: &IdentStr,
module: &CompiledModule,
fn_info_map: &FnInfoMap,
) -> bool {
let fn_name = name.to_string();
let mod_handle = module.self_handle();
let mod_addr = *module.address_identifier_at(mod_handle.address);
let mod_name = module.name().to_string();
let fn_info_key = FnInfoKey {
fn_name,
mod_name,
mod_addr,
};
fn_info_map
.get(&fn_info_key)
.map(|info| info.is_view)
.unwrap_or(false)
}

/// If `include_code` is set to `false`, the normalized module will skip
/// function bodies but still include the signatures.
pub fn normalize_modules<
Expand Down Expand Up @@ -997,6 +1020,7 @@ impl TryFrom<RuntimeModuleMetadataWrapper> for RuntimeModuleMetadata {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum IotaAttribute {
Authenticator(AuthenticatorAttribute),
View,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
Expand All @@ -1008,6 +1032,10 @@ impl IotaAttribute {
pub fn authenticator_attribute(version: u8) -> Self {
IotaAttribute::Authenticator(AuthenticatorAttribute { version })
}

pub fn view_attribute() -> Self {
IotaAttribute::View
}
}

/// V1 of IOTA specific metadata.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,41 @@
use std::{collections::BTreeSet, fmt};

use authenticator::AuthenticatorAttribute;
use view::ViewAttribute;

use crate::shared::known_attributes::{AttributePosition, KnownAttribute as MoveKnownAttribute};

pub mod authenticator;
pub mod view;

/// The list of attribute types recognized by the compiler for the IOTA
/// Flavor.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum KnownAttribute {
Authenticator(AuthenticatorAttribute),
View(ViewAttribute),
}

impl KnownAttribute {
pub fn resolve(attribute_str: impl AsRef<str>) -> Option<MoveKnownAttribute> {
Some(match attribute_str.as_ref() {
AuthenticatorAttribute::AUTHENTICATOR => AuthenticatorAttribute.into(),
ViewAttribute::VIEW => ViewAttribute.into(),
_ => return None,
})
}

pub const fn name(&self) -> &str {
match self {
Self::Authenticator(a) => a.name(),
Self::View(a) => a.name(),
}
}

pub fn expected_positions(&self) -> &'static BTreeSet<AttributePosition> {
match self {
Self::Authenticator(a) => a.expected_positions(),
Self::View(a) => a.expected_positions(),
}
}
}
Expand All @@ -47,6 +53,7 @@ impl fmt::Display for KnownAttribute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Authenticator(a) => a.fmt(f),
Self::View(a) => a.fmt(f),
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) 2026 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

//! IOTA View Attribute

use once_cell::sync::Lazy;
use std::{collections::BTreeSet, fmt};

use crate::{
expansion::ast::Attributes,
shared::known_attributes::{
AttributePosition, FlavoredAttribute, KnownAttribute as MoveKnownAttribute,
},
};

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct ViewAttribute;

impl ViewAttribute {
pub const VIEW: &'static str = "view";

pub const fn name(&self) -> &'static str {
Self::VIEW
}

pub fn expected_positions(&self) -> &'static BTreeSet<AttributePosition> {
static VIEW_POSITIONS: Lazy<BTreeSet<AttributePosition>> =
Lazy::new(|| BTreeSet::from([AttributePosition::Function]));
&VIEW_POSITIONS
}
}

//**************************************************************************************************
// Attributes implementation
//**************************************************************************************************

impl Attributes {
/// Returns true if the function has the `#[view]` attribute.
pub fn is_view(&self) -> bool {
self.contains_key_(&MoveKnownAttribute::from(ViewAttribute))
}
}

impl From<ViewAttribute> for MoveKnownAttribute {
fn from(a: ViewAttribute) -> Self {
Self::Flavored(FlavoredAttribute {
name: a.name(),
expected_positions: a.expected_positions(),
})
}
}

//**************************************************************************************************
// Display
//**************************************************************************************************

impl fmt::Display for ViewAttribute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,15 @@ pub const PRIVATE_ACCOUNT_CALL_DIAG: DiagnosticInfo = custom(
10,
"invalid private account call",
);
pub const VIEW_FUN_SIGNATURE_DIAG: DiagnosticInfo = custom(
IOTA_DIAG_PREFIX,
Severity::NonblockingError,
// category
TYPING,
// code
11,
"invalid 'view' function signature",
);

// Bridge supported asset
pub const BRIDGE_SUPPORTED_ASSET: &[&str] = &["btc", "eth", "usdc", "usdt"];
Loading
Loading