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

Commit fe242ae

Browse files
author
Casey Kuhlman
authored
Merge pull request #1487 from hyperledger/solts
Pull solts into Burrow
2 parents 163912e + 8c11544 commit fe242ae

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+3738
-196
lines changed

CHANGELOG.md

+15
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
11
# [Hyperledger Burrow](https://github.com/hyperledger/burrow) Changelog
2+
## [0.33.0] - 2021-05-24
3+
### Changed
4+
- [JS] Changed Burrow interface and renamed Burrow client object to to Client (merging in features needed for solts support)
5+
6+
### Fixed
7+
- [JS] Fixed RLP encoding extra leading zeros on uint64 (thanks Matthieu Vachon!)
8+
- [JS] Improved compatibility with legacy Solidity bytes types and padding conventions
9+
- [Events] Fixed Burrow event stream wrongly switching to streaming mode for block ranges that are available in state (when the latest block is an empty block - so not stored in state)
10+
11+
### Added
12+
- [JS] Added Solidity-to-Typescript code generation support (merging in solts) - this provides helpers (build.ts, api.ts) to compile Solidity files into corresponding .abi.ts files that include types for functions, events, the ABI, and EVM bytecode, and includes bindings into Burrow JS to deploy and interact with contracts via Typescript/Javascript with strong static types
13+
- [JS] Improved interactions with events which can now be queried over any range and with strong types, see the listenerFor, reduceEvents, readEvents, and iterateEvents functions.
14+
15+
216
## [0.32.1] - 2021-05-15
317
### Changed
418
- [Execution] CallErrors no longer emit very long rather pointless (since there is no tooling to help interpret them currently) EVM call traces
@@ -753,6 +767,7 @@ This release marks the start of Eris-DB as the full permissioned blockchain node
753767
- [Blockchain] Fix getBlocks to respect block height cap.
754768

755769

770+
[0.33.0]: https://github.com/hyperledger/burrow/compare/v0.32.1...v0.33.0
756771
[0.32.1]: https://github.com/hyperledger/burrow/compare/v0.32.0...v0.32.1
757772
[0.32.0]: https://github.com/hyperledger/burrow/compare/v0.31.3...v0.32.0
758773
[0.31.3]: https://github.com/hyperledger/burrow/compare/v0.31.2...v0.31.3

Makefile

+4
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,10 @@ publish_js:
217217
yarn --cwd js build
218218
yarn --cwd js publish --access public --non-interactive --no-git-tag-version --new-version $(shell ./scripts/local_version.sh)
219219

220+
.PHONY: clean_js
221+
clean_js:
222+
find js -name '*.abi.ts' -exec rm '{}' ';' -print
223+
220224
.PHONY: test
221225
test: check bin/solc bin/solang
222226
@tests/scripts/bin_wrapper.sh go test ./... ${GO_TEST_ARGS}

NOTES.md

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
### Changed
2-
- [Execution] CallErrors no longer emit very long rather pointless (since there is no tooling to help interpret them currently) EVM call traces
3-
- [JS] Return byte arrays as Buffers from decode (only return fixed-width byteNN types as hex strings)
2+
- [JS] Changed Burrow interface and renamed Burrow client object to to Client (merging in features needed for solts support)
3+
4+
### Fixed
5+
- [JS] Fixed RLP encoding extra leading zeros on uint64 (thanks Matthieu Vachon!)
6+
- [JS] Improved compatibility with legacy Solidity bytes types and padding conventions
7+
- [Events] Fixed Burrow event stream wrongly switching to streaming mode for block ranges that are available in state (when the latest block is an empty block - so not stored in state)
8+
9+
### Added
10+
- [JS] Added Solidity-to-Typescript code generation support (merging in solts) - this provides helpers (build.ts, api.ts) to compile Solidity files into corresponding .abi.ts files that include types for functions, events, the ABI, and EVM bytecode, and includes bindings into Burrow JS to deploy and interact with contracts via Typescript/Javascript with strong static types
11+
- [JS] Improved interactions with events which can now be queried over any range and with strong types, see the listenerFor, reduceEvents, readEvents, and iterateEvents functions.
412

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

