Skip to content
This repository was archived by the owner on May 13, 2022. It is now read-only.

Commit 07509f9

Browse files
author
Silas Davis
committed
Fix up code gen and pull in Client
- Switch to use promises - Support function overloads - Add support for events Signed-off-by: Silas Davis <[email protected]>
1 parent e3d11fa commit 07509f9

40 files changed

+1748
-760
lines changed

docs/reference/web3.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ you can simply add Burrow to the list of networks.
2424
## Remix
2525

2626
[Remix](https://remix.ethereum.org/) is a web-based integrated development environment for Solidity.
27-
To deploy and run transactions, select `Web3 Provider` as the `Environment` and enter your local RPC
27+
To deploy and run transactions, select `Web3
28+
Provider` as the `Environment` and enter your local RPC
2829
address when prompted.
2930

3031
## Truffle

js/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"build": "tsc --build",
1616
"test": "./with-burrow.sh mocha 'src/**/*.test.ts'",
1717
"lint:fix": "eslint --fix 'src/**/*.ts'",
18-
"test:generate": "ts-node src/solts/sol/build.ts"
18+
"test:generate": "ts-node src/solts/sol/build.ts",
19+
"generate:provider": "ts-node src/solts/provider.gd.ts.gr.ts && eslint --fix --quiet src/solts/provider.gd.ts"
1920
},
2021
"dependencies": {
2122
"@grpc/grpc-js": "^1.3.0",

js/src/burrow.ts renamed to js/src/client.ts

+61-14
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,31 @@
11
import * as grpc from '@grpc/grpc-js';
2+
import { Interface } from 'ethers/lib/utils';
23
import { TxExecution } from '../proto/exec_pb';
34
import { CallTx, ContractMeta } from '../proto/payload_pb';
45
import { ExecutionEventsClient, IExecutionEventsClient } from '../proto/rpcevents_grpc_pb';
56
import { BlockRange } from '../proto/rpcevents_pb';
67
import { IQueryClient, QueryClient } from '../proto/rpcquery_grpc_pb';
7-
import { GetMetadataParam } from '../proto/rpcquery_pb';
8+
import { GetMetadataParam, StatusParam } from '../proto/rpcquery_pb';
89
import { ITransactClient, TransactClient } from '../proto/rpctransact_grpc_pb';
9-
import { callTx } from './contracts/call';
10+
import { ResultStatus } from '../proto/rpc_pb';
11+
import { ContractCodec, getContractCodec } from './codec';
12+
import { Address } from './contracts/abi';
13+
import { makeCallTx } from './contracts/call';
1014
import { CallOptions, Contract, ContractInstance } from './contracts/contract';
1115
import { toBuffer } from './convert';
1216
import { getException } from './error';
1317
import { EventCallback, Events, EventStream, latestStreamingBlockRange } from './events';
1418
import { Namereg } from './namereg';
19+
import { Provider } from './solts/provider.gd';
1520

1621
type TxCallback = (error: grpc.ServiceError | null, txe: TxExecution) => void;
1722

1823
export type Pipe = (payload: CallTx, callback: TxCallback) => void;
1924

20-
export class Burrow {
25+
export type Interceptor = (result: TxExecution) => Promise<TxExecution>;
26+
27+
export class Client implements Provider {
28+
interceptor: Interceptor;
2129
readonly events: Events;
2230
readonly namereg: Namereg;
2331

@@ -33,14 +41,15 @@ export class Burrow {
3341
this.executionEvents = new ExecutionEventsClient(url, credentials);
3442
this.transact = new TransactClient(url, credentials);
3543
this.query = new QueryClient(url, credentials);
36-
37-
this.callPipe = this.transact.callTxSync.bind(this.transact);
38-
this.simPipe = this.transact.callTxSim.bind(this.transact);
39-
4044
// This is the execution events streaming service running on top of the raw streaming function.
4145
this.events = new Events(this.executionEvents);
4246
// Contracts stuff running on top of grpc
4347
this.namereg = new Namereg(this.transact, this.query, this.account);
48+
// NOTE: in general interceptor may be async
49+
this.interceptor = async (data) => data;
50+
51+
this.callPipe = this.transact.callTxSync.bind(this.transact);
52+
this.simPipe = this.transact.callTxSim.bind(this.transact);
4453
}
4554

4655
/**
@@ -76,24 +85,24 @@ export class Burrow {
7685
);
7786
}
7887

79-
call(callTx: CallTx): Promise<TxExecution> {
88+
callTxSync(callTx: CallTx): Promise<TxExecution> {
8089
return new Promise((resolve, reject) =>
81-
this.callPipe(callTx, (error, txe) => {
90+
this.transact.callTxSync(callTx, (error, txe) => {
8291
if (error) {
8392
return reject(error);
8493
}
8594
const err = getException(txe);
8695
if (err) {
8796
return reject(err);
8897
}
89-
return resolve(txe);
98+
return resolve(this.interceptor(txe));
9099
}),
91100
);
92101
}
93102

94-
callSim(callTx: CallTx): Promise<TxExecution> {
103+
callTxSim(callTx: CallTx): Promise<TxExecution> {
95104
return new Promise((resolve, reject) =>
96-
this.simPipe(callTx, (error, txe) => {
105+
this.transact.callTxSim(callTx, (error, txe) => {
97106
if (error) {
98107
return reject(error);
99108
}
@@ -106,6 +115,39 @@ export class Burrow {
106115
);
107116
}
108117

118+
status(): Promise<ResultStatus> {
119+
return new Promise((resolve, reject) =>
120+
this.query.status(new StatusParam(), (err, resp) => (err ? reject(err) : resolve(resp))),
121+
);
122+
}
123+
124+
async latestHeight(): Promise<number> {
125+
const status = await this.status();
126+
return status.getSyncinfo()?.getLatestblockheight() ?? 0;
127+
}
128+
129+
// Methods below implement the generated codegen provider
130+
// TODO: should probably generate canonical version of Provider interface somewhere outside of files
131+
132+
async deploy(msg: CallTx): Promise<Address> {
133+
const txe = await this.callTxSync(msg);
134+
const contractAddress = txe.getReceipt()?.getContractaddress_asU8();
135+
if (!contractAddress) {
136+
throw new Error(`deploy appears to have succeeded but contract address is missing from result: ${txe}`);
137+
}
138+
return Buffer.from(contractAddress).toString('hex').toUpperCase();
139+
}
140+
141+
async call(msg: CallTx): Promise<Uint8Array | undefined> {
142+
const txe = await this.callTxSync(msg);
143+
return txe.getResult()?.getReturn_asU8();
144+
}
145+
146+
async callSim(msg: CallTx): Promise<Uint8Array | undefined> {
147+
const txe = await this.callTxSim(msg);
148+
return txe.getResult()?.getReturn_asU8();
149+
}
150+
109151
listen(
110152
signature: string,
111153
address: string,
@@ -115,7 +157,12 @@ export class Burrow {
115157
return this.events.listen(range, address, signature, callback);
116158
}
117159

118-
callTx(data: string | Uint8Array, address?: string, contractMeta: ContractMeta[] = []): CallTx {
119-
return callTx(typeof data === 'string' ? toBuffer(data) : data, this.account, address, contractMeta);
160+
payload(data: string | Uint8Array, address?: string, contractMeta: ContractMeta[] = []): CallTx {
161+
return makeCallTx(typeof data === 'string' ? toBuffer(data) : data, this.account, address, contractMeta);
162+
}
163+
164+
contractCodec(contractABI: string): ContractCodec {
165+
const iface = new Interface(contractABI);
166+
return getContractCodec(iface);
120167
}
121168
}

js/src/codec.ts

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { EventFragment, Fragment, FunctionFragment, Interface } from 'ethers/lib/utils';
2+
import { postDecodeResult, preEncodeResult, prefixedHexString, toBuffer } from './convert';
3+
4+
export type ContractCodec = {
5+
encodeDeploy(...args: unknown[]): Buffer;
6+
encodeFunctionData(signature: string, ...args: unknown[]): Buffer;
7+
decodeFunctionResult(signature: string, data: Uint8Array | undefined): any;
8+
decodeEventLog(signature: string, data: Uint8Array | undefined, topics?: Uint8Array[]): any;
9+
};
10+
11+
export function getContractCodec(iface: Interface): ContractCodec {
12+
return {
13+
encodeDeploy(...args: unknown[]): Buffer {
14+
const frag = iface.deploy;
15+
try {
16+
return toBuffer(iface.encodeDeploy(preEncodeResult(args, frag.inputs)));
17+
} catch (err) {
18+
throwErr(err, 'encode deploy', 'constructor', args, frag);
19+
}
20+
},
21+
22+
encodeFunctionData(signature: string, ...args: unknown[]): Buffer {
23+
let frag: FunctionFragment | undefined;
24+
try {
25+
frag = iface.getFunction(formatSignature(signature));
26+
return toBuffer(iface.encodeFunctionData(frag, preEncodeResult(args, frag.inputs)));
27+
} catch (err) {
28+
throwErr(err, 'encode function data', signature, args, frag);
29+
}
30+
},
31+
32+
decodeFunctionResult(signature: string, data: Uint8Array = new Uint8Array()): any {
33+
let frag: FunctionFragment | undefined;
34+
try {
35+
frag = iface.getFunction(formatSignature(signature));
36+
return postDecodeResult(iface.decodeFunctionResult(frag, data), frag.outputs);
37+
} catch (err) {
38+
throwErr(err, 'decode function result', signature, { data }, frag);
39+
}
40+
},
41+
42+
decodeEventLog(signature: string, data: Uint8Array = new Uint8Array(), topics?: Uint8Array[]): any {
43+
let frag: EventFragment | undefined;
44+
try {
45+
frag = iface.getEvent(formatSignature(signature));
46+
return postDecodeResult(
47+
iface.decodeEventLog(
48+
frag,
49+
prefixedHexString(data),
50+
topics?.map((topic) => prefixedHexString(topic)),
51+
),
52+
frag.inputs,
53+
);
54+
} catch (err) {
55+
throwErr(err, 'decode event log', signature, { data, topics }, frag);
56+
}
57+
},
58+
};
59+
}
60+
61+
function formatSignature(signature: string): string {
62+
return prefixedHexString(signature);
63+
}
64+
65+
function throwErr(
66+
err: unknown,
67+
action: string,
68+
signature: string,
69+
args: Record<string, unknown> | unknown[],
70+
frag?: Fragment,
71+
): never {
72+
const name = frag ? frag.name : `member with signature '${signature}'`;
73+
const inputs = frag?.inputs ? ` (inputs: ${JSON.stringify(frag.inputs)})` : '';
74+
throw new Error(`ContractCodec could not ${action} for ${name} with args ${JSON.stringify(args)}${inputs}: ${err}`);
75+
}

js/src/contracts/call.ts

+6-29
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import { SolidityFunction } from 'solc';
66
import { Result } from '../../proto/exec_pb';
77
import { CallTx, ContractMeta, TxInput } from '../../proto/payload_pb';
88
import { Envelope } from '../../proto/txs_pb';
9-
import { Pipe } from '../burrow';
10-
import { abiToBurrowResult, burrowToAbiResult, toBuffer } from '../convert';
9+
import { Pipe } from '../client';
10+
import { postDecodeResult, preEncodeResult, toBuffer } from '../convert';
1111
import { Address } from './abi';
1212
import { CallOptions } from './contract';
1313

@@ -19,7 +19,7 @@ const WasmMagic = Buffer.from('\0asm');
1919

2020
const coder = new AbiCoder();
2121

22-
export function callTx(
22+
export function makeCallTx(
2323
data: Uint8Array,
2424
inputAddress: Address,
2525
contractAddress?: Address,
@@ -155,32 +155,9 @@ export async function callFunction(
155155
callee: Address,
156156
args: unknown[],
157157
): Promise<unknown> {
158-
const data = toBuffer(iface.encodeFunctionData(frag, burrowToAbiResult(args, frag.inputs)));
159-
const transactionResult = await call(pipe, middleware(callTx(data, input, callee)));
158+
const data = toBuffer(iface.encodeFunctionData(frag, preEncodeResult(args, frag.inputs)));
159+
const transactionResult = await call(pipe, middleware(makeCallTx(data, input, callee)));
160160
// Unpack return arguments
161-
const result = abiToBurrowResult(iface.decodeFunctionResult(frag, transactionResult.resultBytes), frag.outputs);
161+
const result = postDecodeResult(iface.decodeFunctionResult(frag, transactionResult.resultBytes), frag.outputs);
162162
return handler({ transactionResult, result });
163163
}
164-
165-
// const decodeF = function (abi: Function, output: Uint8Array): DecodedResult {
166-
// if (!output) {
167-
// return;
168-
// }
169-
//
170-
// const outputs = abi.outputs;
171-
// const outputTypes = types(outputs);
172-
//
173-
// Decode raw bytes to arguments
174-
// const raw = convert.abiToBurrow(outputTypes, coder.rawDecode(outputTypes, Buffer.from(output)));
175-
// const result: DecodedResult = { raw: raw.slice() };
176-
//
177-
// result.values = outputs.reduce(function (acc, current) {
178-
// const value = raw.shift();
179-
// if (current.name) {
180-
// acc[current.name] = value;
181-
// }
182-
// return acc;
183-
// }, {});
184-
//
185-
// return result;
186-
// };

js/src/contracts/compile.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import solc from 'solc';
1+
import solc from 'solc_v5';
22
import { CompiledContract, Contract } from './contract';
33

44
// Compile solidity source code
55
export function compile<T = any>(
66
source: string,
77
name: string,
8-
errorSeverity: 'error' | 'warning' = 'error',
8+
fatalErrorSeverity: 'error' | 'warning' = 'error',
99
): Contract<T> {
1010
const desc: solc.InputDescription = { language: 'Solidity', sources: {} };
1111
if (!desc.sources) {
@@ -16,7 +16,7 @@ export function compile<T = any>(
1616

1717
const json = solc.compile(JSON.stringify(desc));
1818
const compiled: solc.OutputDescription = JSON.parse(json);
19-
const fatalErrors = compiled.errors.filter((err) => err.severity === errorSeverity);
19+
const fatalErrors = compiled.errors?.filter((err) => err.severity === fatalErrorSeverity) ?? [];
2020
if (fatalErrors.length) {
2121
throw new Error(fatalErrors.map((err) => err.formattedMessage).toString());
2222
}

js/src/contracts/contract.ts

+10-10
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { EventFragment, Fragment, FunctionFragment, Interface, LogDescription } from 'ethers/lib/utils';
22
import { Keccak } from 'sha3';
33
import { CallTx, ContractMeta } from '../../proto/payload_pb';
4-
import { Burrow } from '../burrow';
5-
import { burrowToAbiResult, toBuffer } from '../convert';
4+
import { Client } from '../client';
5+
import { preEncodeResult, toBuffer } from '../convert';
66
import { EventStream } from '../events';
77
import { ABI, Address } from './abi';
8-
import { call, callFunction, CallResult, callTx, DecodeResult } from './call';
8+
import { call, callFunction, CallResult, DecodeResult, makeCallTx } from './call';
99
import { EventCallback, listen } from './event';
1010

1111
export const meta: unique symbol = Symbol('meta');
@@ -54,7 +54,7 @@ export class Contract<T extends ContractInstance | any = any> {
5454
this.iface = new Interface(this.code.abi);
5555
}
5656

57-
at(address: Address, burrow: Burrow, options: CallOptions = defaultCallOptions): T {
57+
at(address: Address, burrow: Client, options: CallOptions = defaultCallOptions): T {
5858
const instance: ContractInstance = {
5959
[meta]: {
6060
address,
@@ -77,20 +77,20 @@ export class Contract<T extends ContractInstance | any = any> {
7777
return [this.code, ...this.childCode].map(contractMeta).filter((m): m is ContractMeta => Boolean(m));
7878
}
7979

80-
async deploy(burrow: Burrow, ...args: unknown[]): Promise<T> {
80+
async deploy(burrow: Client, ...args: unknown[]): Promise<T> {
8181
return this.deployWith(burrow, defaultCallOptions, ...args);
8282
}
8383

84-
async deployWith(burrow: Burrow, options?: Partial<CallOptions>, ...args: unknown[]): Promise<T> {
84+
async deployWith(burrow: Client, options?: Partial<CallOptions>, ...args: unknown[]): Promise<T> {
8585
const opts = { ...defaultCallOptions, ...options };
8686
if (!this.code.bytecode) {
8787
throw new Error(`cannot deploy contract without compiled bytecode`);
8888
}
8989
const { middleware } = opts;
9090
const data = Buffer.concat(
91-
[this.code.bytecode, this.iface.encodeDeploy(burrowToAbiResult(args, this.iface.deploy.inputs))].map(toBuffer),
91+
[this.code.bytecode, this.iface.encodeDeploy(preEncodeResult(args, this.iface.deploy.inputs))].map(toBuffer),
9292
);
93-
const tx = middleware(callTx(data, burrow.account, undefined, this.meta()));
93+
const tx = middleware(makeCallTx(data, burrow.account, undefined, this.meta()));
9494
const { contractAddress } = await call(burrow.callPipe, tx);
9595
return this.at(contractAddress, burrow, opts);
9696
}
@@ -116,7 +116,7 @@ export type ContractEvent = ((cb: EventCallback) => EventStream) & {
116116
function contractFunction(
117117
iface: Interface,
118118
frag: FunctionFragment,
119-
burrow: Burrow,
119+
burrow: Client,
120120
options: CallOptions,
121121
contractAddress: string,
122122
): ContractFunction {
@@ -135,7 +135,7 @@ function contractFunction(
135135
return func;
136136
}
137137

138-
function contractEvent(iface: Interface, frag: EventFragment, burrow: Burrow, contractAddress: string): ContractEvent {
138+
function contractEvent(iface: Interface, frag: EventFragment, burrow: Client, contractAddress: string): ContractEvent {
139139
const func = (cb: EventCallback) => listen(iface, frag, contractAddress, burrow, cb);
140140
func.at = (address: string, cb: EventCallback) => listen(iface, frag, address, burrow, cb);
141141
func.once = () =>

0 commit comments

Comments
 (0)