From 352983e9002311c31d68952473d12a3c25b7b33c Mon Sep 17 00:00:00 2001 From: kiruse <26940742+Kiruse@users.noreply.github.com> Date: Wed, 7 Dec 2022 19:22:14 +0100 Subject: [PATCH] fix copying raw bytes (ArrayBuffer & views); also introduce NEVER_IMMUTIFY which prevents the storage system from converting specific objects to Immutable collections --- package.json | 2 +- src/modules/wasm.ts | 8 ++++- src/store/transactional.ts | 62 ++++++++++++++++++++++++++++++++------ src/types.ts | 2 ++ src/util.ts | 2 ++ 5 files changed, 64 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 0f53059..a49f198 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@terran-one/cw-simulate", - "version": "2.7.1", + "version": "2.7.2", "description": "Mock blockchain environment for simulating CosmWasm interactions", "main": "dist/index.js", "typings": "dist/index.d.ts", diff --git a/src/modules/wasm.ts b/src/modules/wasm.ts index 623a6ba..50741a2 100644 --- a/src/modules/wasm.ts +++ b/src/modules/wasm.ts @@ -31,7 +31,7 @@ import { import { Map } from 'immutable'; import { Err, Ok, Result } from 'ts-results'; import { fromBinary, fromRustResult, toBinary } from '../util'; -import { Transactional, TransactionalLens } from '../store/transactional'; +import { NEVER_IMMUTIFY, Transactional, TransactionalLens } from '../store/transactional'; function numberToBigEndianUint64(n: number): Uint8Array { const buffer = new ArrayBuffer(8); @@ -310,6 +310,7 @@ export class WasmModule { if ('error' in response) { let result = Err(response.error); trace.push({ + [NEVER_IMMUTIFY]: true, type: 'instantiate' as 'instantiate', contractAddress, msg: instantiateMsg, @@ -350,6 +351,7 @@ export class WasmModule { ); trace.push({ + [NEVER_IMMUTIFY]: true, type: 'instantiate' as 'instantiate', contractAddress, msg: instantiateMsg, @@ -420,6 +422,7 @@ export class WasmModule { let result = Err(response.error); trace.push({ + [NEVER_IMMUTIFY]: true, type: 'execute' as 'execute', contractAddress, msg: executeMsg, @@ -462,6 +465,7 @@ export class WasmModule { ); trace.push({ + [NEVER_IMMUTIFY]: true, type: 'execute' as 'execute', contractAddress, msg: executeMsg, @@ -613,6 +617,7 @@ export class WasmModule { let result = Err(response.error); trace.push({ + [NEVER_IMMUTIFY]: true, type: 'reply' as 'reply', contractAddress, env: this.getExecutionEnv(contractAddress), @@ -656,6 +661,7 @@ export class WasmModule { ); trace.push({ + [NEVER_IMMUTIFY]: true, type: 'reply' as 'reply', contractAddress, msg: replyMsg, diff --git a/src/store/transactional.ts b/src/store/transactional.ts index 7fb15fd..4d0b01e 100644 --- a/src/store/transactional.ts +++ b/src/store/transactional.ts @@ -1,7 +1,13 @@ -import { fromJS, isCollection, isList, isMap, List, Map } from "immutable"; +import { isCollection, isList, isMap, List, Map } from "immutable"; import { Ok, Result } from "ts-results"; +import { isArrayLike } from "../util"; + +// NEVER_IMMUTIFY is a string because that's easily serializable with different algorithms - symbols are not +export type NeverImmutify = typeof NEVER_IMMUTIFY; +export const NEVER_IMMUTIFY = '__NEVER_IMMUTIFY__'; type Primitive = boolean | number | bigint | string | null | undefined | symbol; +type NoImmutify = Primitive | ArrayBuffer | ArrayBufferView | { [NEVER_IMMUTIFY]: any }; type Prefix = [P, ...T]; type First = T extends Prefix ? F : never; @@ -18,7 +24,7 @@ type Lens = : T; type Immutify = - T extends Primitive + T extends NoImmutify ? T : T extends ArrayLike ? List> @@ -89,7 +95,7 @@ export class TransactionalLens { initialize(data: M) { this.db.tx(update => { - const coll = fromJS(data); + const coll = toImmutable(data); if (!isCollection(coll)) throw new Error('Not an Immutable.Map'); update(curr => curr.setIn([...this.prefix], coll)); return Ok(undefined); @@ -112,8 +118,7 @@ export class TransactionalLens { return this.db.tx(update => { const setter: LensSetter =

