Skip to content

Commit

Permalink
fix copying raw bytes (ArrayBuffer & views);
Browse files Browse the repository at this point in the history
also introduce NEVER_IMMUTIFY which prevents the storage system
from converting specific objects to Immutable collections
  • Loading branch information
Kiruse committed Dec 7, 2022
1 parent a8677ff commit 352983e
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 12 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
8 changes: 7 additions & 1 deletion src/modules/wasm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -350,6 +351,7 @@ export class WasmModule {
);

trace.push({
[NEVER_IMMUTIFY]: true,
type: 'instantiate' as 'instantiate',
contractAddress,
msg: instantiateMsg,
Expand Down Expand Up @@ -420,6 +422,7 @@ export class WasmModule {
let result = Err(response.error);

trace.push({
[NEVER_IMMUTIFY]: true,
type: 'execute' as 'execute',
contractAddress,
msg: executeMsg,
Expand Down Expand Up @@ -462,6 +465,7 @@ export class WasmModule {
);

trace.push({
[NEVER_IMMUTIFY]: true,
type: 'execute' as 'execute',
contractAddress,
msg: executeMsg,
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -656,6 +661,7 @@ export class WasmModule {
);

trace.push({
[NEVER_IMMUTIFY]: true,
type: 'reply' as 'reply',
contractAddress,
msg: replyMsg,
Expand Down
62 changes: 52 additions & 10 deletions src/store/transactional.ts
Original file line number Diff line number Diff line change
@@ -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 extends any[]> = [P, ...T];
type First<T extends any[]> = T extends Prefix<infer F, any[]> ? F : never;
Expand All @@ -18,7 +24,7 @@ type Lens<T, P extends PropertyKey[]> =
: T;

type Immutify<T> =
T extends Primitive
T extends NoImmutify
? T
: T extends ArrayLike<infer E>
? List<Immutify<E>>
Expand Down Expand Up @@ -89,7 +95,7 @@ export class TransactionalLens<M extends object> {

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);
Expand All @@ -112,8 +118,7 @@ export class TransactionalLens<M extends object> {
return this.db.tx(update => {
const setter: LensSetter<M> = <P extends PropertyKey[]>(...path: P) =>
(value: Lens<M, P> | Immutify<Lens<M, P>>) => {
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 = <P extends PropertyKey[]>(...path: P) => {
update(curr => curr.deleteIn([...this.prefix, ...path]));
Expand All @@ -129,14 +134,52 @@ export class TransactionalLens<M extends object> {
get data() { return this.db.data.getIn([...this.prefix]) as Immutify<M> }
}

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]);
Expand All @@ -149,7 +192,6 @@ function fromImmutable(value: any): any {
}
return value;
}
else {
return value;
}

return value;
}
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -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[];
Expand Down Expand Up @@ -120,6 +121,7 @@ type CallDebugLog<T extends keyof CosmWasmAPI = keyof CosmWasmAPI> = {
export type Snapshot = Immutable.Map<unknown, unknown>;

interface TraceLogCommon {
[NEVER_IMMUTIFY]: true;
type: string;
contractAddress: string;
env: ExecuteEnv;
Expand Down
2 changes: 2 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)));

Expand Down

0 comments on commit 352983e

Please sign in to comment.