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

[WIP] Support of multiple frameworks and adding ethers as second #833

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
741 changes: 370 additions & 371 deletions examples/lib-complex/package-lock.json

Large diffs are not rendered by default.

5,476 changes: 2,735 additions & 2,741 deletions packages/lib/package-lock.json

Large diffs are not rendered by default.

83 changes: 4 additions & 79 deletions packages/lib/src/artifacts/Contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import ZWeb3 from './ZWeb3';
import Contracts from './Contracts';
import ContractAST from '../utils/ContractAST';
import { StorageLayoutInfo } from '../validations/Storage';
import { Callback, EventLog, EventEmitter, TransactionReceipt } from 'web3/types';
import { Contract as Web3Contract, TransactionObject, BlockType } from 'web3-eth-contract';

/*
Expand All @@ -15,23 +14,14 @@ import { Contract as Web3Contract, TransactionObject, BlockType } from 'web3-eth
*/
export default interface Contract {

// Web3 Contract interface.
options: any;
methods: { [fnName: string]: (...args: any[]) => TransactionObject<any>; };
deploy(options: { data: string; arguments: any[]; }): TransactionObject<Web3Contract>;
events: {
[eventName: string]: (options?: { filter?: object; fromBlock?: BlockType; topics?: string[]; }, cb?: Callback<EventLog>) => EventEmitter;
allEvents: (options?: { filter?: object; fromBlock?: BlockType; topics?: string[]; }, cb?: Callback<EventLog>) => EventEmitter;
};
getPastEvents(event: string, options?: { filter?: object; fromBlock?: BlockType; toBlock?: BlockType; topics?: string[]; }, cb?: Callback<EventLog[]>): Promise<EventLog[]>;
setProvider(provider: any): void;
deploy(options: { data: string; arguments: any[]; }): any;
methods: { [fnName: string]: (...args: any[]) => any; };

// Contract specific.
address: string;
new: (args?: any[], options?: {}) => Promise<Contract>;
at: (address: string) => Contract;
link: (libraries: { [libAlias: string]: string }) => void;
deployment?: { transactionHash: string, transactionReceipt: TransactionReceipt };
schema: {

// Zos schema specific.
Expand Down Expand Up @@ -65,60 +55,9 @@ interface ContractMethod {
inputs: string[];
}

function _wrapContractInstance(schema: any, instance: Web3Contract): Contract {
instance.schema = schema;

instance.new = async function(...passedArguments): Promise<Contract> {
const [args, options] = parseArguments(passedArguments, schema.abi);
if(!schema.linkedBytecode) throw new Error(`${schema.contractName} bytecode contains unlinked libraries.`);
instance.options = { ...instance.options, ...(await Contracts.getDefaultTxParams()) };
return new Promise((resolve, reject) => {
const tx = instance.deploy({ data: schema.linkedBytecode, arguments: args });
let transactionReceipt, transactionHash;
tx.send({ ...options })
.on('error', (error) => reject(error))
.on('receipt', (receipt) => transactionReceipt = receipt)
.on('transactionHash', (hash) => transactionHash = hash)
.then((deployedInstance) => { // instance != deployedInstance
deployedInstance = _wrapContractInstance(schema, deployedInstance);
deployedInstance.deployment = { transactionReceipt, transactionHash };
resolve(deployedInstance);
})
.catch((error) => reject(error));
});
};

instance.at = function(address: string): Contract | never {
if(!ZWeb3.isAddress(address)) throw new Error('Given address is not valid: ' + address);
const newWeb3Instance = instance.clone();
newWeb3Instance._address = address;
newWeb3Instance.options.address = address;
return _wrapContractInstance(instance.schema, newWeb3Instance);
};

instance.link = function(libraries: { [libAlias: string]: string }): void {
instance.schema.linkedBytecode = instance.schema.bytecode;
instance.schema.linkedDeployedBytecode = instance.schema.deployedBytecode;

Object.keys(libraries).forEach((name: string) => {
const address = libraries[name].replace(/^0x/, '');
const regex = new RegExp(`__${name}_+`, 'g');
instance.schema.linkedBytecode = instance.schema.linkedBytecode.replace(regex, address);
instance.schema.linkedDeployedBytecode = instance.schema.linkedDeployedBytecode.replace(regex, address);
});
};

// TODO: Remove after web3 adds the getter: https://github.com/ethereum/web3.js/issues/2274
if(typeof instance.address === 'undefined') {
Object.defineProperty(instance, 'address', { get: () => instance.options.address });
}

return instance;
}

export function createContract(schema: any): Contract {
const contract = ZWeb3.contract(schema.abi, null, Contracts.getArtifactsDefaults());
return _wrapContractInstance(schema, contract);
return ZWeb3.wrapContractInstance(schema, contract);
}

// get methods from AST, as there is no info about the modifiers in the ABI
Expand All @@ -133,18 +72,4 @@ export function contractMethodsFromAst(instance: Contract): ContractMethod[] {

return { ...method, hasInitializer: initializer ? true : false };
});
}

