Skip to content

Commit a4ed361

Browse files
authored
Merge pull request #27 from Terran-One/feat/persist
Persistence Update
2 parents 49740c7 + 5591590 commit a4ed361

File tree

10 files changed

+169
-14
lines changed

10 files changed

+169
-14
lines changed

package-lock.json

Lines changed: 29 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@terran-one/cw-simulate",
3-
"version": "2.7.5",
3+
"version": "2.8.0",
44
"description": "Mock blockchain environment for simulating CosmWasm interactions",
55
"main": "dist/index.js",
66
"typings": "dist/index.d.ts",
@@ -39,6 +39,7 @@
3939
"@cosmjs/amino": "^0.28.13",
4040
"@cosmjs/crypto": "^0.28.13",
4141
"@cosmjs/encoding": "^0.28.13",
42+
"@kiruse/serde": "^0.6.3",
4243
"@terran-one/cosmwasm-vm-js": "^0.2.16",
4344
"immutable": "^4.1.0",
4445
"lobyte": "^0.0.3",

src/CWSimulateApp.spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { toBase64 } from '@cosmjs/encoding';
2+
import fs from 'fs';
3+
import { CWSimulateApp } from './CWSimulateApp';
4+
import * as persist from './persist';
5+
import { TestContract } from '../testing/wasm-util';
6+
7+
const bytecode = fs.readFileSync('./testing/cw_simulate_tests-aarch64.wasm');
8+
9+
describe('de/serialize', () => {
10+
it('works', async () => {
11+
{
12+
const ref = new CWSimulateApp({ chainId: 'phoenix-1', bech32Prefix: 'terra1' });
13+
ref.wasm.create('alice', bytecode);
14+
ref.wasm.create('bob', bytecode);
15+
16+
const response = await ref.wasm.instantiateContract('alice', [], 1, {}, '');
17+
const address = response.unwrap().events[0].attributes[0].value;
18+
19+
const bytes = persist.save(ref);
20+
const clone = await persist.load(bytes);
21+
expect(clone.chainId).toStrictEqual(ref.chainId);
22+
expect(clone.bech32Prefix).toStrictEqual(ref.bech32Prefix);
23+
24+
const code1 = clone.wasm.getCodeInfo(1)!;
25+
const code2 = clone.wasm.getCodeInfo(2)!;
26+
expect(code1.creator).toStrictEqual('alice');
27+
expect(code2.creator).toStrictEqual('bob');
28+
expect(toBase64(code1.wasmCode)).toStrictEqual(toBase64(ref.wasm.store.getObject('codes', 1, 'wasmCode')));
29+
expect(toBase64(code2.wasmCode)).toStrictEqual(toBase64(ref.wasm.store.getObject('codes', 2, 'wasmCode')));
30+
31+
let result = await clone.wasm.executeContract('alice', [], address, { debug: { msg: 'foobar' }});
32+
expect(result.ok).toBeTruthy();
33+
}
34+
})
35+
})

src/CWSimulateApp.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { QuerierBase } from '@terran-one/cosmwasm-vm-js';
2-
import { Map } from 'immutable';
3-
import { Err, Result } from 'ts-results';
2+
import { Err, Ok, Result } from 'ts-results';
43
import { WasmModule, WasmQuery } from './modules/wasm';
54
import { BankModule, BankQuery } from './modules/bank';
5+
import { fromImmutable, toImmutable, Transactional, TransactionalLens } from './store/transactional';
66
import { AppResponse, Binary } from './types';
7-
import { Transactional, TransactionalLens } from './store/transactional';
87

98
export interface CWSimulateAppOptions {
109
chainId: string;

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
export * from './CWSimulateApp';
22
export * from './types';
33
export * from './store';
4+
5+
import { save, load } from './persist';
6+
export const persist = { save, load };

src/modules/bank.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { fromJS } from 'immutable';
21
import { cmd, exec, TestContract } from '../../testing/wasm-util';
32
import { CWSimulateApp } from '../CWSimulateApp';
43
import { fromBinary } from '../util';

src/modules/wasm.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@ export class WasmModule {
202202
};
203203
}
204204

