Skip to content

Commit

Permalink
Merge pull request #104 from thepower/revert-103-feat/new_abi_coder
Browse files Browse the repository at this point in the history
Revert "new abi coder: @ethersproject/abi => web3-eth-abi"
  • Loading branch information
jackkru69 authored Nov 28, 2023
2 parents af57d3e + af6094c commit 710c57a
Show file tree
Hide file tree
Showing 21 changed files with 486 additions and 1,647 deletions.
1,453 changes: 8 additions & 1,445 deletions packages/cli/yarn.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/tssdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@ethereumjs/common": "^2.6.4",
"@ethereumjs/tx": "^3.5.2",
"@ethereumjs/vm": "^5.9.2",
"@ethersproject/abi": "^5.6.3",
"@thepowereco/msgpack": "^1.5.3",
"@types/bitcoinjs-lib": "^5.0.0",
"axios": "^0.27.2",
Expand All @@ -41,7 +42,6 @@
"stream-browserify": "^3.0.0",
"tsconfig-paths-webpack-plugin": "^3.5.2",
"util": "^0.12.4",
"web3-eth-abi": "^4.1.4",
"webpack-node-externals": "^3.0.0",
"wif": "^2.0.6"
},
Expand Down
5 changes: 3 additions & 2 deletions packages/tssdk/src/contracts/evm20Contract.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import BigNumber from 'bignumber.js';
import {
AccountKey, AddressApi, EvmContract, EvmCore,
} from '../index';
Expand Down Expand Up @@ -42,7 +43,7 @@ export class Evm20Contract {
return decimals;
}

