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

Commit

Permalink
Make deploy take an object argument with options and library addresses
Browse files Browse the repository at this point in the history
for easier generic deployment code and reflection by downstream users

Signed-off-by: Silas Davis <[email protected]>
  • Loading branch information
Silas Davis committed Jul 23, 2021
1 parent b74c28f commit dd1462d
Show file tree
Hide file tree
Showing 47 changed files with 536 additions and 217 deletions.
80 changes: 44 additions & 36 deletions docs/js-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,19 +102,19 @@ var addressToCreate = "6075EADD0C7A33EE6153F3FA1B21E4D80045FCE2"
// The amount we send is arbitrary
var amount = 20

burrow.transact.SendTxSync(
{
Inputs: [{
Address: Buffer.from(account, 'hex'),
Amount: amount
}],
Outputs: [{
Address: Buffer.from(addressToCreate, 'hex'),
Amount: amount
}]
})
.then(txe => console.log(txe))
.catch(err => console.error(err))
client.transact.SendTxSync(
{
Inputs: [{
Address: Buffer.from(account, 'hex'),
Amount: amount
}],
Outputs: [{
Address: Buffer.from(addressToCreate, 'hex'),
Amount: amount
}]
})
.then(txe => console.log(txe))
.catch(err => console.error(err))
```

The return `txe` (short for `TxExecution`) logged to the console in the `then` block contains the history and fingerprint of the `SendTx` execution. You can see an example of this in [basic app](example/basic-app/app.js).
Expand All @@ -125,26 +125,30 @@ Here is an example of usage in setting and getting a name:

```javascript
var setPayload = {
Input: {
Address: Buffer.from(account,'hex'),
Amount: 50000
},
Name: "DOUG",
Data: "Marmot",
Fee: 5000
Input: {
Address: Buffer.from(account, 'hex'),
Amount: 50000
},
Name: "DOUG",
Data: "Marmot",
Fee: 5000
}

var getPayload = {Name: "DOUG"}

