Skip to content

Commit

Permalink
[ts-sdk][cli] add util functions in ts-sdk for using CLI to get compi…
Browse files Browse the repository at this point in the history
…ledModules
  • Loading branch information
666lcz committed Sep 9, 2022
1 parent cf5584f commit f0d42d1
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 71 deletions.
11 changes: 9 additions & 2 deletions crates/sui-open-rpc/src/generate_json_rpc_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use move_package::BuildConfig;
use pretty_assertions::assert_str_eq;
use serde::Serialize;
use serde_json::{json, Map, Value};
use sui::client_commands::SuiClientPublishCommandResult;
use sui_json_rpc::api::EventReadApiOpenRpc;
use sui_types::messages::Transaction;

Expand Down Expand Up @@ -221,10 +222,13 @@ async fn create_package_object_response(
build_config,
gas: None,
gas_budget: 10000,
no_execute: false,
}
.execute(context)
.await?;
if let SuiClientCommandResult::Publish(response) = result {
if let SuiClientCommandResult::Publish(SuiClientPublishCommandResult::Executed(response)) =
result
{
let object = context
.client
.read_api()
Expand Down Expand Up @@ -308,10 +312,13 @@ async fn create_hero_response(
gas: None,
build_config,
gas_budget: 10000,
no_execute: false,
}
.execute(context)
.await?;
if let SuiClientCommandResult::Publish(response) = result {
if let SuiClientCommandResult::Publish(SuiClientPublishCommandResult::Executed(response)) =
result
{
let publish_resp = response.parsed_data.unwrap().to_publish_response().unwrap();
let package_id = publish_resp.package.object_id;
let game_info = publish_resp
Expand Down
68 changes: 47 additions & 21 deletions crates/sui/src/client_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ pub enum SuiClientCommands {
/// Gas budget for running module initializers
#[clap(long)]
gas_budget: u64,

/// If set to true, the command will skip executing the transaction
#[clap(long, action(ArgAction::SetTrue))]
no_execute: bool,
},

/// Call Move function
Expand Down Expand Up @@ -285,22 +289,31 @@ impl SuiClientCommands {
gas,
build_config,
gas_budget,
no_execute,
} => {
let sender = context.try_get_object_owner(&gas).await?;
let sender = sender.unwrap_or(context.active_address()?);

let compiled_modules = build_move_package_to_bytes(&package_path, build_config)?;
let data = context
.client
.transaction_builder()
.publish(sender, compiled_modules, gas, gas_budget)
.await?;
let signature = context.keystore.sign(&sender, &data.to_bytes())?;
let response = context
.execute_transaction(Transaction::new(data, signature))
.await?;

SuiClientCommandResult::Publish(response)
if no_execute {
SuiClientCommandResult::Publish(SuiClientPublishCommandResult::CompiledModules(
compiled_modules,
))
} else {
let data = context
.client
.transaction_builder()
.publish(sender, compiled_modules, gas, gas_budget)
.await?;
let signature = context.keystore.sign(&sender, &data.to_bytes())?;
let response = context
.execute_transaction(Transaction::new(data, signature))
.await?;

SuiClientCommandResult::Publish(SuiClientPublishCommandResult::Executed(
response,
))
}
}

SuiClientCommands::Object { id } => {
Expand Down Expand Up @@ -713,16 +726,21 @@ impl Display for SuiClientCommandResult {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut writer = String::new();
match self {
SuiClientCommandResult::Publish(response) => {
write!(
writer,
"{}",
write_cert_and_effects(&response.certificate, &response.effects)?
)?;
if let Some(parsed_resp) = &response.parsed_data {
writeln!(writer, "{}", parsed_resp)?;
SuiClientCommandResult::Publish(resp) => match resp {
SuiClientPublishCommandResult::CompiledModules(response) => {
writeln!(writer, "{:?}", response)?;
}
}
SuiClientPublishCommandResult::Executed(response) => {
write!(
writer,
"{}",
write_cert_and_effects(&response.certificate, &response.effects)?
)?;
if let Some(parsed_resp) = &response.parsed_data {
writeln!(writer, "{}", parsed_resp)?;
}
}
},
SuiClientCommandResult::Object(object_read) => {
let object = unwrap_err_to_string(|| Ok(object_read.object()?));
writeln!(writer, "{}", object)?;
Expand Down Expand Up @@ -932,7 +950,7 @@ impl SuiClientCommandResult {
#[derive(Serialize)]
#[serde(untagged)]
pub enum SuiClientCommandResult {
Publish(SuiTransactionResponse),
Publish(SuiClientPublishCommandResult),
Object(GetObjectDataResponse),
Call(SuiCertifiedTransaction, SuiTransactionEffects),
Transfer(
Expand All @@ -954,6 +972,14 @@ pub enum SuiClientCommandResult {
CreateExampleNFT(GetObjectDataResponse),
}

#[allow(clippy::large_enum_variant)]
#[derive(Serialize)]
#[serde(untagged)]
pub enum SuiClientPublishCommandResult {
CompiledModules(Vec<Vec<u8>>),
Executed(SuiTransactionResponse),
}

#[derive(Serialize, Clone, Debug)]
pub struct SwitchResponse {
/// Active address
Expand Down
40 changes: 24 additions & 16 deletions crates/sui/src/unit_tests/cli_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use anyhow::anyhow;
use move_package::BuildConfig;
use serde_json::json;

use sui::client_commands::SwitchResponse;
use sui::client_commands::{SuiClientPublishCommandResult, SwitchResponse};
use sui::{
client_commands::{SuiClientCommandResult, SuiClientCommands, WalletContext},
config::SuiClientConfig,
Expand Down Expand Up @@ -336,15 +336,19 @@ async fn test_move_call_args_linter_command() -> Result<(), anyhow::Error> {
build_config,
gas: Some(gas_obj_id),
gas_budget: 1000,
no_execute: false,
}
.execute(&mut context)
.await?;
let package = if let SuiClientCommandResult::Publish(response) = resp {
let publish_resp = response.parsed_data.unwrap().to_publish_response().unwrap();
publish_resp.package.object_id
} else {
unreachable!("Invalid response");
};
let package =
if let SuiClientCommandResult::Publish(SuiClientPublishCommandResult::Executed(response)) =
resp
{
let publish_resp = response.parsed_data.unwrap().to_publish_response().unwrap();
publish_resp.package.object_id
} else {
unreachable!("Invalid response");
};

// Sync client to retrieve objects from the network.
SuiClientCommands::SyncClientState {
Expand Down Expand Up @@ -510,22 +514,26 @@ async fn test_package_publish_command() -> Result<(), anyhow::Error> {
build_config,
gas: Some(gas_obj_id),
gas_budget: 1000,
no_execute: false,
}
.execute(&mut context)
.await?;

// Print it out to CLI/logs
resp.print(true);

let (package, created_obj) = if let SuiClientCommandResult::Publish(response) = resp {
let publish_resp = response.parsed_data.unwrap().to_publish_response().unwrap();
(
publish_resp.package,
publish_resp.created_objects[0].reference.clone(),
)
} else {
unreachable!("Invalid response");
};
let (package, created_obj) =
if let SuiClientCommandResult::Publish(SuiClientPublishCommandResult::Executed(response)) =
resp
{
let publish_resp = response.parsed_data.unwrap().to_publish_response().unwrap();
(
publish_resp.package,
publish_resp.created_objects[0].reference.clone(),
)
} else {
unreachable!("Invalid response");
};

// Check the objects
let resp = SuiClientCommands::Object {
Expand Down
22 changes: 13 additions & 9 deletions sdk/typescript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,25 +165,29 @@ console.log('moveCallTxn', moveCallTxn);
To publish a package:

```typescript
import { Ed25519Keypair, JsonRpcProvider, RawSigner } from '@mysten/sui.js';
import {
Ed25519Keypair,
JsonRpcProvider,
RawSigner,
getCompiledModulesInBase64,
} from '@mysten/sui.js';
import * as fs from 'fs/promises';
// Generate a new Keypair
const keypair = new Ed25519Keypair();
const signer = new RawSigner(
keypair,
new JsonRpcProvider('https://gateway.devnet.sui.io:443')
);
const bytecode = await fs.readFile('path/to/project/build/project_name/bytecode_modules/module_name.mv', 'base64');
const publishTxn = await signer.publish(
{
compiledModules: [bytecode.toString()],
gasBudget: 1000
}
const compiledModules = getCompiledModulesInBase64(
'<SUI REPO>/sui_programmability/examples/defi'
);
const publishTxn = await signer.publish({
compiledModules,
gasBudget: 10000,
});
console.log('publishTxn', publishTxn);
```


Alternatively, a Secp256k1 can be initiated:

```typescript
Expand All @@ -195,4 +199,4 @@ const signer = new RawSigner(
keypair,
new JsonRpcProvider('https://gateway.devnet.sui.io:443')
);
```
```
2 changes: 2 additions & 0 deletions sdk/typescript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ export * from './signers/signer-with-provider';

export * from './types';
export * from './types/index.guard';

export * from './utils/publish';
4 changes: 4 additions & 0 deletions sdk/typescript/src/signers/signer-with-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ export abstract class SignerWithProvider implements Signer {
return await this.signAndExecuteTransaction(txBytes);
}

/**
* Publish a Move package on chain
* @param transaction See {@link PublishTransaction}
*/
async publish(
transaction: PublishTransaction
): Promise<SuiTransactionResponse> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,30 +49,26 @@ export interface MoveCallTransaction {
gasBudget: number;
}

/**
* Transaction type used for publishing Move modules to the Sui.
*
* Use the util methods defined in [utils/publish.ts](../../utils/publish.ts)
* to get `compiledModules` bytes by leveraging the sui
* command line tool.
*
* ```
* // If you are using `RpcTxnDataSerializer`,
* const { getCompiledModulesInBase64 } = require('@mysten/sui.js');
* const compiledModules = getCompiledModulesInBase64('<SUI REPO>/sui_programmability/examples/defi');
*
* // If you are using `LocalTxnDataSerializer`,
* const { getCompiledModules } = require('@mysten/sui.js');
* const compiledModules = getCompiledModules('<SUI REPO>/sui_programmability/examples/defi');
* // ... publish logic ...
* ```
*
*/
export interface PublishTransaction {
/**
* Transaction type used for publishing Move modules to the Sui.
* Should be already compiled using `sui-move`, example:
* ```
* $ sui move build
* $ cat build/project_name/bytecode_modules/module.mv
* ```
* In JS:
*
* ```
* // If you are using `RpcTxnDataSerializer`,
* let file = fs.readFileSync('./move/build/project_name/bytecode_modules/module.mv', 'base64');
* let compiledModules = [file.toString()]
*
* // If you are using `LocalTxnDataSerializer`,
* let file = fs.readFileSync('./move/build/project_name/bytecode_modules/module.mv');
* let modules = [ Array.from(file) ];
*
* // ... publish logic ...
* ```
*
* Each module should be represented as a sequence of bytes.
*/
compiledModules: ArrayLike<string> | ArrayLike<ArrayLike<number>>;
gasPayment?: ObjectId;
gasBudget: number;
Expand Down
48 changes: 48 additions & 0 deletions sdk/typescript/src/utils/publish.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { Base64DataBuffer } from '../serialization/base64';

/**
* Use the sui Command line tool to verify, compile and compute
* the topological ordering of the module dependencies within
* the package. This method is only available in NodeJS environment.
*
* Use this method with the local-txn-data-serializer.
*
* @param packagePath path to the Move package to be published
* @param cliPath path to the sui CLI executable. Follow
* {@link https://docs.sui.io/devnet/build/install} to install
* @returns an array of compiled modules in bytes
*/
export function getCompiledModules(
packagePath: String,
cliPath: String = 'sui'
): number[][] {
const { execSync } = require('child_process');
const modules = execSync(
// gas-budget is a placeholder since there's no on-chain execution and thus no gas cost
`${cliPath} client publish --path ${packagePath} --gas-budget 30000 --no-execute`,
{ encoding: 'utf-8' }
);
return JSON.parse(modules);
}

/**
* Same as getCompiledModules except the result is transformed to
* an array of base64 strings. Use this method with the rpc-txn-data-serializer.
*
* @param packagePath path to the package to be published
* @param cliPath path to the sui CLI executable. Follow
* https://docs.sui.io/devnet/build/install to install
* @returns an array of compiled modules in bytes
*/
export function getCompiledModulesInBase64(
packagePath: String,
cliPath: String = 'sui'
): String[] {
const modules = getCompiledModules(packagePath, cliPath);
return modules.map((m) =>
new Base64DataBuffer(Uint8Array.from(m)).toString()
);
}

0 comments on commit f0d42d1

Please sign in to comment.