function parseArguments(passedArguments, abi) {
const constructorAbi = abi.find((elem) => elem.type === 'constructor') || {};
const constructorArgs = constructorAbi.inputs && constructorAbi.inputs.length > 0 ? constructorAbi.inputs : [];
let givenOptions = {};

if (passedArguments.length === constructorArgs.length + 1) {
const lastArg = passedArguments[passedArguments.length - 1];
if (typeof(lastArg) === 'object') {
givenOptions = passedArguments.pop();
}
}
return [passedArguments, givenOptions];
}
}
11 changes: 11 additions & 0 deletions packages/lib/src/artifacts/Networks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Reference: see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md#list-of-chain-ids
const NETWORKS = {
1: 'mainnet',
2: 'morden',
3: 'ropsten',
4: 'rinkeby',
5: 'goerli',
42: 'kovan'
};

export default NETWORKS
160 changes: 60 additions & 100 deletions packages/lib/src/artifacts/ZWeb3.ts
Original file line number Diff line number Diff line change
@@ -1,183 +1,143 @@
import Logger from '../utils/Logger';
import sleep from '../helpers/sleep';
import Web3 from 'web3';
import { TransactionReceipt } from 'web3/types';
import { Eth, Block, Transaction } from 'web3-eth';
import { Contract } from 'web3-eth-contract';
import { toChecksumAddress } from 'web3-utils';

const log: Logger = new Logger('ZWeb3');

// Reference: see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md#list-of-chain-ids
const NETWORKS = {
1: 'mainnet',
2: 'morden',
3: 'ropsten',
4: 'rinkeby',
5: 'goerli',
42: 'kovan'
};
import IMPLEMENTATIONS from './web3-implementations/Implementations';
import { Web3JSImplementation } from './web3-implementations/Web3JSImplementation';
import ZWeb3Interface from './ZWeb3Interface';
import { EthersImplementation } from './web3-implementations/EthersImplementation';
import Contract from './Contract';

