Skip to content

Commit

Permalink
Merge pull request #1007 from oasisprotocol/matevz/feature/cli-dump-w…
Browse files Browse the repository at this point in the history
…asm-code

cli: Add support for dumping deployed WASM of contracts
  • Loading branch information
matevz authored Jul 4, 2022
2 parents d12b712 + a238c92 commit b4efbe8
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 4 deletions.
32 changes: 31 additions & 1 deletion cli/cmd/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ var (

contractsShowCmd = &cobra.Command{
Use: "show <instance-id>",
Short: "Show information about a deployed contract",
Short: "Show information about instantiated contract",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
cfg := cliConfig.Global()
Expand Down Expand Up @@ -94,6 +94,34 @@ var (
},
}

contractsDumpCodeCmd = &cobra.Command{
Use: "dump-code <code-id>",
Short: "Dump WebAssembly smart contract code",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
cfg := cliConfig.Global()
npa := common.GetNPASelection(cfg)
strCodeID := args[0]

if npa.ParaTime == nil {
cobra.CheckErr("no paratimes configured")
}

codeID, err := strconv.ParseUint(strCodeID, 10, 64)
cobra.CheckErr(err)

ctx := context.Background()
conn, err := connection.Connect(ctx, npa.Network)
cobra.CheckErr(err)

// Fetch WASM contract code, if supported.
codeStorage, err := conn.Runtime(npa.ParaTime).Contracts.CodeStorage(ctx, client.RoundLatest, contracts.CodeID(codeID))
cobra.CheckErr(err)

os.Stdout.Write(codeStorage.Code)
},
}