205+
hasVM(contractAddress: string): boolean {
206+
return !!this.vms[contractAddress];
207+
}
208+
205209
async buildVM(contractAddress: string): Promise<CWSimulateVMInstance> {
206210
if (!(contractAddress in this.vms)) {
207211
const contractInfo = this.getContractInfo(contractAddress);

src/persist.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import SerdeProtocol, { SERDE } from '@kiruse/serde';
2+
import { Reference } from '@kiruse/serde/dist/types';
3+
import { List, Map } from 'immutable';
4+
import { Ok } from 'ts-results';
5+
import { CWSimulateApp } from './CWSimulateApp';
6+
7+
export const serde = SerdeProtocol.standard()
8+
.derive('immutable-list',
9+
(list: List<any>, data) => {
10+
return {
11+
data: data(list.toArray()),
12+
// ownerID is a unique object that should not even appear on
13+
// other Immutable data structures. When present, it signifies
14+
// that the Immutable should be mutated in-place rather than
15+
// creating copies of its data.
16+
mutable: !!(list as any).__ownerID,
17+
};
18+
},
19+
({ data, mutable }, deref) => {
20+
if (!data.length) return List();
21+
const list = List().asMutable();
22+
Reference.all(deref, data, values => {
23+
for (const value of values) {
24+
list.push(value);
25+
}
26+
!mutable && list.asImmutable();
27+
});
28+
return list;
29+
},
30+
)
31+
.derive('immutable-map',
32+
(map: Map<any, any>, data) => {
33+
return {
34+
data: data(map.toObject()),
35+
// same as with List above
36+
mutable: !!(map as any).__ownerID,
37+
};
38+
},
39+
({ data, mutable }, deref) => {
40+
const map = Map().asMutable();
41+
const keys = Object.keys(data);
42+
if (!keys.length) return Map();
43+
Reference.all(deref, keys.map(k => data[k]), values => {
44+
values.forEach((value, i) => {
45+
const key = keys[i];
46+
map.set(key, value);
47+
});
48+
!mutable && map.asImmutable();
49+
});
50+
return map;
51+
},
52+
)
53+
.derive('cw-simulate-app',
54+
(app: CWSimulateApp) => ({
55+
chainId: app.chainId,
56+
bech32Prefix: app.bech32Prefix,
57+
store: app.store.db.data,
58+
}),
59+
({ chainId, bech32Prefix, store }, deref): CWSimulateApp => {
60+
const app = new CWSimulateApp({
61+
chainId,
62+
bech32Prefix,
63+
});
64+
Reference.all(deref, [store], ([map]) => {
65+
app.store.db.tx(update => {
66+
update(() => map);
67+
return Ok(undefined);
68+
});
69+
});
70+
return app;
71+
},
72+
)
73+
74+
export const save = (app: CWSimulateApp) => serde.serializeAs('cw-simulate-app', app).compress().buffer;
75+
export const load = async (bytes: Uint8Array) => {
76+
const app = serde.deserializeAs('cw-simulate-app', bytes);
77+
const contracts = [...app.wasm.store.get('contracts').keys()];
78+
await Promise.all(contracts.map(address => app.wasm.buildVM(address)));
79+
return app;
80+
};
81+
82+
// Inject SERDE
83+
Map.prototype[SERDE] = 'immutable-map';
84+
List.prototype[SERDE] = 'immutable-list';

src/store/transactional.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export class Transactional {
4242
constructor(private _data = Map()) {}
4343

4444
lens<M extends object>(...path: PropertyKey[]) {
45-
return new TransactionalLens<M>(this, path);
45+
return new TransactionalLens<M>(this, path.map(stringify));
4646
}
4747

4848
tx<R extends Result<any, any>>(cb: (update: TxUpdater) => Promise<R>): Promise<R>;
@@ -91,7 +91,7 @@ export class Transactional {
9191
}
9292

9393
export class TransactionalLens<M extends object> {
94-
constructor(public readonly db: Transactional, public readonly prefix: PropertyKey[]) {}
94+
constructor(public readonly db: Transactional, public readonly prefix: string[]) {}
9595

9696
initialize(data: M) {
9797
this.db.tx(update => {
@@ -104,7 +104,7 @@ export class TransactionalLens<M extends object> {
104104
}
105105

106106
get<P extends PropertyKey[]>(...path: P): Immutify<Lens<M, P>> {
107-
return this.db.data.getIn([...this.prefix, ...path]) as any;
107+
return this.db.data.getIn([...this.prefix, ...path.map(stringify)]) as any;
108108
}
109109

110110
getObject<P extends PropertyKey[]>(...path: P): Lens<M, P> {
@@ -118,17 +118,17 @@ export class TransactionalLens<M extends object> {
118118
return this.db.tx(update => {
119119
const setter: LensSetter<M> = <P extends PropertyKey[]>(...path: P) =>
120120
(value: Lens<M, P> | Immutify<Lens<M, P>>) => {
121-
update(curr => curr.setIn([...this.prefix, ...path], toImmutable(value)));
121+
update(curr => curr.setIn([...this.prefix, ...path.map(stringify)], toImmutable(value)));
122122
}
123123
const deleter: LensDeleter = <P extends PropertyKey[]>(...path: P) => {
124-
update(curr => curr.deleteIn([...this.prefix, ...path]));
124+
update(curr => curr.deleteIn([...this.prefix, ...path.map(stringify)]));
125125
}
126126
return cb(setter, deleter);
127127
});
128128
}
129129

130130
lens<P extends PropertyKey[]>(...path: P): TransactionalLens<Lens<M, P>> {
131-
return new TransactionalLens<Lens<M, P>>(this.db, [...this.prefix, ...path]);
131+
return new TransactionalLens<Lens<M, P>>(this.db, [...this.prefix, ...path.map(stringify)]);
132132
}
133133

134134
get data() { return this.db.data.getIn([...this.prefix]) as Immutify<M> }
@@ -195,3 +195,5 @@ export function fromImmutable(value: any): any {
195195

196196
return value;
197197
}
198+
199+
const stringify = (v: any) => v+'';

src/util.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import { fromBase64, fromUtf8, toBase64, toUtf8 } from "@cosmjs/encoding";
22
import { Err, Ok, Result } from "ts-results";
33
import { Binary, RustResult } from "./types";
44

5-
export const isArrayLike = (value: any): value is any[] => typeof value === 'object' && typeof value.length === 'number';
5+
export const isArrayLike = (value: any): value is any[] =>
6+
typeof value === 'object' && typeof value.length === 'number';
67

78
export const toBinary = (value: any): Binary => toBase64(toUtf8(JSON.stringify(value)));
89
export const fromBinary = (str: string): unknown => JSON.parse(fromUtf8(fromBase64(str)));

0 commit comments

Comments
 (0)