// TS-TODO: Type Web3.
// TS-TODO: Review what could be private in this class.
export default class ZWeb3 {

public static provider;
public static implementation: ZWeb3Interface;

public static initialize(provider: any, implementation: IMPLEMENTATIONS = IMPLEMENTATIONS.WEB3JS): void {

switch (implementation) {
case IMPLEMENTATIONS.WEB3JS:
ZWeb3.implementation = new Web3JSImplementation(provider)
break;
case IMPLEMENTATIONS.ETHERS:
ZWeb3.implementation = new EthersImplementation(provider)
break;
default:
throw new Error('No usable implementation supplied');
}

ZWeb3.provider = ZWeb3.implementation.provider;

public static initialize(provider: any): void {
ZWeb3.provider = provider;
}

// TODO: this.web3 could be cached and initialized lazily?
public static web3(): any {
if (!ZWeb3.provider) return new Web3();

// TODO: improve provider validation for HttpProvider scenarios
return typeof ZWeb3.provider === 'string'
? new Web3(new Web3.providers.HttpProvider(ZWeb3.provider))
: new Web3(ZWeb3.provider);
return ZWeb3.implementation.web3
}

public static sha3(value: string): string {
return Web3.utils.sha3(value);
return ZWeb3.implementation.sha3(value);
}

public static isAddress(address: string): boolean {
return Web3.utils.isAddress(address);
return ZWeb3.implementation.isAddress(address);
}

public static eth(): Eth {
return ZWeb3.web3().eth;
public static eth(): any { // GEORGETODO Probably remove this completely or leave it only when web3js version
return ZWeb3.implementation.eth;
}

public static version(): string {
return ZWeb3.web3().version;
return ZWeb3.implementation.version;
}

public static contract(abi: any, atAddress?: string, options?: any): Contract {
return new (ZWeb3.eth().Contract)(abi, atAddress, options);
public static contract(abi: any, atAddress?: string, options?: any) {
return ZWeb3.implementation.contract(abi, atAddress, options);
}

public static wrapContractInstance(schema: any, instance: any): Contract {
return ZWeb3.implementation.wrapContractInstance(schema, instance);
}

public static async accounts(): Promise<string[]> {
return await ZWeb3.eth().getAccounts();
return await ZWeb3.implementation.accounts();
}

public static async defaultAccount(): Promise<string> {
return (await ZWeb3.accounts())[0];
return await ZWeb3.implementation.defaultAccount();
}

public static toChecksumAddress(address: string): string | null {
if (!address) return null;

if (address.match(/[A-F]/)) {
if (toChecksumAddress(address) !== address) {
throw Error(`Given address \"${address}\" is not a valid Ethereum address or it has not been checksummed correctly.`);
} else return address;
} else {
log.warn(`WARNING: Address ${address} is not checksummed. Consider checksumming it to avoid future warnings or errors.`);
return toChecksumAddress(address);
}
return ZWeb3.implementation.toChecksumAddress(address);
}

public static async estimateGas(params: any): Promise<number> {
return ZWeb3.eth().estimateGas({ ...params });
return ZWeb3.implementation.estimateGas(params);
}

public static async getBalance(address: string): Promise<string> {
return ZWeb3.eth().getBalance(address);
return ZWeb3.implementation.getBalance(address);
}

public static async getCode(address: string): Promise<string> {
return ZWeb3.eth().getCode(address);
return ZWeb3.implementation.getCode(address);
}

public static async hasBytecode(address): Promise<boolean> {
const bytecode = await ZWeb3.getCode(address);
return bytecode.length > 2;
return ZWeb3.implementation.hasBytecode(address)
}

public static async getStorageAt(address: string, position: string): Promise<string> {
return ZWeb3.eth().getStorageAt(address, position);
return ZWeb3.implementation.getStorageAt(address, position);
}

public static async getNode(): Promise<string> {
return ZWeb3.eth().getNodeInfo();
return ZWeb3.implementation.getNode()
}

public static async isGanacheNode(): Promise<boolean> {
const nodeVersion = await ZWeb3.getNode();
return nodeVersion.match(/TestRPC/) !== null;
return ZWeb3.implementation.isGanacheNode()
}

public static async getBlock(filter: string | number): Promise<Block> {
return ZWeb3.eth().getBlock(filter);
public static async getBlock(filter: string | number): Promise<any> {
return ZWeb3.implementation.getBlock(filter);
}

public static async getLatestBlock(): Promise<Block> {
return ZWeb3.getBlock('latest');
public static async getLatestBlock(): Promise<any> {
return ZWeb3.implementation.getLatestBlock();
}

public static async getLatestBlockNumber(): Promise<number> {
return (await ZWeb3.getLatestBlock()).number;
return ZWeb3.implementation.getLatestBlockNumber();
}

public static async isMainnet(): Promise<boolean> {
return (await ZWeb3.getNetworkName()) === 'mainnet';
return ZWeb3.implementation.isMainnet();
}

public static async getNetwork(): Promise<number> {
return ZWeb3.eth().net.getId();
return ZWeb3.implementation.getNetwork();
}

public static async getNetworkName(): Promise<string> {
const networkId = await ZWeb3.getNetwork();
return NETWORKS[networkId] || `dev-${networkId}`;
return ZWeb3.implementation.getNetworkName()
}

public static async sendTransaction(params: any): Promise<TransactionReceipt> {
return ZWeb3.eth().sendTransaction({ ...params });
public static async sendTransaction(params: any): Promise<any> {
return ZWeb3.implementation.sendTransaction(params);
}

public static sendTransactionWithoutReceipt(params: any): Promise<string> {
return new Promise((resolve, reject) => {
ZWeb3.eth().sendTransaction({ ...params }, (error, txHash) => {
if(error) reject(error.message);
else resolve(txHash);
});
});
return ZWeb3.implementation.sendTransactionWithoutReceipt(params);
}

public static async getTransaction(txHash: string): Promise<Transaction> {
return ZWeb3.eth().getTransaction(txHash);
public static async getTransaction(txHash: string): Promise<any> {
return ZWeb3.implementation.getTransaction(txHash);
}

public static async getTransactionReceipt(txHash: string): Promise<TransactionReceipt> {
return ZWeb3.eth().getTransactionReceipt(txHash);
public static async getTransactionReceipt(txHash: string): Promise<any> {
return ZWeb3.implementation.getTransactionReceipt(txHash);
}

public static async getTransactionReceiptWithTimeout(tx: string, timeout: number): Promise<TransactionReceipt> {
return ZWeb3._getTransactionReceiptWithTimeout(tx, timeout, new Date().getTime());
}

private static async _getTransactionReceiptWithTimeout(tx: string, timeout: number, startTime: number): Promise<TransactionReceipt | never> {
const receipt: any = await ZWeb3._tryGettingTransactionReceipt(tx);
if (receipt) {
if (receipt.status) return receipt;
throw new Error(`Transaction: ${tx} exited with an error (status 0).`);
}

await sleep(1000);
const timeoutReached = timeout > 0 && new Date().getTime() - startTime > timeout;
if (!timeoutReached) return await ZWeb3._getTransactionReceiptWithTimeout(tx, timeout, startTime);
throw new Error(`Transaction ${tx} wasn't processed in ${timeout / 1000} seconds!`);
}

private static async _tryGettingTransactionReceipt(tx: string): Promise<TransactionReceipt | never> {
try {
return await ZWeb3.getTransactionReceipt(tx);
} catch (error) {
if (error.message.includes('unknown transaction')) return null;
else throw error;
}
public static async getTransactionReceiptWithTimeout(txHash: string, timeout: number): Promise<any> {
return ZWeb3.implementation.getTransactionReceiptWithTimeout(txHash, timeout)
}
}
Loading