Skip to content

Commit

Permalink
Merge pull request #25 from Terran-One/feat/transactional
Browse files Browse the repository at this point in the history
Transactional Storage
  • Loading branch information
Kiruse authored Dec 7, 2022
2 parents c6c2b41 + a6048e1 commit c773b92
Show file tree
Hide file tree
Showing 7 changed files with 610 additions and 398 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.6.2",
"version": "2.7.0",
"description": "Mock blockchain environment for simulating CosmWasm interactions",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
Expand Down
20 changes: 14 additions & 6 deletions src/CWSimulateApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,23 @@ import { Err, Result } from 'ts-results';
import { WasmModule, WasmQuery } from './modules/wasm';
import { BankModule, BankQuery } from './modules/bank';
import { AppResponse, Binary } from './types';
import { Transactional, TransactionalLens } from './store/transactional';

export interface CWSimulateAppOptions {
chainId: string;
bech32Prefix: string;
}

export type ChainData = {
height: number;
time: number;
}

export class CWSimulateApp {
public chainId: string;
public bech32Prefix: string;

public store: Map<string, any>;
public height: number;
public time: number;
public store: TransactionalLens<ChainData>;

public wasm: WasmModule;
public bank: BankModule;
Expand All @@ -25,9 +29,10 @@ export class CWSimulateApp {
constructor(options: CWSimulateAppOptions) {
this.chainId = options.chainId;
this.bech32Prefix = options.bech32Prefix;
this.store = Map<string, any>();
this.height = 1;
this.time = 0;
this.store = new Transactional().lens<ChainData>().initialize({
height: 1,
time: 0,
});

this.wasm = new WasmModule(this);
this.bank = new BankModule(this);
Expand All @@ -47,6 +52,9 @@ export class CWSimulateApp {
return Err(`unknown message: ${JSON.stringify(msg)}`);
}
}

get height() { return this.store.get('height') }
get time() { return this.store.get('time') }
}

export type QueryMessage =
Expand Down
99 changes: 51 additions & 48 deletions src/modules/bank.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Map } from 'immutable';
import { fromJS } from 'immutable';
import { cmd, exec, TestContract } from '../../testing/wasm-util';
import { CWSimulateApp } from '../CWSimulateApp';
import { fromBinary } from '../util';
import { BankMessage, BankQuery, ParsedCoin } from './bank';
import { BankMessage, BankQuery } from './bank';

