diff --git a/crates/sui-indexer-alt-e2e-tests/tests/jsonrpc/objects/contents.exp b/crates/sui-indexer-alt-e2e-tests/tests/jsonrpc/objects/contents.exp new file mode 100644 index 00000000000000..4c2d4383c98189 --- /dev/null +++ b/crates/sui-indexer-alt-e2e-tests/tests/jsonrpc/objects/contents.exp @@ -0,0 +1,141 @@ +processed 8 tasks + +init: +A: object(0,0) + +task 1, lines 10-19: +//# publish +created: object(1,0) +mutated: object(0,1) +gas summary: computation_cost: 1000000, storage_cost: 4620800, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2, lines 21-23: +//# programmable --sender A --inputs @A +//> 0: test::mod::new(); +//> 1: TransferObjects([Result(0)], Input(0)) +created: object(2,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 2226800, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 3, lines 25-27: +//# programmable --sender A --inputs 42 @A +//> 0: SplitCoins(Gas, [Input(0)]); +//> 1: TransferObjects([Result(0)], Input(1)) +created: object(3,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 1976000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 4, line 29: +//# create-checkpoint +Checkpoint created: 1 + +task 5, lines 31-35: +//# run-jsonrpc +Response: { + "jsonrpc": "2.0", + "id": 0, + "result": { + "status": "VersionFound", + "details": { + "objectId": "0xb1d114770bfc9968a2ad3da9c6d5bcbf32e4bcf3d0bf3eba674df5d907a83e73", + "version": "1", + "digest": "Sgp59rDKZRKoSZ2ZJKKcc5YQ7MrhEHDxbZLqqFjywcq", + "content": { + "dataType": "package", + "disassembled": { + "mod": "// Move bytecode v6\nmodule b1d114770bfc9968a2ad3da9c6d5bcbf32e4bcf3d0bf3eba674df5d907a83e73.mod {\nuse 0000000000000000000000000000000000000000000000000000000000000002::object;\nuse 0000000000000000000000000000000000000000000000000000000000000002::tx_context;\n\nstruct Foo has store, key {\n\tid: UID\n}\n\npublic new(Arg0: &mut TxContext): Foo {\nB0:\n\t0: MoveLoc[0](Arg0: &mut TxContext)\n\t1: Call object::new(&mut TxContext): UID\n\t2: Pack[0](Foo)\n\t3: Ret\n}\n\n}\n" + } + }, + "bcs": { + "dataType": "package", + "id": "0xb1d114770bfc9968a2ad3da9c6d5bcbf32e4bcf3d0bf3eba674df5d907a83e73", + "version": 1, + "moduleMap": { + "mod": "oRzrCwYAAAAIAQAGAgYMAxIKBRwLBycvCFZACpYBBgycAQ0ABAEGAQcAAAwAAQIEAAIBAgAABQABAAEFAAMAAQcIAgEIAAABCAEDRm9vCVR4Q29udGV4dANVSUQCaWQDbW9kA25ldwZvYmplY3QKdHhfY29udGV4dLHRFHcL/Jlooq09qcbVvL8y5Lzz0L8+umdN9dkHqD5zAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAgEDCAEAAQAAAgQLABEBEgACAA==" + }, + "typeOriginTable": [ + { + "module_name": "mod", + "datatype_name": "Foo", + "package": "0xb1d114770bfc9968a2ad3da9c6d5bcbf32e4bcf3d0bf3eba674df5d907a83e73" + } + ], + "linkageTable": { + "0x0000000000000000000000000000000000000000000000000000000000000001": { + "upgraded_id": "0x0000000000000000000000000000000000000000000000000000000000000001", + "upgraded_version": 1 + }, + "0x0000000000000000000000000000000000000000000000000000000000000002": { + "upgraded_id": "0x0000000000000000000000000000000000000000000000000000000000000002", + "upgraded_version": 1 + } + } + } + } + } +} + +task 6, lines 37-41: +//# run-jsonrpc +Response: { + "jsonrpc": "2.0", + "id": 1, + "result": { + "status": "VersionFound", + "details": { + "objectId": "0x5736caa914301b5f6bc2734fdd6ef4c0097ebf6f0b346ec0ce1119f3a86c8c37", + "version": "2", + "digest": "6pd5G7cgCu3ShFTxpyo7xJTsq5EwzbAnrmfNeR55roUH", + "content": { + "dataType": "moveObject", + "type": "0xb1d114770bfc9968a2ad3da9c6d5bcbf32e4bcf3d0bf3eba674df5d907a83e73::mod::Foo", + "hasPublicTransfer": true, + "fields": { + "id": { + "id": "0x5736caa914301b5f6bc2734fdd6ef4c0097ebf6f0b346ec0ce1119f3a86c8c37" + } + } + }, + "bcs": { + "dataType": "moveObject", + "type": "0xb1d114770bfc9968a2ad3da9c6d5bcbf32e4bcf3d0bf3eba674df5d907a83e73::mod::Foo", + "hasPublicTransfer": true, + "version": 2, + "bcsBytes": "VzbKqRQwG19rwnNP3W70wAl+v28LNG7AzhEZ86hsjDc=" + } + } + } +} + +task 7, lines 43-47: +//# run-jsonrpc +Response: { + "jsonrpc": "2.0", + "id": 2, + "result": { + "status": "VersionFound", + "details": { + "objectId": "0xf2a6833ec5d2dd77e656a3fe62bdd4e4609b23fa0739312e384fdbb06080155e", + "version": "3", + "digest": "AeAb1PukmXSZNcUMrQmqqWeEwSwyoKXhSGogxN37Wdym", + "content": { + "dataType": "moveObject", + "type": "0x2::coin::Coin<0x2::sui::SUI>", + "hasPublicTransfer": true, + "fields": { + "balance": "42", + "id": { + "id": "0xf2a6833ec5d2dd77e656a3fe62bdd4e4609b23fa0739312e384fdbb06080155e" + } + } + }, + "bcs": { + "dataType": "moveObject", + "type": "0x2::coin::Coin<0x2::sui::SUI>", + "hasPublicTransfer": true, + "version": 3, + "bcsBytes": "8qaDPsXS3XfmVqP+Yr3U5GCbI/oHOTEuOE/bsGCAFV4qAAAAAAAAAA==" + } + } + } +} diff --git a/crates/sui-indexer-alt-e2e-tests/tests/jsonrpc/objects/contents.move b/crates/sui-indexer-alt-e2e-tests/tests/jsonrpc/objects/contents.move new file mode 100644 index 00000000000000..5f1fb379cb3a57 --- /dev/null +++ b/crates/sui-indexer-alt-e2e-tests/tests/jsonrpc/objects/contents.move @@ -0,0 +1,47 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --protocol-version 70 --accounts A --addresses test=0x0 --simulator + +// 1. View the contents of a package +// 2. View the contents of an arbitrary object +// 3. View the contents of a coin + +//# publish +module test::mod { + public struct Foo has key, store { + id: UID, + } + + public fun new(ctx: &mut TxContext): Foo { + Foo { id: object::new(ctx) } + } +} + +//# programmable --sender A --inputs @A +//> 0: test::mod::new(); +//> 1: TransferObjects([Result(0)], Input(0)) + +//# programmable --sender A --inputs 42 @A +//> 0: SplitCoins(Gas, [Input(0)]); +//> 1: TransferObjects([Result(0)], Input(1)) + +//# create-checkpoint + +//# run-jsonrpc +{ + "method": "sui_tryGetPastObject", + "params": ["@{obj_1_0}", 1, { "showContent": true, "showBcs": true }] +} + +//# run-jsonrpc +{ + "method": "sui_tryGetPastObject", + "params": ["@{obj_2_0}", 2, { "showContent": true, "showBcs": true }] +} + +//# run-jsonrpc +{ + "method": "sui_tryGetPastObject", + "params": ["@{obj_3_0}", 3, { "showContent": true, "showBcs": true }] +} diff --git a/crates/sui-indexer-alt-jsonrpc/src/api/objects/response.rs b/crates/sui-indexer-alt-jsonrpc/src/api/objects/response.rs index 085043eeff7863..b180b5ff1555f1 100644 --- a/crates/sui-indexer-alt-jsonrpc/src/api/objects/response.rs +++ b/crates/sui-indexer-alt-jsonrpc/src/api/objects/response.rs @@ -2,16 +2,25 @@ // SPDX-License-Identifier: Apache-2.0 use anyhow::Context as _; +use futures::future::OptionFuture; +use move_core_types::annotated_value::MoveTypeLayout; use sui_json_rpc_types::{ - SuiObjectData, SuiObjectDataOptions, SuiObjectRef, SuiPastObjectResponse, + SuiData, SuiObjectData, SuiObjectDataOptions, SuiObjectRef, SuiParsedData, + SuiPastObjectResponse, SuiRawData, }; use sui_types::{ base_types::{ObjectID, ObjectType, SequenceNumber}, digests::ObjectDigest, - object::Object, + object::{Data, Object}, + TypeTag, }; +use tokio::join; -use crate::{context::Context, data::objects::VersionedObjectKey, error::RpcError}; +use crate::{ + context::Context, + data::objects::VersionedObjectKey, + error::{rpc_bail, RpcError}, +}; /// Fetch the necessary data from the stores in `ctx` and transform it to build a response for a /// past object identified by its ID and version, according to the response `options`. @@ -38,13 +47,14 @@ pub(super) async fn past_object( })); }; - Ok(SuiPastObjectResponse::VersionFound(object( - object_id, version, bytes, options, - )?)) + Ok(SuiPastObjectResponse::VersionFound( + object(ctx, object_id, version, bytes, options).await?, + )) } /// Extract a representation of the object from its stored form, according to its response options. -fn object( +async fn object( + ctx: &Context, object_id: ObjectID, version: SequenceNumber, bytes: &[u8], @@ -59,6 +69,26 @@ fn object( .then(|| object.previous_transaction.clone()); let storage_rebate = options.show_storage_rebate.then(|| object.storage_rebate); + let content: OptionFuture<_> = options + .show_content + .then(|| object_data::(ctx, &object)) + .into(); + + let bcs: OptionFuture<_> = options + .show_bcs + .then(|| object_data::(ctx, &object)) + .into(); + + let (content, bcs) = join!(content, bcs); + + let content = content + .transpose() + .context("Failed to deserialize object content")?; + + let bcs = bcs + .transpose() + .context("Failed to deserialize object to BCS")?; + Ok(SuiObjectData { object_id, version, @@ -68,7 +98,37 @@ fn object( previous_transaction, storage_rebate, display: None, - content: None, - bcs: None, + content, + bcs, + }) +} + +/// Extract the contents of an object, in a format chosen by the `D` type parameter. +/// This operaton can fail if it's not possible to get the type layout for the object's type. +async fn object_data(ctx: &Context, object: &Object) -> Result { + Ok(match object.data.clone() { + Data::Package(move_package) => D::try_from_package(move_package)?, + + Data::Move(move_object) => { + let type_: TypeTag = move_object.type_().clone().into(); + let MoveTypeLayout::Struct(layout) = ctx + .package_resolver() + .type_layout(type_.clone()) + .await + .with_context(|| { + format!( + "Failed to resolve type layout for {}", + type_.to_canonical_display(/*with_prefix */ true) + ) + })? + else { + rpc_bail!( + "Type {} is not a struct", + type_.to_canonical_display(/*with_prefix */ true) + ); + }; + + D::try_from_object(move_object, *layout)? + } }) }