Skip to content

Commit

Permalink
fix: contract types, interface, cleanup, extend tests
Browse files Browse the repository at this point in the history
  • Loading branch information
tabaktoni committed Apr 25, 2023
1 parent dfbbd3a commit 02c6b72
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 59 deletions.
63 changes: 55 additions & 8 deletions __tests__/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,19 +474,35 @@ describe('Complex interaction', () => {
};

test('call compiled data', async () => {
const result = await erc20Echo20Contract.echo(CallData.compile(request), {
const calldata = CallData.compile(request);
const args = Object.values(request);

const result = await erc20Echo20Contract.echo(calldata, {
parseRequest: true,
parseResponse: true,
formatResponse,
});

const result2 = await erc20Echo20Contract.echo(...args, {
formatResponse,
});
const result3 = await erc20Echo20Contract.call('echo', calldata, {
formatResponse,
});
const result4 = await erc20Echo20Contract.call('echo', args, {
formatResponse,
});

// Convert request uint256 to match response
const compareRequest = {
...request,
u1: uint256ToBN(request.u1),
au1: request.au1.map((it) => uint256ToBN(it)),
};
expect(json.stringify(compareRequest)).toBe(json.stringify(result));
expect(json.stringify(compareRequest)).toBe(json.stringify(result2));
expect(json.stringify(compareRequest)).toBe(json.stringify(result3));
expect(json.stringify(compareRequest)).toBe(json.stringify(result4));
});

test('invoke compiled data', async () => {
Expand All @@ -496,6 +512,28 @@ describe('Complex interaction', () => {
expect(transaction.status).toBeDefined();
});

// skip on live for performance
test('invoke unit test arguments', async () => {
const calldata = CallData.compile(request);
const args = Object.values(request);

const result = await erc20Echo20Contract.iecho(calldata);
const transaction = await provider.waitForTransaction(result.transaction_hash);
expect(transaction.status).toBeDefined();

const result1 = await erc20Echo20Contract.iecho(...args);
const transaction1 = await provider.waitForTransaction(result1.transaction_hash);
expect(transaction1.status).toBeDefined();

const result2 = await erc20Echo20Contract.invoke('iecho', calldata);
const transaction2 = await provider.waitForTransaction(result2.transaction_hash);
expect(transaction2.status).toBeDefined();

const result3 = await erc20Echo20Contract.invoke('iecho', args);
const transaction3 = await provider.waitForTransaction(result3.transaction_hash);
expect(transaction3.status).toBeDefined();
});

describe('speedup live tests', () => {
test('call parameterized data', async () => {
const result = await erc20Echo20Contract.echo(
Expand Down Expand Up @@ -553,21 +591,22 @@ describe('Complex interaction', () => {
test('populate transaction and call with populated data', async () => {
const args = Object.values(request);
// populateTransaction
const populated1 = await erc20Echo20Contract.populateTransaction.echo(...args);
const populated1 = erc20Echo20Contract.populateTransaction.echo(...args);
// populate
const populated2 = await erc20Echo20Contract.populate('echo', args);
const populated2 = erc20Echo20Contract.populate('echo', args);
// populate with apply
const populated3 = erc20Echo20Contract.populate.apply(erc20Echo20Contract, ['echo', args]);
// populateTransaction with compiledCalldata
const populated4 = await erc20Echo20Contract.populateTransaction.echo(
CallData.compile(request)
);
const calldata = CallData.compile(request);
const populated4 = erc20Echo20Contract.populateTransaction.echo(calldata);
const populated5 = erc20Echo20Contract.populate('echo', calldata);
const expected =
'["474107654995566025798705","123","8","135049554883004558383340439742929429255072943744440858662311072577337126766","203887170123222058415354283980421533276985178030994883159827760142323294308","196343614134218459150194337625778954700414868493373034945803514629145850912","191491606203201332235940470946533476219373216944002683254566549675726417440","150983476482645969577707455338206408996455974968365254240526141964709732462","196916864427988120570407658938236398782031728400132565646592333804118761826","196909666192589839125749789377187946419246316474617716408635151520594095469","2259304674248048077001042434290734","1","1","2","3","4","5","6","1","2","3","4","5","6","7","8","9","10","11","5000","0","1","2","1","2","200","1","2","6","1","2","3","4","5","6","4","1000","0","2000","0","3000","0","4000","0","2","10","11","20","22","2","1","2","3","4","1","2","3","4","3","1","2","3","4","1","2","3","4","1","2","3","4"]';
expect(expected).toBe(json.stringify(populated1.calldata));
expect(expected).toBe(json.stringify(populated2.calldata));
expect(expected).toBe(json.stringify(populated3.calldata));
expect(expected).toBe(json.stringify(populated4.calldata));
expect(expected).toBe(json.stringify(populated5.calldata));

// mark data as compiled (it can be also done manually check defineProperty compiled in CallData.compile)
const compiledCallData = CallData.compile(populated4.calldata);
Expand All @@ -583,8 +622,16 @@ describe('Complex interaction', () => {
});

test('estimate fee', async () => {
const gas = await erc20Echo20Contract.estimateFee.iecho(CallData.compile(request));
expect(gas).toMatchSchemaRef('EstimateFee');
const args = Object.values(request);
const calldata = CallData.compile(request);
const gas1 = await erc20Echo20Contract.estimateFee.iecho(calldata);
const gas2 = await erc20Echo20Contract.estimateFee.iecho(...args);
const gas3 = await erc20Echo20Contract.estimate('iecho', calldata);
const gas4 = await erc20Echo20Contract.estimate('iecho', args);
expect(gas1).toMatchSchemaRef('EstimateFee');
expect(gas2).toMatchSchemaRef('EstimateFee');
expect(gas3).toMatchSchemaRef('EstimateFee');
expect(gas4).toMatchSchemaRef('EstimateFee');
});

test('estimate fee transfer', async () => {
Expand Down
90 changes: 55 additions & 35 deletions src/contract/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ import { AccountInterface } from '../account';
import { ProviderInterface, defaultProvider } from '../provider';
import {
Abi,
ArgsOrCalldata,
AsyncContractFunction,
Call,
Calldata,
ContractFunction,
EstimateFeeResponse,
FunctionAbi,
InvokeFunctionResponse,
Overrides,
RawArgsArray,
Result,
StructAbi,
} from '../types';
Expand Down Expand Up @@ -87,6 +91,15 @@ function buildEstimate(contract: Contract, functionAbi: FunctionAbi): ContractFu
};
}

function getCalldata(args: RawArgsArray | [Calldata] | Calldata, callback: Function): Calldata {
// Check if Calldata in args or args[0] else compile
// @ts-ignore
if (args?.compiled) return args as Calldata;
// @ts-ignore
if (args[0]?.compiled) return args[0] as Calldata;
return callback();
}

/**
* Not used at the moment
*/
Expand Down Expand Up @@ -197,19 +210,27 @@ export class Contract implements ContractInterface {

public async call(
method: string,
args: Array<any> = [],
options: CallOptions = { parseRequest: true, parseResponse: true, formatResponse: undefined }
args: ArgsOrCalldata = [],
{
parseRequest = true,
parseResponse = true,
formatResponse = undefined,
blockIdentifier = undefined,
}: CallOptions = {}
): Promise<Result> {
assert(this.address !== null, 'contract is not connected to an address');
const blockIdentifier = options?.blockIdentifier || undefined;
let calldata = 'compiled' in args ? args : args[0];

if (options.parseRequest && !calldata?.compiled) {
const { inputs } = this.abi.find((abi) => abi.name === method) as FunctionAbi;
const calldata = getCalldata(args, () => {
if (parseRequest) {
const { inputs } = this.abi.find((abi) => abi.name === method) as FunctionAbi;

this.callData.validate('CALL', method, args);
calldata = this.callData.compile(args, inputs);
}
this.callData.validate('CALL', method, args);
return this.callData.compile(args, inputs);
}
// eslint-disable-next-line no-console
console.warn('Call skipped parsing but provided rawArgs, possible malfunction request');
return args;
});

return this.providerOrAccount
.callContract(
Expand All @@ -221,32 +242,34 @@ export class Contract implements ContractInterface {
blockIdentifier
)
.then((x) => {
if (!options.parseResponse) {
if (!parseResponse) {
return x.result;
}
if (options.formatResponse) {
return this.callData.format(method, x.result, options.formatResponse);
if (formatResponse) {
return this.callData.format(method, x.result, formatResponse);
}
return this.callData.parse(method, x.result);
});
}

public invoke(
method: string,
args: Array<any> = [],
options: Overrides = {
parseRequest: true,
}
args: ArgsOrCalldata = [],
{ parseRequest = true, maxFee, nonce, signature }: Overrides = {}
): Promise<InvokeFunctionResponse> {
assert(this.address !== null, 'contract is not connected to an address');
let calldata = 'compiled' in args ? args : args[0];

if (options.parseRequest && !calldata?.compiled) {
const { inputs } = this.abi.find((abi) => abi.name === method) as FunctionAbi;
const calldata = getCalldata(args, () => {
if (parseRequest) {
const { inputs } = this.abi.find((abi) => abi.name === method) as FunctionAbi;

this.callData.validate('INVOKE', method, args);
calldata = this.callData.compile(args, inputs);
}
this.callData.validate('INVOKE', method, args);
return this.callData.compile(args, inputs);
}
// eslint-disable-next-line no-console
console.warn('Invoke skipped parsing but provided rawArgs, possible malfunction request');
return args;
});

const invocation = {
contractAddress: this.address,
Expand All @@ -255,46 +278,43 @@ export class Contract implements ContractInterface {
};
if ('execute' in this.providerOrAccount) {
return this.providerOrAccount.execute(invocation, undefined, {
maxFee: options.maxFee,
nonce: options.nonce,
maxFee,
nonce,
});
}

if (!options.nonce) {
throw new Error(`Nonce is required when invoking a function without an account`);
}

if (!nonce) throw new Error(`Nonce is required when invoking a function without an account`);
// eslint-disable-next-line no-console
console.warn(`Invoking ${method} without an account. This will not work on a public node.`);

return this.providerOrAccount.invokeFunction(
{
...invocation,
signature: options.signature,
signature,
},
{
nonce: options.nonce,
nonce,
}
);
}

public async estimate(method: string, args: Array<any> = []) {
public async estimate(method: string, args: ArgsOrCalldata = []): Promise<EstimateFeeResponse> {
assert(this.address !== null, 'contract is not connected to an address');

if (!args[0]?.compiled) {
if (!getCalldata(args, () => false)) {
this.callData.validate('INVOKE', method, args);
}

const invocation = this.populateTransaction[method](...args);
const invocation = this.populate(method, args);
if ('estimateInvokeFee' in this.providerOrAccount) {
return this.providerOrAccount.estimateInvokeFee(invocation);
}
throw Error('Contract must be connected to the account contract to estimate');
}

public populate(method: string, args: Array<any> = []): Call {
public populate(method: string, args: ArgsOrCalldata = []): Call {
const { inputs } = this.abi.find((abi) => abi.name === method) as FunctionAbi;
const calldata = args?.[0]?.compiled ? args?.[0] : this.callData.compile(args, inputs);
const calldata = getCalldata(args, () => this.callData.compile(args, inputs));

return {
contractAddress: this.address,
Expand Down
27 changes: 17 additions & 10 deletions src/contract/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ import { ProviderInterface } from '../provider';
import { BlockIdentifier } from '../provider/utils';
import {
Abi,
ArgsOrCalldata,
AsyncContractFunction,
ContractFunction,
EstimateFeeResponse,
Invocation,
InvokeFunctionResponse,
Overrides,
Result,
} from '../types';

export type CallOptions = {
blockIdentifier?: BlockIdentifier;
parseRequest: Boolean;
parseResponse: Boolean;
parseRequest?: Boolean;
parseResponse?: Boolean;
formatResponse?: object | null;
};

Expand Down Expand Up @@ -66,43 +69,47 @@ export abstract class ContractInterface {
* @param options optional blockIdentifier
* @returns Result of the call as an array with key value pars
*/
public abstract call(method: string, args?: Array<any>, options?: CallOptions): Promise<Object>;
public abstract call(
method: string,
args?: ArgsOrCalldata,
options?: CallOptions
): Promise<Result>;

/**
* Invokes a method on a contract
*
* @param method name of the method
* @param args Array of the arguments for the invoke
* @param args Array of the arguments for the invoke or Calldata
* @param options
* @returns Add Transaction Response
*/
public abstract invoke(
method: string,
args?: Array<any>,
args?: ArgsOrCalldata,
options?: Overrides
): Promise<InvokeFunctionResponse>;

/**
* Estimates a method on a contract
*
* @param method name of the method
* @param args Array of the arguments for the call
* @param args Array of the arguments for the call or Calldata
* @param options optional blockIdentifier
*/
public abstract estimate(
method: string,
args?: Array<any>,
args?: ArgsOrCalldata,
options?: {
blockIdentifier?: BlockIdentifier;
}
): Promise<any>;
): Promise<EstimateFeeResponse>;

/**
* Calls a method on a contract
*
* @param method name of the method
* @param args Array of the arguments for the call
* @param args Array of the arguments for the call or Calldata
* @returns Invocation object
*/
public abstract populate(method: string, args?: Array<any>): Invocation;
public abstract populate(method: string, args?: ArgsOrCalldata): Invocation;
}
2 changes: 1 addition & 1 deletion src/types/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BigNumberish } from '../../utils/num';
import { Signature } from '../lib';

export type Calldata = string[];
export type Calldata = string[] & { readonly compiled?: boolean };

export type Overrides = {
maxFee?: BigNumberish;
Expand Down
4 changes: 4 additions & 0 deletions src/types/contract.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { Calldata } from './api';
import { RawArgsArray } from './lib';

export type AsyncContractFunction<T = any> = (...args: Array<any>) => Promise<T>;
export type ContractFunction = (...args: Array<any>) => any;
export type Result =
Expand All @@ -8,3 +11,4 @@ export type Result =
| bigint
| string
| boolean;
export type ArgsOrCalldata = RawArgsArray | [Calldata] | Calldata;
Loading

0 comments on commit 02c6b72

Please sign in to comment.