(...path: P) => (value: Lens | Immutify>) => { - const v = isCollection(value) ? value : fromJS(value); - update(curr => curr.setIn([...this.prefix, ...path], v)); + update(curr => curr.setIn([...this.prefix, ...path], toImmutable(value))); } const deleter: LensDeleter =

(...path: P) => { update(curr => curr.deleteIn([...this.prefix, ...path])); @@ -129,14 +134,52 @@ export class TransactionalLens { get data() { return this.db.data.getIn([...this.prefix]) as Immutify } } +function toImmutable(value: any): any { + // passthru Immutable collections + if (isCollection(value)) return value; + + // don't touch ArrayBuffers & ArrayBufferViews - freeze them + if (ArrayBuffer.isView(value)) { + Object.freeze(value.buffer); + return value; + } + if (value instanceof ArrayBuffer) { + Object.freeze(value); + return value; + } + + // recurse into arrays & objects, converting them to lists & maps + // skip primitives & objects that don't want to be touched + if (typeof value === 'object' && !(NEVER_IMMUTIFY in value)) { + if (isArrayLike(value)) { + return List(value.map(item => toImmutable(item))); + } else { + return Map( + Object.entries(value).map( + ([key, value]) => [key, toImmutable(value)] + ) + ); + } + } + + return value; +} + function fromImmutable(value: any): any { + // reverse Immutable maps & lists if (isMap(value)) { return fromImmutable(value.toObject()); } - else if (isList(value)) { + if (isList(value)) { return fromImmutable(value.toArray()); } - else if (typeof value === 'object') { + + // passthru ArrayBuffers & ArrayBufferViews + if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) return value; + + // revert objects & arrays + // but: passthru objects w/ NEVER_IMMUTIFY + if (typeof value === 'object' && !(NEVER_IMMUTIFY in value)) { if (typeof value.length === 'number' && 0 in value && value.length-1 in value) { for (let i = 0; i < value.length; ++i) { value[i] = fromImmutable(value[i]); @@ -149,7 +192,6 @@ function fromImmutable(value: any): any { } return value; } - else { - return value; - } + + return value; } diff --git a/src/types.ts b/src/types.ts index 7955f0c..655a753 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,6 @@ import Immutable from 'immutable'; import { Result } from 'ts-results'; +import type { NEVER_IMMUTIFY } from './store/transactional'; export interface ContractResponse { messages: SubMsg[]; @@ -120,6 +121,7 @@ type CallDebugLog = { export type Snapshot = Immutable.Map; interface TraceLogCommon { + [NEVER_IMMUTIFY]: true; type: string; contractAddress: string; env: ExecuteEnv; diff --git a/src/util.ts b/src/util.ts index ca0de4a..b7b23f9 100644 --- a/src/util.ts +++ b/src/util.ts @@ -2,6 +2,8 @@ import { fromBase64, fromUtf8, toBase64, toUtf8 } from "@cosmjs/encoding"; import { Err, Ok, Result } from "ts-results"; import { Binary, RustResult } from "./types"; +export const isArrayLike = (value: any): value is any[] => typeof value === 'object' && typeof value.length === 'number' && value[0] && value[value.length-1]; + export const toBinary = (value: any): Binary => toBase64(toUtf8(JSON.stringify(value))); export const fromBinary = (str: string): unknown => JSON.parse(fromUtf8(fromBase64(str)));