contractsUploadCmd = &cobra.Command{
Use: "upload <contract.wasm> [--instantiate-policy POLICY]",
Short: "Upload WebAssembly smart contract",
Expand Down Expand Up @@ -340,6 +368,7 @@ func init() {
contractsShowCmd.Flags().AddFlagSet(common.SelectorFlags)

contractsShowCodeCmd.Flags().AddFlagSet(common.SelectorFlags)
contractsDumpCodeCmd.Flags().AddFlagSet(common.SelectorFlags)

contractsUploadFlags := flag.NewFlagSet("", flag.ContinueOnError)
contractsUploadFlags.StringVar(&contractsInstantiatePolicy, "instantiate-policy", "everyone", "contract instantiation policy")
Expand All @@ -365,6 +394,7 @@ func init() {

contractsCmd.AddCommand(contractsShowCmd)
contractsCmd.AddCommand(contractsShowCodeCmd)
contractsCmd.AddCommand(contractsDumpCodeCmd)
contractsCmd.AddCommand(contractsUploadCmd)
contractsCmd.AddCommand(contractsInstantiateCmd)
contractsCmd.AddCommand(contractsCallCmd)
Expand Down
14 changes: 14 additions & 0 deletions client-sdk/go/modules/contracts/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const (

// Queries.
methodCode = "contracts.Code"
methodCodeStorage = "contracts.CodeStorage"
methodInstance = "contracts.Instance"
methodInstanceStorage = "contracts.InstanceStorage"
methodPublicKey = "contracts.PublicKey"
Expand Down Expand Up @@ -76,6 +77,9 @@ type V1 interface {
// Code queries the given code information.
Code(ctx context.Context, round uint64, id CodeID) (*Code, error)

// CodeStorage queries the given code's storage.
CodeStorage(ctx context.Context, round uint64, id CodeID) (*CodeStorageQueryResult, error)

// Instance queries the given instance information.
Instance(ctx context.Context, round uint64, id InstanceID) (*Instance, error)

Expand Down Expand Up @@ -171,6 +175,16 @@ func (a *v1) Code(ctx context.Context, round uint64, id CodeID) (*Code, error) {
return &code, nil
}

// Implements V1.
func (a *v1) CodeStorage(ctx context.Context, round uint64, id CodeID) (*CodeStorageQueryResult, error) {
var rsp CodeStorageQueryResult
err := a.rc.Query(ctx, round, methodCodeStorage, &CodeStorageQuery{ID: id}, &rsp)
if err != nil {
return nil, err
}
return &rsp, nil
}

// Implements V1.
func (a *v1) Instance(ctx context.Context, round uint64, id InstanceID) (*Instance, error) {
var instance Instance
Expand Down
12 changes: 12 additions & 0 deletions client-sdk/go/modules/contracts/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,18 @@ type CodeQuery struct {
ID CodeID `json:"id"`
}

// CodeStorageQuery is the body of the contracts.CodeStorage query.
type CodeStorageQuery struct {
// ID is the code identifier.
ID CodeID `json:"id"`
}

// CodeStorageQueryResult is the result of the contracts.CodeStorage query.
type CodeStorageQueryResult struct {
// Code is the stored contract code.
Code []byte `json:"code"`
}

// InstanceQuery is the body of the contracts.Instance query.
type InstanceQuery struct {
// ID is the instance identifier.
Expand Down
6 changes: 6 additions & 0 deletions client-sdk/ts-web/rt/src/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const METHOD_CALL = 'contracts.Call';
export const METHOD_UPGRADE = 'contracts.Upgrade';
// Queries.
export const METHOD_CODE = 'contracts.Code';
export const METHOD_CODE_STORAGE = 'contracts.CodeStorage';
export const METHOD_INSTANCE = 'contracts.Instance';
export const METHOD_INSTANCE_STORAGE = 'contracts.InstanceStorage';
export const METHOD_PUBLIC_KEY = 'contracts.PublicKey';
Expand Down Expand Up @@ -64,6 +65,11 @@ export class Wrapper extends wrapper.Base {
queryCode() {
return this.query<types.ContractsCodeQuery, types.ContractsCode>(METHOD_CODE);
}
queryCodeStorage() {
return this.query<types.ContractsCodeStorageQuery, types.ContractsCodeStorageQueryResult>(
METHOD_CODE_STORAGE,
);
}
queryInstance() {
return this.query<types.ContractsInstanceQuery, types.ContractsInstance>(METHOD_INSTANCE);
}
Expand Down
17 changes: 17 additions & 0 deletions client-sdk/ts-web/rt/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,23 @@ export interface ContractsCode {
instantiate_policy: ContractsPolicy;
}

/**
* Code storage information query.
*/
export interface ContractsCodeStorageQuery {
/**
* Code identifier.
*/
id: oasis.types.longnum;
}

export interface ContractsCodeStorageQueryResult {
/**
* Stored contract code.
*/
code: Uint8Array;
}

/**
* Instance information query.
*/
Expand Down
4 changes: 2 additions & 2 deletions runtime-sdk/modules/contracts/src/code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ static CODE_CACHE: Lazy<Mutex<lru::LruCache<Hash, Vec<u8>>>> =
Lazy::new(|| Mutex::new(lru::LruCache::new(128)));

impl<Cfg: Config> Module<Cfg> {
/// Stores specified code information.
/// Loads code with the specified code identifier.
pub fn load_code<C: Context>(ctx: &mut C, code_info: &types::Code) -> Result<Vec<u8>, Error> {
let mut cache = CODE_CACHE.lock().unwrap();
if let Some(code) = cache.get(&code_info.hash) {
return Ok(code.clone());
}

// TODO: Spport local untrusted cache to avoid storage queries.
// TODO: Support local untrusted cache to avoid storage queries.
let mut store = storage::PrefixStore::new(ctx.runtime_state(), &MODULE_NAME);
let code_store = storage::PrefixStore::new(&mut store, &state::CODE);
let code = code_store
Expand Down
13 changes: 12 additions & 1 deletion runtime-sdk/modules/contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ impl<Cfg: Config> Module<Cfg> {
let mut store = storage::PrefixStore::new(ctx.runtime_state(), &MODULE_NAME);
let code_info_store =
storage::TypedStore::new(storage::PrefixStore::new(&mut store, &state::CODE_INFO));
let code_info: types::Code = code_info_store
let code_info = code_info_store
.get(code_id.to_storage_key())
.ok_or_else(|| Error::CodeNotFound(code_id.as_u64()))?;

Expand Down Expand Up @@ -646,6 +646,17 @@ impl<Cfg: Config> Module<Cfg> {
Self::load_code_info(ctx, args.id)
}

#[handler(query = "contracts.CodeStorage")]
pub fn query_code_storage<C: Context>(
ctx: &mut C,
args: types::CodeStorageQuery,
) -> Result<types::CodeStorageQueryResult, Error> {
let code_info = Self::load_code_info(ctx, args.id)?;
let code = Self::load_code(ctx, &code_info)?;

Ok(types::CodeStorageQueryResult { code })
}

#[handler(query = "contracts.Instance")]
pub fn query_instance<C: Context>(
ctx: &mut C,
Expand Down
12 changes: 12 additions & 0 deletions runtime-sdk/modules/contracts/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,18 @@ fn test_hello_contract_call() {
assert_eq!(result.id, 0.into());
assert_eq!(result.abi, types::ABI::OasisV1);

// Test code storage query.
let result = Contracts::query_code_storage(&mut ctx, types::CodeStorageQuery { id: 0.into() })
.expect("code storage query should succeed");
// Stored code is the original code plus some injected gas billing calls.
assert_eq!(result.code.len() >= HELLO_CONTRACT_CODE.len(), true);

// Invalid code queries should fail.
Contracts::query_code(&mut ctx, types::CodeQuery { id: 9999.into() })
.expect_err("invalid code query should fail");
Contracts::query_code_storage(&mut ctx, types::CodeStorageQuery { id: 9999.into() })
.expect_err("invalid code storage query should fail");

// Test storage query for the counter key.
let result = Contracts::query_instance_storage(
&mut ctx,
Expand Down
14 changes: 14 additions & 0 deletions runtime-sdk/modules/contracts/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,20 @@ pub struct CodeQuery {
pub id: CodeId,
}

/// Code storage information query.
#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
pub struct CodeStorageQuery {
/// Code identifier.
pub id: CodeId,
}

/// Code storage query result.
#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
pub struct CodeStorageQueryResult {
/// Stored contract code.
pub code: Vec<u8>,
}

/// Instance information query.
#[derive(Clone, Debug, cbor::Encode, cbor::Decode)]
pub struct InstanceQuery {
Expand Down

0 comments on commit b4efbe8

Please sign in to comment.