Skip to content

Commit

Permalink
[ethers] Generate typed variants of ContractFactory
Browse files Browse the repository at this point in the history
  • Loading branch information
quezak committed Jul 3, 2019
1 parent 3be81e3 commit 11f380f
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 57 deletions.
7 changes: 6 additions & 1 deletion __snapshots__/DumbContract.spec.ts.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,12 @@ export class DumbContract extends TC.TypeChainContract {
stateMutability: "pure",
type: "function",
},
{ inputs: [], payable: false, stateMutability: "nonpayable", type: "constructor" },
{
inputs: [{ name: "initialCounter", type: "uint256" }],
payable: false,
stateMutability: "nonpayable",
type: "constructor",
},
{
anonymous: false,
inputs: [
Expand Down
39 changes: 26 additions & 13 deletions lib/targets/ethers/generation.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
import {
Contract,
AbiParameter,
ConstantFunctionDeclaration,
FunctionDeclaration,
ConstantDeclaration,
EventDeclaration,
ConstantFunctionDeclaration,
Contract,
EventArgDeclaration,
EventDeclaration,
FunctionDeclaration,
} from "../../parser/abiParser";
import {
EvmType,
IntegerType,
UnsignedIntegerType,
AddressType,
VoidType,
ArrayType,
BooleanType,
BytesType,
DynamicBytesType,
BooleanType,
ArrayType,
EvmType,
IntegerType,
StringType,
TupleType,
UnsignedIntegerType,
VoidType,
} from "../../parser/typeParser";

export function codegen(contract: Contract) {
// TODO strict typings for the event listener methods?
const template = `
import { Contract, ContractTransaction, EventFilter, Signer } from 'ethers';
import { Contract, ContractFactory, ContractTransaction, EventFilter, Signer } from "ethers";
import { Listener, Provider } from 'ethers/providers';
import { Arrayish, BigNumber, BigNumberish, Interface } from "ethers/utils";
import { UnsignedTransaction } from "ethers/utils/transaction";
import { TransactionOverrides, TypedEventDescription, TypedFunctionDescription } from ".";
interface ${contract.name}Interface extends Interface {
Expand Down Expand Up @@ -65,12 +65,25 @@ export function codegen(contract: Contract) {
estimate: {
${contract.functions.map(generateEstimateFunction).join("\n")}
};
}
}
${generateFactoryIfConstructorPresent(contract)}
`;

return template;
}

function generateFactoryIfConstructorPresent(contract: Contract): string {
if (!contract.constructor) return "";
return `
export class ${contract.name}Factory extends ContractFactory {
deploy(${generateInputTypes(contract.constructor.inputs)}): Promise<${contract.name}>;
getDeployTransaction(${generateInputTypes(contract.constructor.inputs)}): UnsignedTransaction;
attach(address: string): ${contract.name};
connect(signer: Signer): ${contract.name}Factory;
}
`;
}

function generateConstantFunction(fn: ConstantFunctionDeclaration): string {
return `
${fn.name}(${generateInputTypes(fn.inputs)}): Promise<${generateOutputTypes(fn.outputs)}>;
Expand Down
4 changes: 2 additions & 2 deletions test/integration/contracts/DumbContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ contract DumbContract {
bytes32 public byteArray;
bytes public dynamicByteArray;

constructor() public {
counter = 0;
constructor(uint initialCounter) public {
counter = initialCounter;
someAddress = msg.sender;
}

Expand Down
55 changes: 35 additions & 20 deletions test/integration/targets/ethers/DumbContract.spec.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,42 @@
import { deployContract } from "./ethers";
import { DumbContract } from "./types/ethers-contracts/DumbContract";
import { getContractFactory } from "./ethers";
import { DumbContract, DumbContractFactory } from "./types/ethers-contracts/DumbContract";
import { BigNumber } from "ethers/utils";

import { expect } from "chai";
import { Event } from "ethers";
import { arrayify } from "ethers/utils/bytes";

describe("DumbContract", () => {
function deployDumbContract(): Promise<DumbContract> {
const factory = getContractFactory("DumbContract") as DumbContractFactory;
return factory.deploy(0);
}

it("should work", async () => {
const contract = (await deployContract("DumbContract")) as DumbContract;
const contract = await deployDumbContract();

const res = await contract.functions.returnAll();
expect(res).to.be.deep.eq([new BigNumber("0"), new BigNumber("5")]);
});

it("should have an address", async () => {
const contract = (await deployContract("DumbContract")) as DumbContract;
const contract = await deployDumbContract();

expect(contract.address).to.be.string;
});

it("should allow passing a contructor argument in multiple ways", async () => {
const factory = getContractFactory("DumbContract") as DumbContractFactory;
const contract1 = await factory.deploy(42);
expect(await contract1.functions.counter()).to.be.deep.eq(new BigNumber("42"));
const contract2 = await factory.deploy("1234123412341234123");
expect(await contract2.functions.counter()).to.be.deep.eq(new BigNumber("1234123412341234123"));
const contract3 = await factory.deploy(new BigNumber("5678567856785678567"));
expect(await contract3.functions.counter()).to.be.deep.eq(new BigNumber("5678567856785678567"));
});

it("should allow to pass unsigned values in multiple ways", async () => {
const contract = (await deployContract("DumbContract")) as DumbContract;
const contract = await deployDumbContract();

await contract.functions.countup(2);
expect(await contract.functions.counter()).to.be.deep.eq(new BigNumber("2"));
Expand All @@ -32,7 +47,7 @@ describe("DumbContract", () => {
});

it("should allow to pass signed values in multiple ways", async () => {
const contract = (await deployContract("DumbContract")) as DumbContract;
const contract = await deployDumbContract();

expect(await contract.functions.returnSigned(2)).to.be.deep.eq(new BigNumber("2"));
expect(await contract.functions.returnSigned("2")).to.be.deep.eq(new BigNumber("2"));
Expand All @@ -42,15 +57,15 @@ describe("DumbContract", () => {
});

it("should allow to pass address values", async () => {
const contract = (await deployContract("DumbContract")) as DumbContract;
const contract = await deployDumbContract();

expect(
await contract.functions.testAddress("0x0000000000000000000000000000000000000123"),
).to.be.eq("0x0000000000000000000000000000000000000123");
});

it("should allow to pass bytes values in multiple ways", async () => {
const contract = (await deployContract("DumbContract")) as DumbContract;
const contract = await deployDumbContract();
const bytes32Str = "0x0201030700000000000000000000000000000000000000000000000000004200";

await contract.functions.callWithBytes(bytes32Str);
Expand All @@ -62,7 +77,7 @@ describe("DumbContract", () => {
});

it("should allow to pass dynamic byte arrays in multiple ways", async () => {
const contract = (await deployContract("DumbContract")) as DumbContract;
const contract = await deployDumbContract();
const bytesStr = "0x02010307000000000000000000000000133700000000000000000000000000000000004200";

await contract.functions.callWithDynamicByteArray(bytesStr);
Expand All @@ -74,27 +89,27 @@ describe("DumbContract", () => {
});

it("should allow to pass boolean values", async () => {
const contract = (await deployContract("DumbContract")) as DumbContract;
const contract = await deployDumbContract();

const res = await contract.functions.callWithBoolean(true);
expect(res).to.be.deep.eq(true);
});

it("should allow to pass numeric arrays values in multiple ways", async () => {
const contract = (await deployContract("DumbContract")) as DumbContract;
const contract = await deployDumbContract();

const res = await contract.functions.callWithArray2(["1", 2]);
expect(res).to.be.deep.eq([new BigNumber("1"), new BigNumber("2")]);
});

it("should allow to pass strings ", async () => {
const contract = (await deployContract("DumbContract")) as DumbContract;
const contract = await deployDumbContract();

expect(await contract.functions.testString("abc")).to.be.deep.eq("abc");
});

it("should allow to pass overrides", async () => {
const contract = (await deployContract("DumbContract")) as DumbContract;
const contract = await deployDumbContract();
const value = 1;
const gasPrice = 33;

Expand All @@ -105,36 +120,36 @@ describe("DumbContract", () => {
});

it("should return number for small ints", async () => {
const contract = (await deployContract("DumbContract")) as DumbContract;
const contract = await deployDumbContract();

expect(await contract.functions.returnSmallUint()).to.be.eq(18);
});

it("should .attach to another contract instance", async () => {
const contract1 = (await deployContract("DumbContract")) as DumbContract;
const contract2 = (await deployContract("DumbContract")) as DumbContract;
const contract1 = await deployDumbContract();
const contract2 = await deployDumbContract();

await contract1.functions.countup(2);
const reconnectedContract = contract2.attach(contract1.address);
expect(await reconnectedContract.functions.counter()).to.be.deep.eq(new BigNumber("2"));
});

it("should estimate tx gas cost", async () => {
const contract = (await deployContract("DumbContract")) as DumbContract;
const contract = await deployDumbContract();

expect((await contract.estimate.countup(2)).toNumber()).to.be.greaterThan(22000);
});

it("should encode a tx", async () => {
const contract = (await deployContract("DumbContract")) as DumbContract;
const contract = await deployDumbContract();

expect(await contract.interface.functions.countup.encode([2])).to.eq(
"0x7916df080000000000000000000000000000000000000000000000000000000000000002",
);
});

it("should encode event topics", async () => {
const contract = (await deployContract("DumbContract")) as DumbContract;
const contract = await deployDumbContract();

expect(
await contract.interface.events.Deposit.encodeTopics([
Expand All @@ -149,7 +164,7 @@ describe("DumbContract", () => {

it("should listen for an event", async function(this) {
this.timeout(10000); // the listener isn't called within the default 2000ms
const contract = (await deployContract("DumbContract")) as DumbContract;
const contract = await deployDumbContract();
const value = 42;
const signerAddress = await contract.signer.getAddress();

Expand Down
12 changes: 6 additions & 6 deletions test/integration/targets/ethers/ethers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,20 @@ export async function createNewBlockchain() {
}

before(async () => {
const r = await createNewBlockchain();
signer = r.signer;
server = r.server;
({ server, signer } = await createNewBlockchain());
});

export async function deployContract(contractName: string): Promise<Contract> {
export function getContractFactory(contractName: string): ContractFactory {
const abiDirPath = join(__dirname, "../../abis");

const abi = JSON.parse(readFileSync(join(abiDirPath, contractName + ".abi"), "utf-8"));
const bin = readFileSync(join(abiDirPath, contractName + ".bin"), "utf-8");
const code = "0x" + bin;
return new ContractFactory(abi, code, signer);
}

const factory = new ContractFactory(abi, code, signer);

export async function deployContract(contractName: string): Promise<Contract> {
const factory = getContractFactory(contractName);
return factory.deploy();
}

Expand Down
2 changes: 1 addition & 1 deletion test/integration/targets/legacy/DumbContract.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe("DumbContract", () => {
let contractAddress: string;

beforeEach(async () => {
contractAddress = (await deployContract("DumbContract")).address;
contractAddress = (await deployContract("DumbContract", 0)).address;
});

it("should snapshot generated code", () =>
Expand Down
26 changes: 15 additions & 11 deletions test/integration/targets/web3-1.0.0/DumbContract.spec.web3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import { DumbContract } from "./types/web3-contracts/DumbContract";
import { expect } from "chai";

describe("DumbContract", () => {
function deployDumbContract(): Promise<DumbContract> {
return deployContract<DumbContract>("DumbContract", 0);
}

it("should work", async () => {
const contract: DumbContract = await deployContract<DumbContract>("DumbContract");
const contract: DumbContract = await deployDumbContract();

const res = await contract.methods.returnAll().call({ from: accounts[0] });
expect(isBigNumber(res[0])).to.be.true;
Expand All @@ -15,13 +19,13 @@ describe("DumbContract", () => {
});

it("should have an address", async () => {
const contract = await deployContract<DumbContract>("DumbContract");
const contract = await deployDumbContract();

expect(await contract.options.address).to.be.string;
});

it("should allow to pass unsigned values in multiple ways", async () => {
const contract = await deployContract<DumbContract>("DumbContract");
const contract = await deployDumbContract();

await contract.methods.countup(2).send({ from: accounts[0] });
const withNumber = await contract.methods.counter().call();
Expand All @@ -35,7 +39,7 @@ describe("DumbContract", () => {
});

it("should allow to pass signed values in multiple ways", async () => {
const contract = await deployContract<DumbContract>("DumbContract");
const contract = await deployDumbContract();

const withNumber = await contract.methods.returnSigned(2).call();
expect(isBigNumber(withNumber)).to.be.true;
Expand All @@ -47,15 +51,15 @@ describe("DumbContract", () => {
});

it("should allow to pass address values in multiple ways", async () => {
const contract = await deployContract<DumbContract>("DumbContract");
const contract = await deployDumbContract();

expect(
await contract.methods.testAddress("0x0000000000000000000000000000000000000123").call(),
).to.be.eq("0x0000000000000000000000000000000000000123");
});

it("should allow to pass bytes values in multiple ways", async () => {
const contract = await deployContract<DumbContract>("DumbContract");
const contract = await deployDumbContract();
const byteString = "0xabcd123456000000000000000000000000000000000000000000000000000000";

await contract.methods.callWithBytes(byteString).send({ from: accounts[0] });
Expand All @@ -66,7 +70,7 @@ describe("DumbContract", () => {
});

it("should allow to pass Buffer for dynamic bytes array", async () => {
const contract = await deployContract<DumbContract>("DumbContract");
const contract = await deployDumbContract();
const byteString = "0xabcd123456000000000000000000000000000000000000000000000000000000";

await contract.methods.callWithDynamicByteArray(byteString).send({ from: accounts[0] });
Expand All @@ -77,14 +81,14 @@ describe("DumbContract", () => {
});

it("should allow to pass boolean values", async () => {
const contract = await deployContract<DumbContract>("DumbContract");
const contract = await deployDumbContract();

const res = await contract.methods.callWithBoolean(true).call();
expect(res).to.be.deep.eq(true);
});

it("should allow to pass numeric arrays values in multiple ways", async () => {
const contract = await deployContract<DumbContract>("DumbContract");
const contract = await deployDumbContract();

const res = await contract.methods.callWithArray2(["1", 2]).call();
expect(res.length).to.be.eq(2);
Expand All @@ -95,13 +99,13 @@ describe("DumbContract", () => {
});

it("should allow to pass strings ", async () => {
const contract = await deployContract<DumbContract>("DumbContract");
const contract = await deployDumbContract();

expect(await contract.methods.testString("abc").call()).to.be.deep.eq("abc");
});

it("should allow to clone contracts ", async () => {
const contract = await deployContract<DumbContract>("DumbContract");
const contract = await deployDumbContract();

const contractClone = await contract.clone();

Expand Down
Loading

0 comments on commit 11f380f

Please sign in to comment.