diff --git a/crates/sui-graphql-e2e-tests/tests/packages/versioning.exp b/crates/sui-graphql-e2e-tests/tests/packages/versioning.exp new file mode 100644 index 0000000000000..4c37560181402 --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/packages/versioning.exp @@ -0,0 +1,318 @@ +processed 9 tasks + +init: +A: object(0,0) + +task 1, lines 6-9: +//# publish --upgradeable --sender A +created: object(1,0), object(1,1) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 5076800, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2, lines 11-15: +//# upgrade --package P0 --upgrade-capability 1,1 --sender A +created: object(2,0) +mutated: object(0,0), object(1,1) +gas summary: computation_cost: 1000000, storage_cost: 5251600, storage_rebate: 2595780, non_refundable_storage_fee: 26220 + +task 3, lines 17-22: +//# upgrade --package P1 --upgrade-capability 1,1 --sender A +created: object(3,0) +mutated: object(0,0), object(1,1) +gas summary: computation_cost: 1000000, storage_cost: 5426400, storage_rebate: 2595780, non_refundable_storage_fee: 26220 + +task 4, line 24: +//# create-checkpoint +Checkpoint created: 1 + +task 5, lines 26-45: +//# run-graphql +Response: { + "data": { + "v1": { + "module": { + "functions": { + "nodes": [ + { + "name": "f" + } + ] + } + } + }, + "v2": { + "module": { + "functions": { + "nodes": [ + { + "name": "f" + }, + { + "name": "g" + } + ] + } + } + }, + "v3": { + "module": { + "functions": { + "nodes": [ + { + "name": "f" + }, + { + "name": "g" + }, + { + "name": "h" + } + ] + } + } + } + } +} + +task 6, lines 47-84: +//# run-graphql +Response: { + "data": { + "v1_from_p1": { + "module": { + "functions": { + "nodes": [ + { + "name": "f" + } + ] + } + } + }, + "v1_from_p2": { + "module": { + "functions": { + "nodes": [ + { + "name": "f" + } + ] + } + } + }, + "v2_from_p0": { + "module": { + "functions": { + "nodes": [ + { + "name": "f" + }, + { + "name": "g" + } + ] + } + } + }, + "v2_from_p2": { + "module": { + "functions": { + "nodes": [ + { + "name": "f" + }, + { + "name": "g" + } + ] + } + } + }, + "v3_from_p0": { + "module": { + "functions": { + "nodes": [ + { + "name": "f" + }, + { + "name": "g" + }, + { + "name": "h" + } + ] + } + } + }, + "v3_from_p1": { + "module": { + "functions": { + "nodes": [ + { + "name": "f" + }, + { + "name": "g" + }, + { + "name": "h" + } + ] + } + } + } + } +} + +task 7, lines 86-141: +//# run-graphql +Response: { + "data": { + "v1": { + "v1": { + "module": { + "functions": { + "nodes": [ + { + "name": "f" + } + ] + } + } + }, + "v2": { + "module": { + "functions": { + "nodes": [ + { + "name": "f" + }, + { + "name": "g" + } + ] + } + } + }, + "v3": { + "module": { + "functions": { + "nodes": [ + { + "name": "f" + }, + { + "name": "g" + }, + { + "name": "h" + } + ] + } + } + } + }, + "v2": { + "v1": { + "module": { + "functions": { + "nodes": [ + { + "name": "f" + } + ] + } + } + }, + "v2": { + "module": { + "functions": { + "nodes": [ + { + "name": "f" + }, + { + "name": "g" + } + ] + } + } + }, + "v3": { + "module": { + "functions": { + "nodes": [ + { + "name": "f" + }, + { + "name": "g" + }, + { + "name": "h" + } + ] + } + } + } + }, + "v3": { + "v1": { + "module": { + "functions": { + "nodes": [ + { + "name": "f" + } + ] + } + } + }, + "v2": { + "module": { + "functions": { + "nodes": [ + { + "name": "f" + }, + { + "name": "g" + } + ] + } + } + }, + "v3": { + "module": { + "functions": { + "nodes": [ + { + "name": "f" + }, + { + "name": "g" + }, + { + "name": "h" + } + ] + } + } + } + } + } +} + +task 8, lines 143-171: +//# run-graphql +Response: { + "data": { + "v0": null, + "v1": { + "v0": null, + "v4": null + }, + "v4": null + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/packages/versioning.move b/crates/sui-graphql-e2e-tests/tests/packages/versioning.move new file mode 100644 index 0000000000000..72bdc66db632f --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/packages/versioning.move @@ -0,0 +1,171 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --protocol-version 39 --addresses P0=0x0 P1=0x0 P2=0x0 --accounts A --simulator + +//# publish --upgradeable --sender A +module P0::m { + public fun f(): u64 { 42 } +} + +//# upgrade --package P0 --upgrade-capability 1,1 --sender A +module P1::m { + public fun f(): u64 { 42 } + public fun g(): u64 { 43 } +} + +//# upgrade --package P1 --upgrade-capability 1,1 --sender A +module P2::m { + public fun f(): u64 { 42 } + public fun g(): u64 { 43 } + public fun h(): u64 { 44 } +} + +//# create-checkpoint + +//# run-graphql +{ # Test fetching by ID + v1: package(address: "@{P0}") { + module(name: "m") { + functions { nodes { name } } + } + } + + v2: package(address: "@{P1}") { + module(name: "m") { + functions { nodes { name } } + } + } + + v3: package(address: "@{P2}") { + module(name: "m") { + functions { nodes { name } } + } + } +} + +//# run-graphql +{ # Test fetching by version + v1_from_p1: package(address: "@{P1}", version: 1) { + module(name: "m") { + functions { nodes { name } } + } + } + + v1_from_p2: package(address: "@{P2}", version: 1) { + module(name: "m") { + functions { nodes { name } } + } + } + + v2_from_p0: package(address: "@{P0}", version: 2) { + module(name: "m") { + functions { nodes { name } } + } + } + + v2_from_p2: package(address: "@{P2}", version: 2) { + module(name: "m") { + functions { nodes { name } } + } + } + + v3_from_p0: package(address: "@{P0}", version: 3) { + module(name: "m") { + functions { nodes { name } } + } + } + + v3_from_p1: package(address: "@{P1}", version: 3) { + module(name: "m") { + functions { nodes { name } } + } + } +} + +//# run-graphql +{ # Go from one version to another using packageAtVersion + v1: package(address: "@{P1}") { + v1: packageAtVersion(version: 1) { + module(name: "m") { + functions { nodes { name } } + } + } + v2: packageAtVersion(version: 2) { + module(name: "m") { + functions { nodes { name } } + } + } + v3: packageAtVersion(version: 3) { + module(name: "m") { + functions { nodes { name } } + } + } + } + + v2: package(address: "@{P2}") { + v1: packageAtVersion(version: 1) { + module(name: "m") { + functions { nodes { name } } + } + } + v2: packageAtVersion(version: 2) { + module(name: "m") { + functions { nodes { name } } + } + } + v3: packageAtVersion(version: 3) { + module(name: "m") { + functions { nodes { name } } + } + } + } + + v3: package(address: "@{P2}") { + v1: packageAtVersion(version: 1) { + module(name: "m") { + functions { nodes { name } } + } + } + v2: packageAtVersion(version: 2) { + module(name: "m") { + functions { nodes { name } } + } + } + v3: packageAtVersion(version: 3) { + module(name: "m") { + functions { nodes { name } } + } + } + } +} + +//# run-graphql +{ # Fetch out of range versions (should return null) + v0: package(address: "@{P0}", version: 0) { + module(name: "m") { + functions { nodes { name } } + } + } + + # This won't return null, but its inner queries will + v1: package(address: "@{P0}") { + v0: packageAtVersion(version: 0) { + module(name: "m") { + functions { nodes { name } } + } + } + + v4: packageAtVersion(version: 4) { + module(name: "m") { + functions { nodes { name } } + } + } + } + + v4: package(address: "@{P0}", version: 4) { + module(name: "m") { + functions { nodes { name } } + } + } +} diff --git a/crates/sui-graphql-rpc/schema/current_progress_schema.graphql b/crates/sui-graphql-rpc/schema/current_progress_schema.graphql index cbde89c2c6fc5..201b62290eabe 100644 --- a/crates/sui-graphql-rpc/schema/current_progress_schema.graphql +++ b/crates/sui-graphql-rpc/schema/current_progress_schema.graphql @@ -2168,6 +2168,11 @@ type MovePackage implements IObject & IOwner { """ bcs: Base64 """ + Fetch another version of this package (the package that shares this package's original ID, + but has the specified `version`). + """ + packageAtVersion(version: Int!): MovePackage + """ A representation of the module called `name` in this package, including the structs and functions it defines. """ @@ -3040,6 +3045,19 @@ type Query { """ object(address: SuiAddress!, version: UInt53): Object """ + The package corresponding to the given address at the (optionally) given version. + + When no version is given, the package is loaded directly from the address given. Otherwise, + the address is translated before loading to point to the package whose original ID matches + the package at `address`, but whose version is `version`. For non-system packages, this may + result in a different address than `address` because different versions of a package, + introduced by upgrades, exist at distinct addresses. + + Note that this interpretation of `version` is different from a historical object read (the + interpretation of `version` for the `object` query). + """ + package(address: SuiAddress!, version: UInt53): MovePackage + """ Look-up an Account by its SuiAddress. """ address(address: SuiAddress!): Address diff --git a/crates/sui-graphql-rpc/src/types/move_module.rs b/crates/sui-graphql-rpc/src/types/move_module.rs index e34ad6c46a8bc..f85d6fe558abc 100644 --- a/crates/sui-graphql-rpc/src/types/move_module.rs +++ b/crates/sui-graphql-rpc/src/types/move_module.rs @@ -15,7 +15,6 @@ use super::datatype::MoveDatatype; use super::move_enum::MoveEnum; use super::move_function::MoveFunction; use super::move_struct::MoveStruct; -use super::object::Object; use super::{base64::Base64, move_package::MovePackage, sui_address::SuiAddress}; #[derive(Clone)] @@ -40,7 +39,7 @@ impl MoveModule { MovePackage::query( ctx, self.storage_id, - Object::latest_at(self.checkpoint_viewed_at), + MovePackage::by_id_at(self.checkpoint_viewed_at), ) .await .extend()? @@ -91,7 +90,7 @@ impl MoveModule { let Some(package) = MovePackage::query( ctx, self.storage_id, - Object::latest_at(checkpoint_viewed_at), + MovePackage::by_id_at(checkpoint_viewed_at), ) .await .extend()? @@ -482,7 +481,7 @@ impl MoveModule { checkpoint_viewed_at: u64, ) -> Result, Error> { let Some(package) = - MovePackage::query(ctx, address, Object::latest_at(checkpoint_viewed_at)).await? + MovePackage::query(ctx, address, MovePackage::by_id_at(checkpoint_viewed_at)).await? else { return Ok(None); }; diff --git a/crates/sui-graphql-rpc/src/types/move_package.rs b/crates/sui-graphql-rpc/src/types/move_package.rs index 1791b0bde32ca..a9dbeb0188f15 100644 --- a/crates/sui-graphql-rpc/src/types/move_package.rs +++ b/crates/sui-graphql-rpc/src/types/move_package.rs @@ -1,6 +1,8 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use std::collections::{BTreeSet, HashMap}; + use super::balance::{self, Balance}; use super::base64::Base64; use super::big_int::BigInt; @@ -8,9 +10,7 @@ use super::coin::Coin; use super::cursor::{JsonCursor, Page}; use super::move_module::MoveModule; use super::move_object::MoveObject; -use super::object::{ - self, Object, ObjectFilter, ObjectImpl, ObjectLookup, ObjectOwner, ObjectStatus, -}; +use super::object::{self, Object, ObjectFilter, ObjectImpl, ObjectOwner, ObjectStatus}; use super::owner::OwnerImpl; use super::stake::StakedSui; use super::sui_address::SuiAddress; @@ -19,10 +19,16 @@ use super::transaction_block::{self, TransactionBlock, TransactionBlockFilter}; use super::type_filter::ExactTypeFilter; use super::uint53::UInt53; use crate::consistency::ConsistentNamedCursor; +use crate::data::{DataLoader, Db, DbConnection, QueryExecutor}; use crate::error::Error; +use crate::types::sui_address::addr; use async_graphql::connection::{Connection, CursorType, Edge}; +use async_graphql::dataloader::Loader; use async_graphql::*; +use diesel::{BoolExpressionMethods, ExpressionMethods, JoinOnDsl, QueryDsl}; +use sui_indexer::schema::packages; use sui_package_resolver::{error::Error as PackageCacheError, Package as ParsedMovePackage}; +use sui_types::is_system_package; use sui_types::{move_package::MovePackage as NativeMovePackage, object::Data}; #[derive(Clone)] @@ -35,6 +41,21 @@ pub(crate) struct MovePackage { pub native: NativeMovePackage, } +/// Filter for a point query of a MovePackage, supporting querying different versions of a package +/// by their version. Note that different versions of the same user package exist at different IDs +/// to each other, so this is different from looking up the historical version of an object. +pub(crate) enum PackageLookup { + /// Get the package at the given address, if it was created before the given checkpoint. + ById { checkpoint_viewed_at: u64 }, + + /// Get the package whose original ID matches the storage ID of the package at the given + /// address, but whose version is `version`. + Versioned { + version: u64, + checkpoint_viewed_at: u64, + }, +} + /// Information used by a package to link to a specific version of its dependency. #[derive(SimpleObject)] struct Linkage { @@ -66,6 +87,18 @@ pub(crate) struct MovePackageDowncastError; pub(crate) type CModule = JsonCursor; +/// DataLoader key for fetching the storage ID of the (user) package that shares an original (aka +/// runtime) ID with the package stored at `package_id`, and whose version is `version`. +/// +/// Note that this is different from looking up the historical version of an object -- the query +/// returns the ID of the package (each version of a user package is at a different ID) -- and it +/// does not work for system packages (whose versions do all reside under the same ID). +#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] +struct PackageVersionKey { + address: SuiAddress, + version: u64, +} + /// A MovePackage is a kind of Move object that represents code that has been published on chain. /// It exposes information about its modules, type definitions, functions, and dependencies. #[Object] @@ -255,6 +288,22 @@ impl MovePackage { ObjectImpl(&self.super_).bcs().await } + /// Fetch another version of this package (the package that shares this package's original ID, + /// but has the specified `version`). + async fn package_at_version( + &self, + ctx: &Context<'_>, + version: u64, + ) -> Result> { + MovePackage::query( + ctx, + self.super_.address, + MovePackage::by_version(version, self.checkpoint_viewed_at_impl()), + ) + .await + .extend() + } + /// A representation of the module called `name` in this package, including the /// structs and functions it defines. async fn module(&self, name: String) -> Result> { @@ -416,11 +465,53 @@ impl MovePackage { } } + /// Look-up the package by its Storage ID, as of a given checkpoint. + pub(crate) fn by_id_at(checkpoint_viewed_at: u64) -> PackageLookup { + PackageLookup::ById { + checkpoint_viewed_at, + } + } + + /// Look-up a specific version of the package, identified by the storage ID of any version of + /// the package, and the desired version (the actual object loaded might be at a different + /// object ID). + pub(crate) fn by_version(version: u64, checkpoint_viewed_at: u64) -> PackageLookup { + PackageLookup::Versioned { + version, + checkpoint_viewed_at, + } + } + pub(crate) async fn query( ctx: &Context<'_>, address: SuiAddress, - key: ObjectLookup, + key: PackageLookup, ) -> Result, Error> { + let (address, key) = match key { + PackageLookup::ById { + checkpoint_viewed_at, + } => (address, Object::latest_at(checkpoint_viewed_at)), + + PackageLookup::Versioned { + version, + checkpoint_viewed_at, + } => { + if is_system_package(address) { + (address, Object::at_version(version, checkpoint_viewed_at)) + } else { + let DataLoader(loader) = &ctx.data_unchecked(); + let Some(translation) = loader + .load_one(PackageVersionKey { address, version }) + .await? + else { + return Ok(None); + }; + + (translation, Object::latest_at(checkpoint_viewed_at)) + } + } + }; + let Some(object) = Object::query(ctx, address, key).await? else { return Ok(None); }; @@ -431,6 +522,64 @@ impl MovePackage { } } +#[async_trait::async_trait] +impl Loader for Db { + type Value = SuiAddress; + type Error = Error; + + async fn load( + &self, + keys: &[PackageVersionKey], + ) -> Result, Error> { + use packages::dsl; + let other = diesel::alias!(packages as other); + + let id_versions: BTreeSet<_> = keys + .iter() + .map(|k| (k.address.into_vec(), k.version as i64)) + .collect(); + + let stored_packages: Vec<(Vec, i64, Vec)> = self + .execute(move |conn| { + conn.results(|| { + let mut query = dsl::packages + .inner_join(other.on(dsl::original_id.eq(other.field(dsl::original_id)))) + .select(( + dsl::package_id, + other.field(dsl::package_version), + other.field(dsl::package_id), + )) + .into_boxed(); + + for (id, version) in id_versions.iter().cloned() { + query = query.or_filter( + dsl::package_id + .eq(id) + .and(other.field(dsl::package_version).eq(version)), + ); + } + + query + }) + }) + .await + .map_err(|e| Error::Internal(format!("Failed to load packages: {e}")))?; + + let mut result = HashMap::new(); + for (id, version, other_id) in stored_packages { + result.insert( + PackageVersionKey { + address: addr(&id)?, + version: version as u64, + }, + addr(&other_id)?, + ); + } + + Ok(result) + } +} + impl TryFrom<&Object> for MovePackage { type Error = MovePackageDowncastError; diff --git a/crates/sui-graphql-rpc/src/types/object.rs b/crates/sui-graphql-rpc/src/types/object.rs index e9177a122057d..cd3e18709760c 100644 --- a/crates/sui-graphql-rpc/src/types/object.rs +++ b/crates/sui-graphql-rpc/src/types/object.rs @@ -17,6 +17,7 @@ use super::move_object::MoveObject; use super::move_package::MovePackage; use super::owner::OwnerImpl; use super::stake::StakedSui; +use super::sui_address::addr; use super::suins_registration::{DomainFormat, SuinsRegistration}; use super::transaction_block; use super::transaction_block::TransactionBlockFilter; @@ -181,6 +182,7 @@ pub(crate) struct AddressOwner { owner: Option, } +/// Filter for a point query of an Object. pub(crate) enum ObjectLookup { LatestAt { /// The checkpoint sequence number at which this was viewed at @@ -1459,15 +1461,6 @@ impl From<&Object> for OwnerImpl { } } -/// Parse a `SuiAddress` from its stored representation. Failure is an internal error: the -/// database should never contain a malformed address (containing the wrong number of bytes). -fn addr(bytes: impl AsRef<[u8]>) -> Result { - SuiAddress::from_bytes(bytes.as_ref()).map_err(|e| { - let bytes = bytes.as_ref().to_vec(); - Error::Internal(format!("Error deserializing address: {bytes:?}: {e}")) - }) -} - pub(crate) async fn deserialize_move_struct( move_object: &NativeMoveObject, resolver: &PackageResolver, diff --git a/crates/sui-graphql-rpc/src/types/query.rs b/crates/sui-graphql-rpc/src/types/query.rs index 10a6f4c9a8464..46334df30b1fd 100644 --- a/crates/sui-graphql-rpc/src/types/query.rs +++ b/crates/sui-graphql-rpc/src/types/query.rs @@ -12,6 +12,7 @@ use sui_sdk::SuiClient; use sui_types::transaction::{TransactionData, TransactionKind}; use sui_types::{gas_coin::GAS, transaction::TransactionDataAPI, TypeTag}; +use super::move_package::MovePackage; use super::suins_registration::NameService; use super::uint53::UInt53; use super::{ @@ -214,17 +215,37 @@ impl Query { version: Option, ) -> Result> { let Watermark { checkpoint, .. } = *ctx.data()?; + let key = match version { + Some(version) => Object::at_version(version.into(), checkpoint), + None => Object::latest_at(checkpoint), + }; + + Object::query(ctx, address, key).await.extend() + } + + /// The package corresponding to the given address at the (optionally) given version. + /// + /// When no version is given, the package is loaded directly from the address given. Otherwise, + /// the address is translated before loading to point to the package whose original ID matches + /// the package at `address`, but whose version is `version`. For non-system packages, this may + /// result in a different address than `address` because different versions of a package, + /// introduced by upgrades, exist at distinct addresses. + /// + /// Note that this interpretation of `version` is different from a historical object read (the + /// interpretation of `version` for the `object` query). + async fn package( + &self, + ctx: &Context<'_>, + address: SuiAddress, + version: Option, + ) -> Result> { + let Watermark { checkpoint, .. } = *ctx.data()?; + let key = match version { + Some(version) => MovePackage::by_version(version.into(), checkpoint), + None => MovePackage::by_id_at(checkpoint), + }; - match version { - Some(version) => { - Object::query(ctx, address, Object::at_version(version.into(), checkpoint)) - .await - .extend() - } - None => Object::query(ctx, address, Object::latest_at(checkpoint)) - .await - .extend(), - } + MovePackage::query(ctx, address, key).await.extend() } /// Look-up an Account by its SuiAddress. diff --git a/crates/sui-graphql-rpc/src/types/sui_address.rs b/crates/sui-graphql-rpc/src/types/sui_address.rs index 287bf0540e887..3a775e15064d4 100644 --- a/crates/sui-graphql-rpc/src/types/sui_address.rs +++ b/crates/sui-graphql-rpc/src/types/sui_address.rs @@ -3,18 +3,18 @@ use std::str::FromStr; +use crate::error::Error; use async_graphql::*; use move_core_types::account_address::AccountAddress; use serde::{Deserialize, Serialize}; use sui_types::base_types::{ObjectID, SuiAddress as NativeSuiAddress}; -use thiserror::Error; const SUI_ADDRESS_LENGTH: usize = 32; #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Copy)] pub(crate) struct SuiAddress([u8; SUI_ADDRESS_LENGTH]); -#[derive(Error, Debug, Eq, PartialEq)] +#[derive(thiserror::Error, Debug, Eq, PartialEq)] pub(crate) enum FromStrError { #[error("Invalid SuiAddress. Missing 0x prefix.")] NoPrefix, @@ -30,7 +30,7 @@ pub(crate) enum FromStrError { BadHex(char, usize), } -#[derive(Error, Debug, Eq, PartialEq)] +#[derive(thiserror::Error, Debug, Eq, PartialEq)] pub(crate) enum FromVecError { #[error("Expected SuiAddress with {} bytes, received {0}", SUI_ADDRESS_LENGTH)] WrongLength(usize), @@ -161,6 +161,15 @@ impl std::fmt::Display for SuiAddress { } } +/// Parse a `SuiAddress` from its stored representation. Failure is an internal error: the +/// database should never contain a malformed address (containing the wrong number of bytes). +pub(crate) fn addr(bytes: impl AsRef<[u8]>) -> Result { + SuiAddress::from_bytes(bytes.as_ref()).map_err(|e| { + let bytes = bytes.as_ref().to_vec(); + Error::Internal(format!("Error deserializing address: {bytes:?}: {e}")) + }) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__schema_sdl_export.snap b/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__schema_sdl_export.snap index c82e1181d7f6f..809354db88972 100644 --- a/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__schema_sdl_export.snap +++ b/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__schema_sdl_export.snap @@ -2172,6 +2172,11 @@ type MovePackage implements IObject & IOwner { """ bcs: Base64 """ + Fetch another version of this package (the package that shares this package's original ID, + but has the specified `version`). + """ + packageAtVersion(version: Int!): MovePackage + """ A representation of the module called `name` in this package, including the structs and functions it defines. """ @@ -3044,6 +3049,19 @@ type Query { """ object(address: SuiAddress!, version: UInt53): Object """ + The package corresponding to the given address at the (optionally) given version. + + When no version is given, the package is loaded directly from the address given. Otherwise, + the address is translated before loading to point to the package whose original ID matches + the package at `address`, but whose version is `version`. For non-system packages, this may + result in a different address than `address` because different versions of a package, + introduced by upgrades, exist at distinct addresses. + + Note that this interpretation of `version` is different from a historical object read (the + interpretation of `version` for the `object` query). + """ + package(address: SuiAddress!, version: UInt53): MovePackage + """ Look-up an Account by its SuiAddress. """ address(address: SuiAddress!): Address