+11-9
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44
"description": "TypeScript library that calls a Hyperledger Burrow server over GRPC.",
55
"main": "./dist/index.js",
66
"types": "./dist/index.d.ts",
7-
"author": {
8-
"name": "Dennis Mckinnon"
9-
},
107
"files": [
118
"dist",
129
"proto"
@@ -16,15 +13,20 @@
1613
},
1714
"scripts": {
1815
"build": "tsc --build",
19-
"test": "./with-burrow.sh mocha 'src/test/**/*.test.ts'",
20-
"lint:fix": "eslint --fix src/**/*.ts"
16+
"test": "./with-burrow.sh mocha 'src/**/*.test.ts'",
17+
"lint:fix": "eslint --fix 'src/**/*.ts'",
18+
"lint:fix:abi": "eslint --fix --quiet 'src/**/*.abi.ts'",
19+
"test:generate": "ts-node src/solts/sol/build.ts && yarn lint:fix:abi",
20+
"generate:interface": "ts-node src/solts/interface.gd.ts.gr.ts && eslint --fix --quiet src/solts/interface.gd.ts"
2121
},
2222
"dependencies": {
2323
"@grpc/grpc-js": "^1.3.0",
2424
"ethers": "^5.1.4",
2525
"google-protobuf": "^3.15.8",
26-
"solc": "^0.8.4",
27-
"sha3": "^2.1.4"
26+
"sha3": "^2.1.4",
27+
"solc_v5": "npm:solc@^0.5.17",
28+
"solc_v8": "npm:solc@^0.8.4",
29+
"typescript": "^4.2.4"
2830
},
2931
"devDependencies": {
3032
"@types/google-protobuf": "^3.15.2",
@@ -33,11 +35,11 @@
3335
"@typescript-eslint/parser": "^4.22.0",
3436
"eslint": "^7.25.0",
3537
"eslint-plugin-prettier": "^3.3.1",
36-
"prettier": "^2.2.1",
37-
"prettier-plugin-organize-imports": "^1.1.1",
3838
"grpc-tools": "^1.11.1",
3939
"grpc_tools_node_protoc_ts": "^5.2.1",
4040
"mocha": "^8.3.2",
41+
"prettier": "^2.2.1",
42+
"prettier-plugin-organize-imports": "^1.1.1",
4143
"ts-node": "^9.1.1",
4244
"typescript": "^4.2.4"
4345
},

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

+68-25
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
11
import * as grpc from '@grpc/grpc-js';
2-
import { Readable } from 'stream';
3-
import { TxExecution } from '../proto/exec_pb';
2+
import { Interface } from 'ethers/lib/utils';
3+
import { Event, TxExecution } from '../proto/exec_pb';
44
import { CallTx, ContractMeta } from '../proto/payload_pb';
55
import { ExecutionEventsClient, IExecutionEventsClient } from '../proto/rpcevents_grpc_pb';
6-
import { BlockRange } from '../proto/rpcevents_pb';
76
import { IQueryClient, QueryClient } from '../proto/rpcquery_grpc_pb';
8-
import { GetMetadataParam } from '../proto/rpcquery_pb';
7+
import { GetMetadataParam, StatusParam } from '../proto/rpcquery_pb';
98
import { ITransactClient, TransactClient } from '../proto/rpctransact_grpc_pb';
10-
import { callTx } from './contracts/call';
9+
import { ResultStatus } from '../proto/rpc_pb';
10+
import { ContractCodec, getContractCodec } from './codec';
11+
import { Address } from './contracts/abi';
12+
import { makeCallTx } from './contracts/call';
1113
import { CallOptions, Contract, ContractInstance } from './contracts/contract';
1214
import { toBuffer } from './convert';
1315
import { getException } from './error';
14-
import { EventCallback, Events, latestStreamingBlockRange } from './events';
16+
import { Bounds, EventCallback, EventStream, getBlockRange, queryFor, stream } from './events';
1517
import { Namereg } from './namereg';
18+
import { Provider } from './solts/interface.gd';
1619

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

1922
export type Pipe = (payload: CallTx, callback: TxCallback) => void;
2023

