essential-eth 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/classes/Contract.js +14 -61
- package/lib/cjs/classes/test/Contract/foo-abi.d.ts +2 -0
- package/lib/cjs/classes/test/Contract/foo-abi.js +42 -0
- package/lib/cjs/classes/test/Contract/foo.test.d.ts +1 -0
- package/lib/cjs/classes/test/Contract/foo.test.js +25 -0
- package/lib/cjs/classes/utils/encode-decode-transaction.d.ts +3 -0
- package/lib/cjs/classes/utils/encode-decode-transaction.js +100 -0
- package/lib/cjs/index.d.ts +4 -2
- package/lib/cjs/types/Contract.types.d.ts +4 -3
- package/lib/esm/classes/Contract.js +14 -56
- package/lib/esm/classes/test/Contract/foo-abi.d.ts +2 -0
- package/lib/esm/classes/test/Contract/foo-abi.js +33 -0
- package/lib/esm/classes/test/Contract/foo.test.d.ts +1 -0
- package/lib/esm/classes/test/Contract/foo.test.js +14 -0
- package/lib/esm/classes/utils/encode-decode-transaction.d.ts +3 -0
- package/lib/esm/classes/utils/encode-decode-transaction.js +85 -0
- package/lib/esm/index.d.ts +4 -2
- package/lib/esm/types/Contract.types.d.ts +4 -3
- package/package.json +1 -1
|
@@ -10,10 +10,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.Contract = exports.defineReadOnly = exports.BaseContract = void 0;
|
|
13
|
-
const
|
|
14
|
-
const __1 = require("..");
|
|
13
|
+
const encode_decode_transaction_1 = require("./utils/encode-decode-transaction");
|
|
15
14
|
const fetchers_1 = require("./utils/fetchers");
|
|
16
|
-
const hex_to_decimal_1 = require("./utils/hex-to-decimal");
|
|
17
15
|
function estimateGas(txnData) {
|
|
18
16
|
// https://ethereum.stackexchange.com/questions/1570/what-does-intrinsic-gas-too-low-mean/1694
|
|
19
17
|
txnData.split('').reduce((previousValue, currentValue) => {
|
|
@@ -32,78 +30,33 @@ class BaseContract {
|
|
|
32
30
|
this._contractInterface = contractInterface;
|
|
33
31
|
this._provider = signerOrProvider;
|
|
34
32
|
contractInterface
|
|
35
|
-
.filter((
|
|
36
|
-
.forEach((
|
|
37
|
-
if ('name' in
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
.filter((jsonABIArgument) => jsonABIArgument.type === 'function')
|
|
34
|
+
.forEach((jsonABIArgument) => {
|
|
35
|
+
if ('name' in jsonABIArgument &&
|
|
36
|
+
typeof jsonABIArgument.name === 'string') {
|
|
37
|
+
defineReadOnly(this, jsonABIArgument.name, (..._args) => __awaiter(this, void 0, void 0, function* () {
|
|
38
|
+
let functionArguments = _args;
|
|
40
39
|
let options = {};
|
|
41
40
|
// remove options from encoding
|
|
42
41
|
const lastArg = _args[_args.length - 1];
|
|
43
42
|
if (!Array.isArray(lastArg) && typeof lastArg === 'object') {
|
|
44
43
|
options = lastArg;
|
|
45
|
-
|
|
44
|
+
functionArguments = _args.slice(0, _args.length - 1);
|
|
46
45
|
}
|
|
47
|
-
const
|
|
48
|
-
const rawOutputs = argument.outputs;
|
|
49
|
-
/* first 4 bytes will create the data parameter */
|
|
50
|
-
const functionString = `${argument.name}(${argument.inputs.map((input) => input.type)})`;
|
|
51
|
-
// encoding learnt from https://ethereum.stackexchange.com/questions/3514/how-to-call-a-contract-method-using-the-eth-call-json-rpc-api
|
|
52
|
-
const functionHash = hash.update(functionString).digest('hex');
|
|
53
|
-
if (args.length !== argument.inputs.length) {
|
|
54
|
-
throw new Error(`args inputs of "${args.length}" does not match expected length of "${argument.inputs.length}"`);
|
|
55
|
-
}
|
|
56
|
-
const encodedArgs = (args || []).map((arg, i) => {
|
|
57
|
-
let rawArg = arg;
|
|
58
|
-
// remove leading "0x" on address types
|
|
59
|
-
if (argument.inputs[i].type === 'address') {
|
|
60
|
-
rawArg = arg.replace(/^0x/g, '').toLowerCase();
|
|
61
|
-
}
|
|
62
|
-
const argEncoded = rawArg.toString(16);
|
|
63
|
-
const paddedEncodedArg = argEncoded.padStart(64, '0');
|
|
64
|
-
return paddedEncodedArg;
|
|
65
|
-
});
|
|
66
|
-
const functionEncoded = functionHash.slice(0, 8);
|
|
67
|
-
const myData = `0x${functionEncoded}${encodedArgs.join('')}`;
|
|
46
|
+
const data = (0, encode_decode_transaction_1.encodeData)(jsonABIArgument, functionArguments);
|
|
68
47
|
const decimalGas = typeof options.gasLimit === 'number'
|
|
69
48
|
? options.gasLimit /* user passed in "gasLimit" directly */
|
|
70
|
-
: typeof (
|
|
71
|
-
|
|
49
|
+
: typeof (jsonABIArgument === null || jsonABIArgument === void 0 ? void 0 : jsonABIArgument.gas) ===
|
|
50
|
+
'number' /* ABI specified "gas". */
|
|
51
|
+
? estimateGas(data)
|
|
72
52
|
: null;
|
|
73
53
|
const nodeResponse = yield (0, fetchers_1.post)(this._provider._rpcUrl, (0, fetchers_1.buildRPCPostBody)('eth_call', [
|
|
74
|
-
Object.assign({ to: this._address.toLowerCase(), data:
|
|
54
|
+
Object.assign({ to: this._address.toLowerCase(), data: data }, (decimalGas
|
|
75
55
|
? { gas: `0x${decimalGas.toString(16)}` }
|
|
76
56
|
: {})),
|
|
77
57
|
'latest',
|
|
78
58
|
]));
|
|
79
|
-
|
|
80
|
-
const encodedOutputs = nodeResponse.slice(2).match(/.{1,64}/g);
|
|
81
|
-
const outputs = (encodedOutputs || []).map((output, i) => {
|
|
82
|
-
const outputType = (rawOutputs || [])[i].type;
|
|
83
|
-
switch (outputType) {
|
|
84
|
-
case 'bool':
|
|
85
|
-
switch (output) {
|
|
86
|
-
case '0000000000000000000000000000000000000000000000000000000000000001':
|
|
87
|
-
return true;
|
|
88
|
-
case '0000000000000000000000000000000000000000000000000000000000000000':
|
|
89
|
-
return false;
|
|
90
|
-
default:
|
|
91
|
-
throw new Error(`boolean response of ${output} not defined`);
|
|
92
|
-
}
|
|
93
|
-
case 'address':
|
|
94
|
-
/* address types have 26 leading zeroes to remove */
|
|
95
|
-
return (0, __1.toChecksumAddress)(`0x${output.slice(24)}`);
|
|
96
|
-
case 'uint256':
|
|
97
|
-
return (0, __1.tinyBig)((0, hex_to_decimal_1.hexToDecimal)(`0x${output}`));
|
|
98
|
-
case 'bytes32':
|
|
99
|
-
return `0x${output}`;
|
|
100
|
-
case 'uint8':
|
|
101
|
-
return Number((0, hex_to_decimal_1.hexToDecimal)(`0x${output}`));
|
|
102
|
-
default:
|
|
103
|
-
throw new Error(`essential-eth does not yet support "${outputType}" inputs. Make a PR today!"`);
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
return outputs.length === 1 ? outputs[0] : outputs;
|
|
59
|
+
return (0, encode_decode_transaction_1.decodeRPCResponse)(jsonABIArgument, nodeResponse);
|
|
107
60
|
}));
|
|
108
61
|
}
|
|
109
62
|
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// https://docs.soliditylang.org/en/v0.6.8/abi-spec.html#examples
|
|
3
|
+
// contract Foo {
|
|
4
|
+
// function bar(bytes3[2] memory) public pure {}
|
|
5
|
+
// function baz(uint32 x, bool y) public pure returns (bool r) { r = x > 32 || y; }
|
|
6
|
+
// function sam(bytes memory, bool, uint[] memory) public pure {}
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.fooABI = void 0;
|
|
9
|
+
// }
|
|
10
|
+
exports.fooABI = [
|
|
11
|
+
{
|
|
12
|
+
inputs: [
|
|
13
|
+
{
|
|
14
|
+
internalType: 'bytes3[2]',
|
|
15
|
+
name: 'memory',
|
|
16
|
+
type: 'bytes3[2]',
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
name: 'bar',
|
|
20
|
+
outputs: [],
|
|
21
|
+
type: 'function',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
inputs: [
|
|
25
|
+
{ internalType: 'uint32', name: 'x', type: 'uint32' },
|
|
26
|
+
{ internalType: 'bool', name: 'y', type: 'bool' },
|
|
27
|
+
],
|
|
28
|
+
name: 'baz',
|
|
29
|
+
outputs: [{ internalType: 'bool', name: 'r', type: 'bool' }],
|
|
30
|
+
type: 'function',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
inputs: [
|
|
34
|
+
{ internalType: 'bytes', name: 'memory', type: 'bytes' },
|
|
35
|
+
{ internalType: 'bool', name: '', type: 'bool' },
|
|
36
|
+
{ internalType: 'uint[]', name: 'memory', type: 'uint[]' },
|
|
37
|
+
],
|
|
38
|
+
name: 'sam',
|
|
39
|
+
outputs: [],
|
|
40
|
+
type: 'function',
|
|
41
|
+
},
|
|
42
|
+
];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const encode_decode_transaction_1 = require("../../utils/encode-decode-transaction");
|
|
4
|
+
const foo_abi_1 = require("./foo-abi");
|
|
5
|
+
describe('foo encode', () => {
|
|
6
|
+
it('encodes "baz" function', () => {
|
|
7
|
+
const jsonABIArgument = foo_abi_1.fooABI.find((abi) => abi.name === 'baz');
|
|
8
|
+
const encoded = (0, encode_decode_transaction_1.encodeData)(jsonABIArgument, [69, true]);
|
|
9
|
+
expect(encoded).toBe('0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001');
|
|
10
|
+
});
|
|
11
|
+
it('encodes "bar" function', () => {
|
|
12
|
+
const jsonABIArgument = foo_abi_1.fooABI.find((abi) => abi.name === 'bar');
|
|
13
|
+
const encoded = (0, encode_decode_transaction_1.encodeData)(jsonABIArgument, ['abc', 'def']);
|
|
14
|
+
expect(encoded).toBe('0xfce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000');
|
|
15
|
+
});
|
|
16
|
+
// it('encodes "sam" function', () => {
|
|
17
|
+
// const jsonABIArgument = fooABI.find(
|
|
18
|
+
// (abi) => abi.name === 'sam',
|
|
19
|
+
// ) as JSONABIArgument;
|
|
20
|
+
// const encoded = encodeData(jsonABIArgument, ['dave', true, [1, 2, 3]]);
|
|
21
|
+
// expect(encoded).toBe(
|
|
22
|
+
// '0xa5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003',
|
|
23
|
+
// );
|
|
24
|
+
// });
|
|
25
|
+
});
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { JSONABIArgument } from '../../types/Contract.types';
|
|
2
|
+
export declare function encodeData(jsonABIArgument: JSONABIArgument, args: any[]): string;
|
|
3
|
+
export declare function decodeRPCResponse(jsonABIArgument: JSONABIArgument, nodeResponse: string): string | number | boolean | import("../..").TinyBig | (string | number | boolean | import("../..").TinyBig)[];
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.decodeRPCResponse = exports.encodeData = void 0;
|
|
4
|
+
const sha3_1 = require("sha3");
|
|
5
|
+
const __1 = require("../..");
|
|
6
|
+
const hex_to_decimal_1 = require("./hex-to-decimal");
|
|
7
|
+
const hexTrue = '0000000000000000000000000000000000000000000000000000000000000001';
|
|
8
|
+
const hexFalse = '0000000000000000000000000000000000000000000000000000000000000000';
|
|
9
|
+
function expandType(type) {
|
|
10
|
+
// https://docs.soliditylang.org/en/v0.8.7/types.html#integers
|
|
11
|
+
if (type === 'uint[]') {
|
|
12
|
+
return 'uint256[]';
|
|
13
|
+
}
|
|
14
|
+
else if (type === 'int[]') {
|
|
15
|
+
return 'int256[]';
|
|
16
|
+
}
|
|
17
|
+
return type;
|
|
18
|
+
}
|
|
19
|
+
function encodeData(jsonABIArgument, args) {
|
|
20
|
+
const hash = new sha3_1.Keccak(256);
|
|
21
|
+
/* first 4 bytes will create the data parameter */
|
|
22
|
+
const functionString = `${jsonABIArgument.name}(${jsonABIArgument.inputs.map((input) => expandType(input.type))})`;
|
|
23
|
+
// encoding learnt from https://ethereum.stackexchange.com/questions/3514/how-to-call-a-contract-method-using-the-eth-call-json-rpc-api
|
|
24
|
+
const functionHash = hash.update(functionString).digest('hex');
|
|
25
|
+
// no arrays
|
|
26
|
+
const jsonABIInputsLength = jsonABIArgument.inputs.length;
|
|
27
|
+
let shouldValidateInputLength = true;
|
|
28
|
+
// inputs contains 1 or more arrays
|
|
29
|
+
if (jsonABIArgument.inputs.find((input) => input.type.includes('['))) {
|
|
30
|
+
shouldValidateInputLength = false;
|
|
31
|
+
}
|
|
32
|
+
if (shouldValidateInputLength && args.length !== jsonABIInputsLength) {
|
|
33
|
+
throw new Error(`args inputs of "${args.length}" does not match expected length of "${jsonABIArgument.inputs.length}"`);
|
|
34
|
+
}
|
|
35
|
+
const argsWithTypes = (jsonABIArgument.inputs || []).reduce((acc, input, i) => {
|
|
36
|
+
var _a;
|
|
37
|
+
if (input.type.includes('[')) {
|
|
38
|
+
// strip array and length like "[2]" from type
|
|
39
|
+
const basicType = (_a = /([^[]*)\[.*$/g.exec(input.type)) === null || _a === void 0 ? void 0 : _a[1];
|
|
40
|
+
args.forEach((arg) => {
|
|
41
|
+
acc = acc.concat([[arg, basicType]]);
|
|
42
|
+
});
|
|
43
|
+
return acc;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
return acc.concat([[args[i], input.type]]);
|
|
47
|
+
}
|
|
48
|
+
}, []);
|
|
49
|
+
const encodedArgs = argsWithTypes.map(([arg, inputType]) => {
|
|
50
|
+
let rawArg = arg;
|
|
51
|
+
if (inputType === 'bool') {
|
|
52
|
+
return arg ? hexTrue : hexFalse;
|
|
53
|
+
}
|
|
54
|
+
else if (inputType.startsWith('bytes')) {
|
|
55
|
+
// encode each character to hex
|
|
56
|
+
const argEncoded = rawArg
|
|
57
|
+
.split('')
|
|
58
|
+
.map((character) => character.charCodeAt(0).toString(16))
|
|
59
|
+
.join('');
|
|
60
|
+
const paddedEncodedArg = argEncoded.padEnd(64, '0');
|
|
61
|
+
return paddedEncodedArg;
|
|
62
|
+
}
|
|
63
|
+
// remove leading "0x" on address types
|
|
64
|
+
else if (inputType === 'address') {
|
|
65
|
+
rawArg = arg.replace(/^0x/g, '').toLowerCase();
|
|
66
|
+
}
|
|
67
|
+
const argEncoded = rawArg.toString(16);
|
|
68
|
+
const paddedEncodedArg = argEncoded.padStart(64, '0');
|
|
69
|
+
return paddedEncodedArg;
|
|
70
|
+
});
|
|
71
|
+
const functionEncoded = functionHash.slice(0, 8);
|
|
72
|
+
const data = `0x${functionEncoded}${encodedArgs.join('')}`;
|
|
73
|
+
return data;
|
|
74
|
+
}
|
|
75
|
+
exports.encodeData = encodeData;
|
|
76
|
+
function decodeRPCResponse(jsonABIArgument, nodeResponse) {
|
|
77
|
+
const rawOutputs = jsonABIArgument.outputs;
|
|
78
|
+
// chunk response every 64 characters
|
|
79
|
+
const encodedOutputs = nodeResponse.slice(2).match(/.{1,64}/g);
|
|
80
|
+
const outputs = (encodedOutputs || []).map((output, i) => {
|
|
81
|
+
const outputType = (rawOutputs || [])[i].type;
|
|
82
|
+
switch (outputType) {
|
|
83
|
+
case 'bool':
|
|
84
|
+
return output === hexTrue;
|
|
85
|
+
case 'address':
|
|
86
|
+
/* address types have 26 leading zeroes to remove */
|
|
87
|
+
return (0, __1.toChecksumAddress)(`0x${output.slice(24)}`);
|
|
88
|
+
case 'uint256':
|
|
89
|
+
return (0, __1.tinyBig)((0, hex_to_decimal_1.hexToDecimal)(`0x${output}`));
|
|
90
|
+
case 'bytes32':
|
|
91
|
+
return `0x${output}`;
|
|
92
|
+
case 'uint8':
|
|
93
|
+
return Number((0, hex_to_decimal_1.hexToDecimal)(`0x${output}`));
|
|
94
|
+
default:
|
|
95
|
+
throw new Error(`essential-eth does not yet support "${outputType}" inputs. Make a PR today!"`);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
return outputs.length === 1 ? outputs[0] : outputs;
|
|
99
|
+
}
|
|
100
|
+
exports.decodeRPCResponse = decodeRPCResponse;
|
package/lib/cjs/index.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { Transaction } from 'ethers';
|
|
1
2
|
import { Contract } from './classes/Contract';
|
|
2
3
|
import { JsonRpcProvider, jsonRpcProvider } from './providers/JsonRpcProvider';
|
|
3
4
|
import { tinyBig, TinyBig } from './shared/tiny-big/tiny-big';
|
|
4
|
-
import {
|
|
5
|
+
import { Block } from './types/block.types';
|
|
6
|
+
import { JSONABI, JSONABIArgument } from './types/Contract.types';
|
|
5
7
|
import { etherToWei } from './utils/ether-to-wei';
|
|
6
8
|
import { isAddress } from './utils/is-address';
|
|
7
9
|
import { toChecksumAddress } from './utils/to-checksum-address';
|
|
8
10
|
import { weiToEther } from './utils/wei-to-ether';
|
|
9
|
-
export { jsonRpcProvider, JsonRpcProvider, toChecksumAddress, isAddress, etherToWei, weiToEther, tinyBig, TinyBig, Contract, JSONABI, };
|
|
11
|
+
export { jsonRpcProvider, JsonRpcProvider, toChecksumAddress, isAddress, etherToWei, weiToEther, tinyBig, TinyBig, Contract, JSONABI, Block, JSONABIArgument, Transaction, };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export declare type ContractTypes = 'bool' | 'bytes32' | 'bytes32[]' | 'address' | 'address payable' | 'address[4]' | 'address[100]' | 'uint256' | 'uint256[100]' | 'uint8' | 'uint32' |
|
|
1
|
+
export declare type ContractTypes = 'bool' | 'bytes1' | 'bytes2' | 'bytes3' | 'bytes4' | 'bytes5' | 'bytes6' | 'bytes7' | 'bytes8' | 'bytes9' | 'bytes10' | 'bytes11' | 'bytes12' | 'bytes13' | 'bytes14' | 'bytes15' | 'bytes16' | 'bytes17' | 'bytes18' | 'bytes19' | 'bytes20' | 'bytes21' | 'bytes22' | 'bytes23' | 'bytes24' | 'bytes25' | 'bytes26' | 'bytes27' | 'bytes28' | 'bytes29' | 'bytes30' | 'bytes31' | 'bytes32' | 'bytes32[]' | 'address' | 'address payable' | 'address[4]' | 'address[100]' | 'uint256' | 'uint256[100]' | 'uint8' | 'uint32' | string;
|
|
2
2
|
export declare type ContractInterface = JSONABI;
|
|
3
3
|
export declare type ContractFunction<T = any> = (...args: Array<any>) => Promise<T>;
|
|
4
|
-
export declare type
|
|
4
|
+
export declare type JSONABIArgument = {
|
|
5
5
|
anonymous?: false;
|
|
6
6
|
inputs: {
|
|
7
7
|
internalType?: ContractTypes | string;
|
|
@@ -18,4 +18,5 @@ export declare type JSONABI = {
|
|
|
18
18
|
stateMutability?: 'view' | 'nonpayable' | string;
|
|
19
19
|
type: 'function' | 'event' | 'constructor' | 'error';
|
|
20
20
|
gas?: number;
|
|
21
|
-
}
|
|
21
|
+
};
|
|
22
|
+
export declare type JSONABI = JSONABIArgument[];
|
|
@@ -7,10 +7,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import {
|
|
11
|
-
import { tinyBig, toChecksumAddress } from '..';
|
|
10
|
+
import { decodeRPCResponse, encodeData, } from './utils/encode-decode-transaction';
|
|
12
11
|
import { buildRPCPostBody, post } from './utils/fetchers';
|
|
13
|
-
import { hexToDecimal } from './utils/hex-to-decimal';
|
|
14
12
|
function estimateGas(txnData) {
|
|
15
13
|
txnData.split('').reduce((previousValue, currentValue) => {
|
|
16
14
|
const characterCost = currentValue === '0' ? 4 : 68;
|
|
@@ -23,72 +21,32 @@ export class BaseContract {
|
|
|
23
21
|
this._contractInterface = contractInterface;
|
|
24
22
|
this._provider = signerOrProvider;
|
|
25
23
|
contractInterface
|
|
26
|
-
.filter((
|
|
27
|
-
.forEach((
|
|
28
|
-
if ('name' in
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
.filter((jsonABIArgument) => jsonABIArgument.type === 'function')
|
|
25
|
+
.forEach((jsonABIArgument) => {
|
|
26
|
+
if ('name' in jsonABIArgument &&
|
|
27
|
+
typeof jsonABIArgument.name === 'string') {
|
|
28
|
+
defineReadOnly(this, jsonABIArgument.name, (..._args) => __awaiter(this, void 0, void 0, function* () {
|
|
29
|
+
let functionArguments = _args;
|
|
31
30
|
let options = {};
|
|
32
31
|
const lastArg = _args[_args.length - 1];
|
|
33
32
|
if (!Array.isArray(lastArg) && typeof lastArg === 'object') {
|
|
34
33
|
options = lastArg;
|
|
35
|
-
|
|
34
|
+
functionArguments = _args.slice(0, _args.length - 1);
|
|
36
35
|
}
|
|
37
|
-
const
|
|
38
|
-
const rawOutputs = argument.outputs;
|
|
39
|
-
const functionString = `${argument.name}(${argument.inputs.map((input) => input.type)})`;
|
|
40
|
-
const functionHash = hash.update(functionString).digest('hex');
|
|
41
|
-
if (args.length !== argument.inputs.length) {
|
|
42
|
-
throw new Error(`args inputs of "${args.length}" does not match expected length of "${argument.inputs.length}"`);
|
|
43
|
-
}
|
|
44
|
-
const encodedArgs = (args || []).map((arg, i) => {
|
|
45
|
-
let rawArg = arg;
|
|
46
|
-
if (argument.inputs[i].type === 'address') {
|
|
47
|
-
rawArg = arg.replace(/^0x/g, '').toLowerCase();
|
|
48
|
-
}
|
|
49
|
-
const argEncoded = rawArg.toString(16);
|
|
50
|
-
const paddedEncodedArg = argEncoded.padStart(64, '0');
|
|
51
|
-
return paddedEncodedArg;
|
|
52
|
-
});
|
|
53
|
-
const functionEncoded = functionHash.slice(0, 8);
|
|
54
|
-
const myData = `0x${functionEncoded}${encodedArgs.join('')}`;
|
|
36
|
+
const data = encodeData(jsonABIArgument, functionArguments);
|
|
55
37
|
const decimalGas = typeof options.gasLimit === 'number'
|
|
56
38
|
? options.gasLimit
|
|
57
|
-
: typeof (
|
|
58
|
-
|
|
39
|
+
: typeof (jsonABIArgument === null || jsonABIArgument === void 0 ? void 0 : jsonABIArgument.gas) ===
|
|
40
|
+
'number'
|
|
41
|
+
? estimateGas(data)
|
|
59
42
|
: null;
|
|
60
43
|
const nodeResponse = yield post(this._provider._rpcUrl, buildRPCPostBody('eth_call', [
|
|
61
|
-
Object.assign({ to: this._address.toLowerCase(), data:
|
|
44
|
+
Object.assign({ to: this._address.toLowerCase(), data: data }, (decimalGas
|
|
62
45
|
? { gas: `0x${decimalGas.toString(16)}` }
|
|
63
46
|
: {})),
|
|
64
47
|
'latest',
|
|
65
48
|
]));
|
|
66
|
-
|
|
67
|
-
const outputs = (encodedOutputs || []).map((output, i) => {
|
|
68
|
-
const outputType = (rawOutputs || [])[i].type;
|
|
69
|
-
switch (outputType) {
|
|
70
|
-
case 'bool':
|
|
71
|
-
switch (output) {
|
|
72
|
-
case '0000000000000000000000000000000000000000000000000000000000000001':
|
|
73
|
-
return true;
|
|
74
|
-
case '0000000000000000000000000000000000000000000000000000000000000000':
|
|
75
|
-
return false;
|
|
76
|
-
default:
|
|
77
|
-
throw new Error(`boolean response of ${output} not defined`);
|
|
78
|
-
}
|
|
79
|
-
case 'address':
|
|
80
|
-
return toChecksumAddress(`0x${output.slice(24)}`);
|
|
81
|
-
case 'uint256':
|
|
82
|
-
return tinyBig(hexToDecimal(`0x${output}`));
|
|
83
|
-
case 'bytes32':
|
|
84
|
-
return `0x${output}`;
|
|
85
|
-
case 'uint8':
|
|
86
|
-
return Number(hexToDecimal(`0x${output}`));
|
|
87
|
-
default:
|
|
88
|
-
throw new Error(`essential-eth does not yet support "${outputType}" inputs. Make a PR today!"`);
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
return outputs.length === 1 ? outputs[0] : outputs;
|
|
49
|
+
return decodeRPCResponse(jsonABIArgument, nodeResponse);
|
|
92
50
|
}));
|
|
93
51
|
}
|
|
94
52
|
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const fooABI = [
|
|
2
|
+
{
|
|
3
|
+
inputs: [
|
|
4
|
+
{
|
|
5
|
+
internalType: 'bytes3[2]',
|
|
6
|
+
name: 'memory',
|
|
7
|
+
type: 'bytes3[2]',
|
|
8
|
+
},
|
|
9
|
+
],
|
|
10
|
+
name: 'bar',
|
|
11
|
+
outputs: [],
|
|
12
|
+
type: 'function',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
inputs: [
|
|
16
|
+
{ internalType: 'uint32', name: 'x', type: 'uint32' },
|
|
17
|
+
{ internalType: 'bool', name: 'y', type: 'bool' },
|
|
18
|
+
],
|
|
19
|
+
name: 'baz',
|
|
20
|
+
outputs: [{ internalType: 'bool', name: 'r', type: 'bool' }],
|
|
21
|
+
type: 'function',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
inputs: [
|
|
25
|
+
{ internalType: 'bytes', name: 'memory', type: 'bytes' },
|
|
26
|
+
{ internalType: 'bool', name: '', type: 'bool' },
|
|
27
|
+
{ internalType: 'uint[]', name: 'memory', type: 'uint[]' },
|
|
28
|
+
],
|
|
29
|
+
name: 'sam',
|
|
30
|
+
outputs: [],
|
|
31
|
+
type: 'function',
|
|
32
|
+
},
|
|
33
|
+
];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { encodeData } from '../../utils/encode-decode-transaction';
|
|
2
|
+
import { fooABI } from './foo-abi';
|
|
3
|
+
describe('foo encode', () => {
|
|
4
|
+
it('encodes "baz" function', () => {
|
|
5
|
+
const jsonABIArgument = fooABI.find((abi) => abi.name === 'baz');
|
|
6
|
+
const encoded = encodeData(jsonABIArgument, [69, true]);
|
|
7
|
+
expect(encoded).toBe('0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001');
|
|
8
|
+
});
|
|
9
|
+
it('encodes "bar" function', () => {
|
|
10
|
+
const jsonABIArgument = fooABI.find((abi) => abi.name === 'bar');
|
|
11
|
+
const encoded = encodeData(jsonABIArgument, ['abc', 'def']);
|
|
12
|
+
expect(encoded).toBe('0xfce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000');
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { JSONABIArgument } from '../../types/Contract.types';
|
|
2
|
+
export declare function encodeData(jsonABIArgument: JSONABIArgument, args: any[]): string;
|
|
3
|
+
export declare function decodeRPCResponse(jsonABIArgument: JSONABIArgument, nodeResponse: string): string | number | boolean | import("../..").TinyBig | (string | number | boolean | import("../..").TinyBig)[];
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Keccak } from 'sha3';
|
|
2
|
+
import { tinyBig, toChecksumAddress } from '../..';
|
|
3
|
+
import { hexToDecimal } from './hex-to-decimal';
|
|
4
|
+
const hexTrue = '0000000000000000000000000000000000000000000000000000000000000001';
|
|
5
|
+
const hexFalse = '0000000000000000000000000000000000000000000000000000000000000000';
|
|
6
|
+
function expandType(type) {
|
|
7
|
+
if (type === 'uint[]') {
|
|
8
|
+
return 'uint256[]';
|
|
9
|
+
}
|
|
10
|
+
else if (type === 'int[]') {
|
|
11
|
+
return 'int256[]';
|
|
12
|
+
}
|
|
13
|
+
return type;
|
|
14
|
+
}
|
|
15
|
+
export function encodeData(jsonABIArgument, args) {
|
|
16
|
+
const hash = new Keccak(256);
|
|
17
|
+
const functionString = `${jsonABIArgument.name}(${jsonABIArgument.inputs.map((input) => expandType(input.type))})`;
|
|
18
|
+
const functionHash = hash.update(functionString).digest('hex');
|
|
19
|
+
const jsonABIInputsLength = jsonABIArgument.inputs.length;
|
|
20
|
+
let shouldValidateInputLength = true;
|
|
21
|
+
if (jsonABIArgument.inputs.find((input) => input.type.includes('['))) {
|
|
22
|
+
shouldValidateInputLength = false;
|
|
23
|
+
}
|
|
24
|
+
if (shouldValidateInputLength && args.length !== jsonABIInputsLength) {
|
|
25
|
+
throw new Error(`args inputs of "${args.length}" does not match expected length of "${jsonABIArgument.inputs.length}"`);
|
|
26
|
+
}
|
|
27
|
+
const argsWithTypes = (jsonABIArgument.inputs || []).reduce((acc, input, i) => {
|
|
28
|
+
var _a;
|
|
29
|
+
if (input.type.includes('[')) {
|
|
30
|
+
const basicType = (_a = /([^[]*)\[.*$/g.exec(input.type)) === null || _a === void 0 ? void 0 : _a[1];
|
|
31
|
+
args.forEach((arg) => {
|
|
32
|
+
acc = acc.concat([[arg, basicType]]);
|
|
33
|
+
});
|
|
34
|
+
return acc;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
return acc.concat([[args[i], input.type]]);
|
|
38
|
+
}
|
|
39
|
+
}, []);
|
|
40
|
+
const encodedArgs = argsWithTypes.map(([arg, inputType]) => {
|
|
41
|
+
let rawArg = arg;
|
|
42
|
+
if (inputType === 'bool') {
|
|
43
|
+
return arg ? hexTrue : hexFalse;
|
|
44
|
+
}
|
|
45
|
+
else if (inputType.startsWith('bytes')) {
|
|
46
|
+
const argEncoded = rawArg
|
|
47
|
+
.split('')
|
|
48
|
+
.map((character) => character.charCodeAt(0).toString(16))
|
|
49
|
+
.join('');
|
|
50
|
+
const paddedEncodedArg = argEncoded.padEnd(64, '0');
|
|
51
|
+
return paddedEncodedArg;
|
|
52
|
+
}
|
|
53
|
+
else if (inputType === 'address') {
|
|
54
|
+
rawArg = arg.replace(/^0x/g, '').toLowerCase();
|
|
55
|
+
}
|
|
56
|
+
const argEncoded = rawArg.toString(16);
|
|
57
|
+
const paddedEncodedArg = argEncoded.padStart(64, '0');
|
|
58
|
+
return paddedEncodedArg;
|
|
59
|
+
});
|
|
60
|
+
const functionEncoded = functionHash.slice(0, 8);
|
|
61
|
+
const data = `0x${functionEncoded}${encodedArgs.join('')}`;
|
|
62
|
+
return data;
|
|
63
|
+
}
|
|
64
|
+
export function decodeRPCResponse(jsonABIArgument, nodeResponse) {
|
|
65
|
+
const rawOutputs = jsonABIArgument.outputs;
|
|
66
|
+
const encodedOutputs = nodeResponse.slice(2).match(/.{1,64}/g);
|
|
67
|
+
const outputs = (encodedOutputs || []).map((output, i) => {
|
|
68
|
+
const outputType = (rawOutputs || [])[i].type;
|
|
69
|
+
switch (outputType) {
|
|
70
|
+
case 'bool':
|
|
71
|
+
return output === hexTrue;
|
|
72
|
+
case 'address':
|
|
73
|
+
return toChecksumAddress(`0x${output.slice(24)}`);
|
|
74
|
+
case 'uint256':
|
|
75
|
+
return tinyBig(hexToDecimal(`0x${output}`));
|
|
76
|
+
case 'bytes32':
|
|
77
|
+
return `0x${output}`;
|
|
78
|
+
case 'uint8':
|
|
79
|
+
return Number(hexToDecimal(`0x${output}`));
|
|
80
|
+
default:
|
|
81
|
+
throw new Error(`essential-eth does not yet support "${outputType}" inputs. Make a PR today!"`);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
return outputs.length === 1 ? outputs[0] : outputs;
|
|
85
|
+
}
|
package/lib/esm/index.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { Transaction } from 'ethers';
|
|
1
2
|
import { Contract } from './classes/Contract';
|
|
2
3
|
import { JsonRpcProvider, jsonRpcProvider } from './providers/JsonRpcProvider';
|
|
3
4
|
import { tinyBig, TinyBig } from './shared/tiny-big/tiny-big';
|
|
4
|
-
import {
|
|
5
|
+
import { Block } from './types/block.types';
|
|
6
|
+
import { JSONABI, JSONABIArgument } from './types/Contract.types';
|
|
5
7
|
import { etherToWei } from './utils/ether-to-wei';
|
|
6
8
|
import { isAddress } from './utils/is-address';
|
|
7
9
|
import { toChecksumAddress } from './utils/to-checksum-address';
|
|
8
10
|
import { weiToEther } from './utils/wei-to-ether';
|
|
9
|
-
export { jsonRpcProvider, JsonRpcProvider, toChecksumAddress, isAddress, etherToWei, weiToEther, tinyBig, TinyBig, Contract, JSONABI, };
|
|
11
|
+
export { jsonRpcProvider, JsonRpcProvider, toChecksumAddress, isAddress, etherToWei, weiToEther, tinyBig, TinyBig, Contract, JSONABI, Block, JSONABIArgument, Transaction, };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export declare type ContractTypes = 'bool' | 'bytes32' | 'bytes32[]' | 'address' | 'address payable' | 'address[4]' | 'address[100]' | 'uint256' | 'uint256[100]' | 'uint8' | 'uint32' |
|
|
1
|
+
export declare type ContractTypes = 'bool' | 'bytes1' | 'bytes2' | 'bytes3' | 'bytes4' | 'bytes5' | 'bytes6' | 'bytes7' | 'bytes8' | 'bytes9' | 'bytes10' | 'bytes11' | 'bytes12' | 'bytes13' | 'bytes14' | 'bytes15' | 'bytes16' | 'bytes17' | 'bytes18' | 'bytes19' | 'bytes20' | 'bytes21' | 'bytes22' | 'bytes23' | 'bytes24' | 'bytes25' | 'bytes26' | 'bytes27' | 'bytes28' | 'bytes29' | 'bytes30' | 'bytes31' | 'bytes32' | 'bytes32[]' | 'address' | 'address payable' | 'address[4]' | 'address[100]' | 'uint256' | 'uint256[100]' | 'uint8' | 'uint32' | string;
|
|
2
2
|
export declare type ContractInterface = JSONABI;
|
|
3
3
|
export declare type ContractFunction<T = any> = (...args: Array<any>) => Promise<T>;
|
|
4
|
-
export declare type
|
|
4
|
+
export declare type JSONABIArgument = {
|
|
5
5
|
anonymous?: false;
|
|
6
6
|
inputs: {
|
|
7
7
|
internalType?: ContractTypes | string;
|
|
@@ -18,4 +18,5 @@ export declare type JSONABI = {
|
|
|
18
18
|
stateMutability?: 'view' | 'nonpayable' | string;
|
|
19
19
|
type: 'function' | 'event' | 'constructor' | 'error';
|
|
20
20
|
gas?: number;
|
|
21
|
-
}
|
|
21
|
+
};
|
|
22
|
+
export declare type JSONABI = JSONABIArgument[];
|