Skip to content

Commit

Permalink
feat: response parse tuple, moved to calldata class, test
Browse files Browse the repository at this point in the history
  • Loading branch information
tabaktoni committed Jan 25, 2023
1 parent 58a506b commit dbe04de
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 146 deletions.
65 changes: 14 additions & 51 deletions __tests__/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,15 +252,15 @@ describe('contract module', () => {
});

describe('Contract interaction', () => {
let ierc20Contract: Contract;
let erc20Echo20Contract: Contract;
const provider = getTestProvider();
const account = getTestAccount(provider);
const classHash = '0x03a00384217ad692b75a7b1bad47f269e7e3597c8498e6db939c952164130c09';
let factory;
let factory: ContractFactory;

beforeAll(async () => {
factory = new ContractFactory(compiledErc20Echo, classHash, account);
ierc20Contract = await factory.deploy(
erc20Echo20Contract = await factory.deploy(
'Token',
'ERC20',
18,
Expand All @@ -273,12 +273,12 @@ describe('Contract interaction', () => {

test('contractFactory.deploy with raw arguments - all types constructor params', () => {
// executed in beforeAll
expect(ierc20Contract instanceof Contract);
expect(erc20Echo20Contract instanceof Contract);
});

test('contractFactory.deploy with callData - all types constructor params', async () => {
// Deploy with callData - OK
ierc20Contract = await factory.deploy(
erc20Echo20Contract = await factory.deploy(
callData({
name: 'Token',
symbol: 'ERC20',
Expand All @@ -289,7 +289,7 @@ describe('Contract interaction', () => {
threshold: 1,
})
);
expect(ierc20Contract instanceof Contract);
expect(erc20Echo20Contract instanceof Contract);
});

test('declareDeploy with callData - all types using felt,uint256,tuple helpers', async () => {
Expand All @@ -307,8 +307,8 @@ describe('Contract interaction', () => {
}),
});

ierc20Contract = new Contract(compiledErc20Echo.abi, deploy.contract_address!, provider);
expect(ierc20Contract instanceof Contract);
erc20Echo20Contract = new Contract(compiledErc20Echo.abi, deploy.contract_address!, provider);
expect(erc20Echo20Contract instanceof Contract);
});

test('Assert helpers and non helpers structure produce same result', async () => {
Expand Down Expand Up @@ -337,7 +337,7 @@ describe('Contract interaction', () => {
expect(JSON.stringify(feltedData)).toBe(JSON.stringify(composedData));
});

test('call contract method with raw arguments and callData arguments', async () => {
test('call contract method array params, with raw arguments and callData arguments', async () => {
const data = {
f1: [1, 2, 3, 4, 5, 6],
u1: [uint256(1000), uint256(2000), uint256(3000), uint256(4000)],
Expand All @@ -349,10 +349,10 @@ describe('Contract interaction', () => {

const compiledData = callData(data);
// call function with compiled data
const result3 = await ierc20Contract.echo2(compiledData);
const result3 = await erc20Echo20Contract.echo2(compiledData);

// call function with raw data as parameters
const result2 = await ierc20Contract.echo2(data.f1, data.u1, data.s2, {
const result2 = await erc20Echo20Contract.echo2(data.f1, data.u1, data.s2, {
parseRequest: true,
parseResponse: true,
});
Expand All @@ -361,25 +361,8 @@ describe('Contract interaction', () => {
console.log(result3);
});

test('call contract method with array params', async () => {
const data = {
f1: [1, 2, 3, 4, 5, 6],
u1: [uint256(1000), uint256(2000), uint256(3000), uint256(4000)],
s2: [
{ discount_fix_bps: 10, discount_transfer_bps: 11 },
{ discount_fix_bps: 20, discount_transfer_bps: 22 },
],
};

const result2 = await ierc20Contract.echo2(data.f1, data.u1, data.s2, {
parseRequest: true,
parseResponse: true,
});
console.log(result2);
});

test('call contract method with encodeShortString, composed struct and nested tuple', async () => {
const result3 = await ierc20Contract.echo3(
const result3 = await erc20Echo20Contract.echo3(
encodeShortString('some text1'),
123,
encodeShortString('some text 2'),
Expand Down Expand Up @@ -459,19 +442,15 @@ describe('Contract interaction', () => {

console.log(compiledNewCallData);

const { balance: uint256Balance } = await ierc20Contract.balanceOf(account.address, {
const { balance: uint256Balance } = await erc20Echo20Contract.balanceOf(account.address, {
parseRequest: true, // user can opt out from validation and paring request/response
parseResponse: true,
});
const bnBalance = uint256ToBN(uint256Balance);
const balance = bnBalance.toString();
expect(balance).toBe('1000000000');

// validating and parsing tuple in request and response are not supported because of possibility of complex nested structure
// users that use tuples can opt-out from request validation and response parsing and do it manually
// user can use callData to compile data with tuples
// TODO: create regex for nested tuple abi type parsing (support tuple in request validation and response parse)
const result = await ierc20Contract.echo(
const result = await erc20Echo20Contract.echo(
callData({
// t1: tuple(felt(10), felt(20)),
f1: felt('someText'),
Expand Down Expand Up @@ -502,21 +481,5 @@ describe('Contract interaction', () => {
})
);
console.log(result);

const test2Data = callData({
f1: [1, 2, 3, 4, 5, 6],
u1: [uint256(1000), uint256(2000), uint256(3000), uint256(4000)],
s2: [
{ discount_fix_bps: 10, discount_transfer_bps: 11 },
{ discount_fix_bps: 20, discount_transfer_bps: 22 },
],
});

// call function with ready call data
const result2 = await ierc20Contract.echo2(test2Data, {
parseRequest: false,
parseResponse: true,
});
console.log(result2);
});
});
93 changes: 3 additions & 90 deletions src/contract/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,17 @@ import { AccountInterface } from '../account';
import { ProviderInterface, defaultProvider } from '../provider';
import {
Abi,
AbiEntry,
Args,
AsyncContractFunction,
BlockTag,
Call,
ContractFunction,
FunctionAbi,
InvokeFunctionResponse,
Overrides,
ParsedStruct,
Result,
StructAbi,
} from '../types';
import { CheckCallData } from '../utils/calldata';
import { BigNumberish, toBN } from '../utils/number';
import { CallOptions, ContractInterface } from './interface';

/**
Expand Down Expand Up @@ -271,7 +267,9 @@ export class Contract implements ContractInterface {
},
blockIdentifier
)
.then((x) => (options.parseResponse ? this.parseResponse(method, x.result) : x.result));
.then((x) =>
options.parseResponse ? this.checkCalldata.parseResponse(method, x.result) : x.result
);
}

public invoke(
Expand Down Expand Up @@ -351,89 +349,4 @@ export class Contract implements ContractInterface {
calldata: this.checkCalldata.compileCalldata(args, inputs),
};
}

/**
* Parse of the response elements that are converted to Object (Struct) by using the abi
*
* @param responseIterator - iterator of the response
* @param type - type of the struct
* @return {BigNumberish | ParsedStruct} - parsed arguments in format that contract is expecting
*/
protected parseResponseStruct(
responseIterator: Iterator<string>,
type: string
): BigNumberish | ParsedStruct {
// check the type of current element
if (type in this.structs && this.structs[type]) {
return this.structs[type].members.reduce((acc, el) => {
// parse each member of the struct (member can felt or nested struct)
acc[el.name] = this.parseResponseStruct(responseIterator, el.type);
return acc;
}, {} as any);
}
return toBN(responseIterator.next().value);
}

/**
* Parse elements of the response and structuring them into one field by using output property from the abi for that method
*
* @param responseIterator - iterator of the response
* @param output - output(field) information from the abi that will be used to parse the data
* @return - parsed response corresponding to the abi structure of the field
*/
protected parseResponseField(
responseIterator: Iterator<string>,
output: AbiEntry,
parsedResult?: Args
): any {
const { name, type } = output;
const parsedDataArr: (BigNumberish | ParsedStruct)[] = [];
switch (true) {
case /_len$/.test(name):
return toBN(responseIterator.next().value).toNumber();
case /\(felt/.test(type):
return type.split(',').reduce((acc) => {
acc.push(toBN(responseIterator.next().value));
return acc;
}, [] as BigNumberish[]);
case /\*/.test(type):
if (parsedResult && parsedResult[`${name}_len`]) {
const arrLen = parsedResult[`${name}_len`] as number;
while (parsedDataArr.length < arrLen) {
parsedDataArr.push(
this.parseResponseStruct(responseIterator, output.type.replace('*', ''))
);
}
}
return parsedDataArr;
case type in this.structs:
return this.parseResponseStruct(responseIterator, type);
default:
return toBN(responseIterator.next().value);
}
}

/**
* Parse elements of the response array and structuring them into response object
*
* @param method - method name
* @param response - response from the method
* @return - parsed response corresponding to the abi
*/
protected parseResponse(method: string, response: string[]): Result {
const { outputs } = this.abi.find((abi) => abi.name === method) as FunctionAbi;
const responseIterator = response.flat()[Symbol.iterator]();
const resultObject = outputs.flat().reduce((acc, output) => {
acc[output.name] = this.parseResponseField(responseIterator, output, acc);
if (acc[output.name] && acc[`${output.name}_len`]) {
delete acc[`${output.name}_len`];
}
return acc;
}, {} as Args);
return Object.entries(resultObject).reduce((acc, [key, value]) => {
acc.push(value);
acc[key] = value;
return acc;
}, [] as Result);
}
}

0 comments on commit dbe04de

Please sign in to comment.