21-
export class Burrow {
22-
readonly events: Events;
24+
export type Interceptor = (result: TxExecution) => Promise<TxExecution>;
25+
26+
export class Client implements Provider {
27+
interceptor: Interceptor;
2328
readonly namereg: Namereg;
2429

2530
readonly executionEvents: IExecutionEventsClient;
@@ -34,14 +39,13 @@ export class Burrow {
3439
this.executionEvents = new ExecutionEventsClient(url, credentials);
3540
this.transact = new TransactClient(url, credentials);
3641
this.query = new QueryClient(url, credentials);
42+
// Contracts stuff running on top of grpc
43+
this.namereg = new Namereg(this.transact, this.query, this.account);
44+
// NOTE: in general interceptor may be async
45+
this.interceptor = async (data) => data;
3746

3847
this.callPipe = this.transact.callTxSync.bind(this.transact);
3948
this.simPipe = this.transact.callTxSim.bind(this.transact);
40-
41-
// This is the execution events streaming service running on top of the raw streaming function.
42-
this.events = new Events(this.executionEvents);
43-
// Contracts stuff running on top of grpc
44-
this.namereg = new Namereg(this.transact, this.query, this.account);
4549
}
4650

4751
/**
@@ -77,24 +81,24 @@ export class Burrow {
7781
);
7882
}
7983

80-
call(callTx: CallTx): Promise<TxExecution> {
84+
callTxSync(callTx: CallTx): Promise<TxExecution> {
8185
return new Promise((resolve, reject) =>
82-
this.callPipe(callTx, (error, txe) => {
86+
this.transact.callTxSync(callTx, (error, txe) => {
8387
if (error) {
8488
return reject(error);
8589
}
8690
const err = getException(txe);
8791
if (err) {
8892
return reject(err);
8993
}
90-
return resolve(txe);
94+
return resolve(this.interceptor(txe));
9195
}),
9296
);
9397
}
9498

95-
callSim(callTx: CallTx): Promise<TxExecution> {
99+
callTxSim(callTx: CallTx): Promise<TxExecution> {
96100
return new Promise((resolve, reject) =>
97-
this.simPipe(callTx, (error, txe) => {
101+
this.transact.callTxSim(callTx, (error, txe) => {
98102
if (error) {
99103
return reject(error);
100104
}
@@ -107,16 +111,55 @@ export class Burrow {
107111
);
108112
}
109113

114+
status(): Promise<ResultStatus> {
115+
return new Promise((resolve, reject) =>
116+
this.query.status(new StatusParam(), (err, resp) => (err ? reject(err) : resolve(resp))),
117+
);
118+
}
119+
120+
async latestHeight(): Promise<number> {
121+
const status = await this.status();
122+
return status.getSyncinfo()?.getLatestblockheight() ?? 0;
123+
}
124+
125+
// Methods below implement the generated codegen provider
126+
// TODO: should probably generate canonical version of Provider interface somewhere outside of files
127+
128+
async deploy(msg: CallTx): Promise<Address> {
129+
const txe = await this.callTxSync(msg);
130+
const contractAddress = txe.getReceipt()?.getContractaddress_asU8();
131+
if (!contractAddress) {
132+
throw new Error(`deploy appears to have succeeded but contract address is missing from result: ${txe}`);
133+
}
134+
return Buffer.from(contractAddress).toString('hex').toUpperCase();
135+
}
136+
137+
async call(msg: CallTx): Promise<Uint8Array | undefined> {
138+
const txe = await this.callTxSync(msg);
139+
return txe.getResult()?.getReturn_asU8();
140+
}
141+
142+
async callSim(msg: CallTx): Promise<Uint8Array | undefined> {
143+
const txe = await this.callTxSim(msg);
144+
return txe.getResult()?.getReturn_asU8();
145+
}
146+
110147
listen(
111-
signature: string,
148+
signatures: string[],
112149
address: string,
113-
callback: EventCallback,
114-
range: BlockRange = latestStreamingBlockRange,
115-
): Readable {
116-
return this.events.listen(range, address, signature, callback);
150+
callback: EventCallback<Event>,
151+
start: Bounds = 'latest',
152+
end: Bounds = 'stream',
153+
): EventStream {
154+
return stream(this.executionEvents, getBlockRange(start, end), queryFor({ address, signatures }), callback);
155+
}
156+
157+
payload(data: string | Uint8Array, address?: string, contractMeta: ContractMeta[] = []): CallTx {
158+
return makeCallTx(typeof data === 'string' ? toBuffer(data) : data, this.account, address, contractMeta);
117159
}
118160

119-
callTx(data: string | Uint8Array, address?: string, contractMeta: ContractMeta[] = []): CallTx {
120-
return callTx(typeof data === 'string' ? toBuffer(data) : data, this.account, address, contractMeta);
161+
contractCodec(contractABI: string): ContractCodec {
162+
const iface = new Interface(contractABI);
163+
return getContractCodec(iface);
121164
}
122165
}

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-
// };

0 commit comments

Comments
 (0)