type WrappedBankMessage = {
bank: BankMessage;
Expand All @@ -27,43 +27,43 @@ describe('BankModule', () => {
bank.send('alice', 'bob', [{denom: 'foo', amount: '100'}]).unwrap();

// Assert
expect(bank.getBalance('alice')).toEqual([new ParsedCoin('foo', BigInt(900))]);
expect(bank.getBalance('bob')).toEqual([new ParsedCoin('foo', BigInt(100))]);
expect(bank.getBalances()).toEqual(Map([
['alice', [{denom: 'foo', amount: '900'}]],
['bob', [{denom: 'foo', amount: '100'}]],
]));
expect(bank.getBalance('alice')).toEqual([coin('foo', 900)]);
expect(bank.getBalance('bob')).toEqual([coin('foo', 100)]);
expect(bank.getBalances()).toEqual(fromJS({
alice: [coin('foo', 900)],
bob: [coin('foo', 100)],
}));
});

it('handle send failure', () => {
// Arrange
const bank = chain.bank;
bank.setBalance('alice', [{denom: 'foo', amount: '100'}]);
bank.setBalance('alice', [coin('foo', 100)]);

// Act
const res = bank.send('alice', 'bob', [{denom: 'foo', amount: '1000'}]);
const res = bank.send('alice', 'bob', [coin('foo', 1000)]);

// Assert
expect(res.err).toBeDefined();
expect(bank.getBalances()).toEqual(Map([
['alice', [{denom: 'foo', amount: '100'}]],
]));
expect(bank.getBalance('alice')).toEqual([new ParsedCoin('foo', BigInt(100))]);
expect(bank.getBalances()).toEqual(fromJS({
alice: [coin('foo', 100)],
}));
expect(bank.getBalance('alice')).toEqual([coin('foo', 100)]);
});

it('handle burn', () => {
// Arrange
const bank = chain.bank;
bank.setBalance('alice', [{denom: 'foo', amount: '1000'}]);
bank.setBalance('alice', [coin('foo', 1000)]);

// Act
bank.burn('alice', [{denom: 'foo', amount: '100'}]);
bank.burn('alice', [coin('foo', 100)]);

// Assert
expect(bank.getBalance('alice')).toEqual([new ParsedCoin('foo', BigInt(900))]);
expect(bank.getBalances()).toEqual(Map([
['alice', [{denom: 'foo', amount: '900'}]],
]));
expect(bank.getBalance('alice')).toEqual([coin('foo', 900)]);
expect(bank.getBalances()).toEqual(fromJS({
alice: [coin('foo', 900)],
}));
});

it('handle burn failure', () => {
Expand All @@ -76,16 +76,16 @@ describe('BankModule', () => {

// Assert
expect(res.err).toBeDefined()
expect(bank.getBalance('alice')).toEqual([new ParsedCoin('foo', BigInt(100))]);
expect(bank.getBalances()).toEqual(Map([
['alice', [{denom: 'foo', amount: '100'}]],
]));
expect(bank.getBalance('alice')).toEqual([coin('foo', 100)]);
expect(bank.getBalances()).toEqual(fromJS({
alice: [coin('foo', 100)],
}));
});

it('handle msg', () => {
// Arrange
const bank = chain.bank;
bank.setBalance('alice', [{denom: 'foo', amount: '1000'}]);
bank.setBalance('alice', [coin('foo', 1000)]);

// Act
let msg: WrappedBankMessage = {
Expand All @@ -99,10 +99,10 @@ describe('BankModule', () => {
chain.handleMsg('alice', msg);

// Assert
expect(bank.getBalances()).toEqual(Map([
['alice', [{denom: 'foo', amount: '900'}]],
['bob', [{denom: 'foo', amount: '100'}]],
]));
expect(bank.getBalances()).toEqual(fromJS({
alice: [coin('foo', 900)],
bob: [coin('foo', 100)],
}));
});

it('contract integration', async () => {
Expand All @@ -116,30 +116,30 @@ describe('BankModule', () => {
cmd.bank({
send: {
to_address: 'alice',
amount: [{denom: 'foo', amount: '100'}],
amount: [coin('foo', 100)],
},
}),
cmd.bank({
send: {
to_address: 'bob',
amount: [{denom: 'foo', amount: '100'}],
amount: [coin('foo', 100)],
},
}),
cmd.bank({
burn: {
amount: [{denom: 'foo', amount: '100'}],
amount: [coin('foo', 100)],
},
}),
);
const res = await contract.execute('alice', msg);

// Assert
expect(res.ok).toBeTruthy();
expect(bank.getBalances()).toEqual(Map([
[contract.address, [{denom: 'foo', amount: '700'}]],
['alice', [{denom: 'foo', amount: '100'}]],
['bob', [{denom: 'foo', amount: '100'}]],
]));
expect(bank.getBalances()).toEqual(fromJS({
[contract.address]: [coin('foo', 700)],
alice: [coin('foo', 100)],
bob: [coin('foo', 100)],
}));
});

it('querier integration', () => {
Expand All @@ -159,28 +159,29 @@ describe('BankModule', () => {
};

bank.setBalance('alice', [
{ denom: 'foo', amount: '100' },
{ denom: 'bar', amount: '200' },
coin('foo', 100),
coin('bar', 200),
]);
bank.setBalance('bob', [
{ denom: 'foo', amount: '200' },
{ denom: 'bar', amount: '200' },
coin('foo', 200),
coin('bar', 200),
]);

let res = chain.querier.handleQuery({ bank: queryBalance });
expect(res.ok).toBeTruthy();
expect(fromBinary(res.val)).toEqual({ amount: { denom: 'foo', amount: '100' }});
expect(fromBinary(res.val)).toEqual({ amount: coin('foo', 100)});

res = chain.querier.handleQuery({ bank: queryAllBalances });
expect(res.ok).toBeTruthy();
expect(fromBinary(res.val)).toEqual({
amount: [
{ denom: 'foo', amount: '200' },
{ denom: 'bar', amount: '200' },
coin('foo', 200),
coin('bar', 200),
],
});
});
it('handle delete', () => {

it('handle delete', () => {
// Arrange
const bank = chain.bank;
bank.setBalance('alice', [{denom: 'foo', amount: '1000'}]);
Expand All @@ -191,8 +192,10 @@ describe('BankModule', () => {

// Assert
expect(bank.getBalance('alice')).toBeDefined();
expect(bank.getBalances()).toEqual(Map([
['alice', [{denom: 'foo', amount: '1000'}]]
]));
expect(bank.getBalances()).toEqual(fromJS({
alice: [{denom: 'foo', amount: '1000'}],
}));
});
});

const coin = (denom: string, amount: string | number) => ({ denom, amount: `${amount}` });
Loading

0 comments on commit c773b92

Please sign in to comment.