public async getBalance(owner: string): Promise<bigint> {
public async getBalance(owner: string): Promise<BigNumber> {
const balanceOf = await this.evmContract.scGet('balanceOf', [AddressApi.textAddressToEvmAddress(owner)]);
return balanceOf;
}
Expand All @@ -52,7 +53,7 @@ export class Evm20Contract {
return totalSupply;
}

public async getAllowance(owner: string, spender: string): Promise<bigint> {
public async getAllowance(owner: string, spender: string): Promise<BigNumber> {
const allowance = await this.evmContract.scGet('allowance', [
AddressApi.textAddressToEvmAddress(owner),
AddressApi.textAddressToEvmAddress(spender),
Expand Down
24 changes: 12 additions & 12 deletions packages/tssdk/src/evm/examples/helpers/tx.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
import { Interface, defaultAbiCoder as AbiCoder } from '@ethersproject/abi';
import { AccessListEIP2930TxData, FeeMarketEIP1559TxData, TxData } from '@ethereumjs/tx';
import { encodeFunctionCall } from 'web3-eth-abi';

type TransactionsData = TxData | AccessListEIP2930TxData | FeeMarketEIP1559TxData;

export const encodeFunction = (
method: string,
params: any[],
abi: any,
params?: {
types: any[]
values: unknown[]
},
): string => {
const abiItem = abi?.find((item: any) => item.name === method);

if (!abiItem) {
throw new Error('ABI item not found');
}
const parameters = params?.types ?? [];
const methodWithParameters = `function ${method}(${parameters.join(',')})`;
const signatureHash = new Interface([methodWithParameters]).getSighash(method);
const encodedArgs = AbiCoder.encode(parameters, params?.values ?? []);

const paramStringAbi = encodeFunctionCall(abiItem, params).slice(2);
return paramStringAbi;
return signatureHash + encodedArgs.slice(2);
};

export const encodeDeployment = (
bytecode: string,
params?: {
types: any
types: any[]
values: unknown[]
},
) => {
const deploymentData = `0x${bytecode}`;
if (params) {
const argumentsEncoded = encodeFunctionCall(params.types, params.values);
const argumentsEncoded = AbiCoder.encode(params.types, params.values);
return deploymentData + argumentsEncoded.slice(2);
}
return deploymentData;
Expand Down
51 changes: 30 additions & 21 deletions packages/tssdk/src/helpers/abi.helper.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,40 @@
import { decodeParameters, encodeFunctionCall } from 'web3-eth-abi';

export const encodeFunction = (
method: string,
params: any[],
abi: any,
): string => {
const abiItem = abi?.find((item: any) => item.name === method);
import { defaultAbiCoder as AbiCoder } from '@ethersproject/abi/lib/abi-coder';
import { Interface } from '@ethersproject/abi';

export const getAbiInputsOutputsType = (abi: any, method: string) => {
const abiItem = abi.find((item: any) => item.name === method);
if (!abiItem) {
throw new Error('ABI item not found');
throw new Error('ABI not found');
}

const paramStringAbi = encodeFunctionCall(abiItem, params).slice(2);
return paramStringAbi;
return {
inputs: abiItem.inputs.map((input: any) => input.type),
outputs: abiItem.outputs.map((output: any) => output.type),
outputNames: abiItem.outputs
.filter((output: any) => !!output.name)
.map((output: any) => output.name),
};
};

export const decodeReturnValue = (
method: string,
returnValue: Buffer,
abi: any,
) => {
const abiItem = abi?.find((item: any) => item.name === method);

export const getAbiInputsOutputs = (abi: any, method: string) => {
const abiItem = abi.find((item: any) => item.name === method);
if (!abiItem) {
throw new Error('ABI item not found');
throw new Error('ABI not found');
}

const paramStringAbi: any = decodeParameters(abiItem.outputs, returnValue.toString('hex'));
return paramStringAbi;
return {
inputs: abiItem.inputs,
outputs: abiItem.outputs,
};
};

export const encodeFunction = (
method: string,
params: any[] = [],
inputs: any[] = [],
): string => {
const methodWithParameters = `function ${method}(${inputs.join(',')})`;
const signatureHash = new Interface([methodWithParameters]).getSighash(method);
const paramStringAbi = (params.length ? AbiCoder.encode(inputs, params) : '').slice(2);
return signatureHash.slice(2) + paramStringAbi;
};
68 changes: 61 additions & 7 deletions packages/tssdk/src/libs/evm-api/evm-api.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import VM from '@ethereumjs/vm';
import { Address } from 'ethereumjs-util';
import { defaultAbiCoder as AbiCoder } from '@ethersproject/abi';
import { NetworkApi, TransactionsApi } from '../index';

import { AccountKey } from '../../typings';
import { bnToHex } from '../../helpers/bnHex.helper';
import { decodeReturnValue, encodeFunction } from '../../helpers/abi.helper';
import { encodeFunction, getAbiInputsOutputs, getAbiInputsOutputsType } from '../../helpers/abi.helper';
import { MethodDoesNotExistException } from './exceptions/method-does-not-exist.exception';
import { WrongParamsPassedToMethodException } from './exceptions/wrong-params-passed-to-method.exception';
import { WrongAmountOfArgumentsException } from './exceptions/wrong-amount-of-arguments.exception';
import { isValid } from './validators/is-valid';
import { WrongParamTypeException } from './exceptions/wrong-param-type.exception';

export class EvmApi {
private vm: VM;
Expand Down Expand Up @@ -41,7 +47,16 @@ export class EvmApi {
}

public async scGet(method: string, params: any[]) {
const encodedFunction = encodeFunction(method, params, this.abi);
if (!this.isMethodExist(method)) {
throw new MethodDoesNotExistException();
}

if (!this.isValidParamsPassedToMethod(method, params)) {
throw new WrongParamsPassedToMethodException();
}

const io = getAbiInputsOutputsType(this.abi, method);
const encodedFunction = encodeFunction(method, params, io.inputs);

if (!this.cache.has(this.scAddress)) {
const loadedData = await this.network.loadScCode(this.scAddress);
Expand All @@ -61,15 +76,31 @@ export class EvmApi {
throw greetResult.execResult.exceptionError;
}

const results = decodeReturnValue(method, greetResult.execResult.returnValue, this.abi);
const results = AbiCoder.decode(io.outputs, greetResult.execResult.returnValue);
let returnValue: any = results;

if (io.outputNames.length === results.length) {
returnValue = results.reduce((aggr, item, key) => {
aggr[io.outputNames[key]] = item;
return aggr;
}, {});
}

// eslint-disable-next-line no-underscore-dangle
return results?.__length__ === 1 ? results[0] : results;
return results.length === 1 ? results[0] : returnValue;
}

// Send trx to chain
public async scSet(key: AccountKey, method: string, params: any[] = [], amount = 0) {
const encodedFunction = encodeFunction(method, params, this.abi);
public async scSet(key: AccountKey, method: string, params?: string[], amount = 0) {
if (!this.isMethodExist(method)) {
throw new MethodDoesNotExistException();
}

if (!this.isValidParamsPassedToMethod(method, params)) {
throw new WrongParamsPassedToMethodException();
}

const io = getAbiInputsOutputsType(this.abi, method);
const encodedFunction = encodeFunction(method, params, io.inputs);
const data = Buffer.from(encodedFunction, 'hex');

const tx = await TransactionsApi.composeSCMethodCallTX(
Expand All @@ -87,4 +118,27 @@ export class EvmApi {

return this.network.sendTxAndWaitForResponse(tx);
}

private isMethodExist(method: string): boolean {
return !!this.abi.find((item: any) => item.name === method);
}

private isValidParamsPassedToMethod(method: string, params: string[] = []): boolean {
const { inputs } = getAbiInputsOutputs(this.abi, method);

if (params.length !== inputs.length) {
throw new WrongAmountOfArgumentsException(method, inputs.length, params.length);
}

for (let i = 0; i < inputs.length; i += 1) {
const input = inputs[i];
const param = params[i];

if (!isValid(param, input.type, input.components)) {
throw new WrongParamTypeException(method, input.name);
}
}

return true;
}
}
73 changes: 62 additions & 11 deletions packages/tssdk/src/libs/evm-api/evmCore.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import VM from '@ethereumjs/vm';
import { Address } from 'ethereumjs-util';
import { defaultAbiCoder as AbiCoder } from '@ethersproject/abi';
import { AddressApi, NetworkApi, TransactionsApi } from '../index';

import { AccountKey } from '../../typings';
import { bnToHex } from '../../helpers/bnHex.helper';
import { decodeReturnValue, encodeFunction } from '../../helpers/abi.helper';
import { encodeFunction, getAbiInputsOutputs, getAbiInputsOutputsType } from '../../helpers/abi.helper';
import { MethodDoesNotExistException } from './exceptions/method-does-not-exist.exception';
import { WrongParamsPassedToMethodException } from './exceptions/wrong-params-passed-to-method.exception';
import { WrongAmountOfArgumentsException } from './exceptions/wrong-amount-of-arguments.exception';
import { isValid } from './validators/is-valid';
import { WrongParamTypeException } from './exceptions/wrong-param-type.exception';

export class EvmContract {
private evm: EvmCore;
Expand All @@ -22,13 +28,22 @@ export class EvmContract {
this.code = code;
}

public static async build(evmCore: EvmCore, address: string, abi: any): Promise<EvmContract> {
public static async build(evmCore: EvmCore, address: string, abi:any): Promise<EvmContract> {
const code = await evmCore.network.loadScCode(address);
return new EvmContract(evmCore, address, abi, code);
}

public async scGet(method: string, params: any[] = []) {
const encodedFunction = encodeFunction(method, params, this.abi);
public async scGet(method: string, params: any[]) {
if (!this.isMethodExist(method)) {
throw new MethodDoesNotExistException();
}

if (!this.isValidParamsPassedToMethod(method, params)) {
throw new WrongParamsPassedToMethodException();
}

const io = getAbiInputsOutputsType(this.abi, method);
const encodedFunction = encodeFunction(method, params, io.inputs);

const contractAddress = Address.fromString(AddressApi.textAddressToEvmAddress(this.address));

Expand All @@ -42,23 +57,36 @@ export class EvmContract {
throw getResult.execResult.exceptionError;
}

const results = decodeReturnValue(method, getResult.execResult.returnValue, this.abi);
const results = AbiCoder.decode(io.outputs, getResult.execResult.returnValue);
let returnValue: any = results;

if (io.outputNames.length === results.length) {
returnValue = results.reduce((aggr, item, key) => {
aggr[io.outputNames[key]] = item;
return aggr;
}, {});
}

// eslint-disable-next-line no-underscore-dangle
return results?.__length__ === 1 ? results[0] : results;
return results.length === 1 ? results[0] : returnValue;
}

public async scSet(key: AccountKey, method: string, params: any[] = [], amount = 0, isHTTPSNodesOnly = false, sponsor = '') {
public async scSet(key: AccountKey, method: string, params?: any[], amount = 0, isHTTPSNodesOnly = false, sponsor = '') {
if (!this.isMethodExist(method)) {
throw new MethodDoesNotExistException();
}

if (!this.isValidParamsPassedToMethod(method, params)) {
throw new WrongParamsPassedToMethodException();
}
const addressChain = await this.evm.network.getAddressChain(key.address);
const addressChainNumber = addressChain?.chain;
let workNetwork = this.evm.network;
if (this.evm.network.getChain() !== addressChainNumber) {
workNetwork = new NetworkApi(addressChainNumber);
await workNetwork.bootstrap(isHTTPSNodesOnly);
}

const encodedFunction = encodeFunction(method, params, this.abi);

const io = getAbiInputsOutputsType(this.abi, method);
const encodedFunction = encodeFunction(method, params, io.inputs);
const data = Buffer.from(encodedFunction, 'hex');
const tx = sponsor === '' ?
await TransactionsApi.composeSCMethodCallTX(
Expand Down Expand Up @@ -90,6 +118,29 @@ export class EvmContract {
const res = await workNetwork.sendTxAndWaitForResponse(tx);
return res;
}

private isMethodExist(method: string): boolean {
return !!this.abi.find((item: any) => item.name === method);
}

private isValidParamsPassedToMethod(method: string, params: string[] = []): boolean {
const { inputs } = getAbiInputsOutputs(this.abi, method);

if (params.length !== inputs.length) {
throw new WrongAmountOfArgumentsException(method, inputs.length, params.length);
}

for (let i = 0; i < inputs.length; i += 1) {
const input = inputs[i];
const param = params[i];

if (!isValid(param, input.type, input.components)) {
throw new WrongParamTypeException(method, input.name);
}
}

return true;
}
}

export class EvmCore {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export class MethodDoesNotExistException extends Error {
public static MESSAGE = 'METHOD_DOES_NOT_EXIST_IN_ABI';

constructor() {
super(MethodDoesNotExistException.MESSAGE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { formatString } from '../../../helpers/string';

export class WrongAmountOfArgumentsException extends Error {
public static MESSAGE = 'The ?? method expected ?? arguments but received ?? arguments';

constructor(method: string, expectedAmountOfArgs: number, receivedAmountOfArgs: number) {
super(formatString(WrongAmountOfArgumentsException.MESSAGE, method, expectedAmountOfArgs, receivedAmountOfArgs));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { formatString } from '../../../helpers/string';

export class WrongParamTypeException extends TypeError {
public static MESSAGE = 'Wrong type of param (??) passed to method (??)';

public static CODE = 'WRONG_PARAM_TYPE';

constructor(method: string, paramName: string) {
super(formatString(WrongParamTypeException.MESSAGE, paramName, method));
}
}
Loading

0 comments on commit 710c57a

Please sign in to comment.