// Using a callback
burrow.transact.NameTxSync(setPayload, function(error, data){
if (error) throw error; // or something more sensible
// data object contains detailed information of the transaction execution.

// Get a name this time using a promise
burrow.query.GetName(getPayload)
.then((data) => {console.log(data);}) // should print "Marmot"
.catch((error)=> {throw error;})
client.transact.NameTxSync(setPayload, function (error, data) {
if (error) throw error; // or something more sensible
// data object contains detailed information of the transaction execution.

// Get a name this time using a promise
client.query.GetName(getPayload)
.then((data) => {
console.log(data);
}) // should print "Marmot"
.catch((error) => {
throw error;
})
})

```
Expand Down Expand Up @@ -239,14 +243,18 @@ Sets an entry in the namereg. It returns a promise if callback not provided.

```javascript
// Using a callback
burrow.namereg.set("DOUG", "Marmot", 5000, function(error, data){
if (error) throw error; // or something more sensible
// data object contains detailed information of the transaction execution.

// Get a name this time using a promise
burrow.namereg.get("DOUG")
.then((data) => {console.log(data);}) // Should print "Marmot"
.catch((error)=> {throw error;})
client.namereg.set("DOUG", "Marmot", 5000, function (error, data) {
if (error) throw error; // or something more sensible
// data object contains detailed information of the transaction execution.

// Get a name this time using a promise
client.namereg.get("DOUG")
.then((data) => {
console.log(data);
}) // Should print "Marmot"
.catch((error) => {
throw error;
})
})
```

Expand Down
3 changes: 2 additions & 1 deletion js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
"generate:interface": "ts-node src/solts/interface.gd.ts.gr.ts && eslint --fix --quiet src/solts/interface.gd.ts"
},
"dependencies": {
"@grpc/grpc-js": "^1.3.0",
"@ethersproject/abi": "^5.2.0",
"@grpc/grpc-js": "^1.3.0",
"camel-case": "^4.1.2",
"google-protobuf": "^3.15.8",
"sha3": "^2.1.4",
"solc_v5": "npm:solc@^0.5.17",
Expand Down
20 changes: 16 additions & 4 deletions js/src/solts/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ts, { factory } from 'typescript';
import { ABI } from "../contracts/abi";
import { ABI } from '../contracts/abi';
import { callerTypes, createCallerFunction } from './lib/caller';
import { declareContractType, generateContractObject } from './lib/contract';
import { generateDecodeObject } from './lib/decoder';
Expand All @@ -16,7 +16,13 @@ import { getContractMethods } from './lib/solidity';
import { declareConstant, ExportToken, importBurrow, importReadable } from './lib/syntax';
import Func = ABI.Func;

export { decodeOutput, encodeInput, importLocalResolver, inputDescriptionFromFiles, tokenizeLinks } from '../contracts/compile';
export {
decodeOutput,
encodeInput,
importLocalResolver,
inputDescriptionFromFiles,
tokenizeLinks,
} from '../contracts/compile';

export type Compiled = {
name: string;
Expand All @@ -34,7 +40,6 @@ const abiName = factory.createIdentifier('abi');
export function newFile(contracts: Compiled[], burrowImportPath: string): ts.Node[] {
const provider = new Provider();

const contractNames = contracts.map((c) => factory.createIdentifier(c.name));
return [
ts.addSyntheticLeadingComment(
importReadable(),
Expand All @@ -55,7 +60,14 @@ export function newFile(contracts: Compiled[], burrowImportPath: string): ts.Nod
? [
declareConstant(bytecodeName, factory.createStringLiteral(contract.bytecode, true), true),
declareConstant(deployedBytecodeName, factory.createStringLiteral(contract.deployedBytecode, true), true),
generateDeployFunction(deploy, contract.links, provider, abiName, contractNames),
generateDeployFunction(
deploy,
contract.links,
provider,
abiName,
// Exclude names of interface contracts since they have no deployed code
contracts.filter((c) => c.bytecode).map((c) => factory.createIdentifier(c.name)),
),
generateDeployContractFunction(deploy, contract.links, provider),
]
: [];
Expand Down
110 changes: 56 additions & 54 deletions js/src/solts/build.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
import { promises as fs } from "fs";
import * as path from "path";
import * as solcv5 from "solc_v5";
import * as solcv8 from "solc_v8";
import { promises as fs } from 'fs';
import * as path from 'path';
import * as solcv5 from 'solc_v5';
import * as solcv8 from 'solc_v8';
import {
decodeOutput,
encodeInput,
importLocalResolver,
inputDescriptionFromFiles,
Solidity
} from "../contracts/compile";
import { Compiled, newFile, printNodes, tokenizeLinks } from "./api";
Solidity,
} from '../contracts/compile';
import { Compiled, newFile, printNodes, tokenizeLinks } from './api';

const solcCompilers = {
v5: solcv5,
v8: solcv8
v8: solcv8,
} as const;

export const defaultBuildOptions = {
solcVersion: "v5" as keyof typeof solcCompilers,
burrowImportPath: (sourceFile: string) => "@hyperledger/burrow" as string,
binPath: "bin" as string,
abiExt: ".abi" as string,
solcVersion: 'v5' as keyof typeof solcCompilers,
burrowImportPath: (sourceFile: string) => '@hyperledger/burrow' as string,
binPath: 'bin' as string | false,
abiExt: '.abi' as string,
// Used to resolve layout in bin folder - defaults to srcPath if is passed or process.cwd() otherwise
basePath: undefined as undefined | string,
failOnWarnings: false as boolean
failOnWarnings: false as boolean,
} as const;

export type BuildOptions = typeof defaultBuildOptions;
Expand All @@ -38,60 +38,62 @@ export type BuildOptions = typeof defaultBuildOptions;
export async function build(srcPathOrFiles: string | string[], opts?: Partial<BuildOptions>): Promise<void> {
const { failOnWarnings, solcVersion, binPath, basePath, burrowImportPath, abiExt } = {
...defaultBuildOptions,
...opts
...opts,
};
const resolvedBasePath = basePath ?? (typeof srcPathOrFiles === "string" ? srcPathOrFiles : process.cwd());
const resolvedBasePath = basePath ?? (typeof srcPathOrFiles === 'string' ? srcPathOrFiles : process.cwd());
process.chdir(resolvedBasePath);
const basePathPrefix = new RegExp("^" + path.resolve(resolvedBasePath));
await fs.mkdir(binPath, { recursive: true });
const basePathPrefix = new RegExp('^' + path.resolve(resolvedBasePath));
const solidityFiles = await getSourceFilesList(srcPathOrFiles);
const inputDescription = inputDescriptionFromFiles(
// solidityFiles.map((f) => path.resolve(resolvedBasePath, f.replace(basePathPrefix, ''))),
solidityFiles
solidityFiles,
);
const input = encodeInput(inputDescription);
const solc = solcCompilers[solcVersion];

const solcOutput = solc.compile(input, { import: importLocalResolver(resolvedBasePath) });
const output = decodeOutput(solcOutput);
const errors = output.errors?.filter((e) => failOnWarnings || (e.severity === "error")) || [];
const errors = output.errors?.filter((e) => failOnWarnings || e.severity === 'error') || [];
if (errors.length > 0) {
throw new Error(
"Solidity compiler errors:\n" + formatErrors(errors)
);
throw new Error('Solidity compiler errors:\n' + formatErrors(errors));
}
const warnings = output.errors?.filter((e) => e.severity === "warning") || [];
const warnings = output.errors?.filter((e) => e.severity === 'warning') || [];

if (warnings.length) {
console.error("Solidity compiler warnings (not treated as fatal):\n" + formatErrors(warnings))
console.error('Solidity compiler warnings (not treated as fatal):\n' + formatErrors(warnings));
}

const plan = Object.keys(output.contracts).map((filename) => ({
source: filename,
target: filename.replace(/\.[^/.]+$/, ".abi.ts"),
target: filename.replace(/\.[^/.]+$/, '.abi.ts'),
contracts: Object.entries(output.contracts[filename]).map(([name, contract]) => ({
name,
contract
}))
contract,
})),
}));

const binPlan = plan.flatMap((f) => {
return f.contracts.map(({ name, contract }) => ({
source: f.source,
name,
filename: path.join(binPath, path.dirname(path.resolve(f.source)).replace(basePathPrefix, ""), name + abiExt),
abi: JSON.stringify(contract)
}));
});

const dupes = findDupes(binPlan, (b) => b.filename);

if (dupes.length) {
const dupeDescs = dupes.map(({ key, dupes }) => ({ duplicate: key, sources: dupes.map((d) => d.source) }));
throw Error(
`Duplicate contract names found (these contracts will result ABI filenames that will collide since ABIs ` +
`are flattened in '${binPath}'):\n${dupeDescs.map((d) => JSON.stringify(d)).join("\n")}`
);
let binPlan: { source: string; filename: string; abi: string }[] = [];

if (binPath !== false) {
await fs.mkdir(binPath, { recursive: true });
binPlan = plan.flatMap((f) => {
return f.contracts.map(({ name, contract }) => ({
source: f.source,
name,
filename: path.join(binPath, path.dirname(path.resolve(f.source)).replace(basePathPrefix, ''), name + abiExt),
abi: JSON.stringify(contract),
}));
});

const dupes = findDupes(binPlan, (b) => b.filename);

if (dupes.length) {
const dupeDescs = dupes.map(({ key, dupes }) => ({ duplicate: key, sources: dupes.map((d) => d.source) }));
throw Error(
`Duplicate contract names found (these contracts will result ABI filenames that will collide since ABIs ` +
`are flattened in '${binPath}'):\n${dupeDescs.map((d) => JSON.stringify(d)).join('\n')}`,
);
}
}

// Write the ABIs emitted for each file to the name of that file without extension. We flatten into a single
Expand All @@ -107,11 +109,11 @@ export async function build(srcPathOrFiles: string | string[], opts?: Partial<Bu
printNodes(
...newFile(
contracts.map(({ name, contract }) => getCompiled(name, contract)),
burrowImportPath(source)
)
)
)
)
burrowImportPath(source),
),
),
),
),
]);
}

Expand All @@ -121,7 +123,7 @@ function getCompiled(name: string, contract: Solidity.Contract): Compiled {
abi: contract.abi,
bytecode: contract.evm.bytecode.object,
deployedBytecode: contract.evm.deployedBytecode.object,
links: tokenizeLinks(contract.evm.bytecode.linkReferences)
links: tokenizeLinks(contract.evm.bytecode.linkReferences),
};
}

Expand All @@ -138,15 +140,15 @@ function findDupes<T>(list: T[], by: (t: T) => string): { key: string; dupes: T[
.filter(([_, group]) => group.length > 1)
.map(([key, dupes]) => ({
key,
dupes
dupes,
}));
}

async function getSourceFilesList(srcPathOrFiles: string | string[]): Promise<string[]> {
if (typeof srcPathOrFiles === "string") {
if (typeof srcPathOrFiles === 'string') {
const files: string[] = [];
for await (const f of walkDir(srcPathOrFiles)) {
if (path.extname(f) === ".sol") {
if (path.extname(f) === '.sol') {
files.push(f);
}
}
Expand All @@ -167,5 +169,5 @@ async function* walkDir(dir: string): AsyncGenerator<string, void, void> {
}

function formatErrors(errors: Solidity.Error[]): string {
return errors.map((err) => err.formattedMessage || err.message).join("")
return errors.map((err) => err.formattedMessage || err.message).join('');
}
Loading

0 comments on commit dd1462d

Please sign in to comment.