mirror of
https://github.com/cerc-io/watcher-ts
synced 2025-07-27 02:32:07 +00:00
Implement ethereum ABI encode and decode in subgraph (#103)
* Implement ethereum ABI encode in subgraph * Implement ethereum ABI decoding host API * Implement ABI encode decode for array type * Implement ABI encode decode for bytes type
This commit is contained in:
parent
9b1aa29afd
commit
8b913af93f
@ -71,7 +71,7 @@ describe('call handler in mapping code', () => {
|
|||||||
db,
|
db,
|
||||||
indexer,
|
indexer,
|
||||||
provider,
|
provider,
|
||||||
{ event: { block: dummyEventData.block } },
|
{ block: dummyEventData.block },
|
||||||
filePath,
|
filePath,
|
||||||
dummyGraphData
|
dummyGraphData
|
||||||
);
|
);
|
||||||
|
@ -88,7 +88,7 @@ describe('eden wasm loader tests', async () => {
|
|||||||
db,
|
db,
|
||||||
indexer,
|
indexer,
|
||||||
provider,
|
provider,
|
||||||
{ event: { block: dummyEventData.block } },
|
{ block: dummyEventData.block },
|
||||||
filePath,
|
filePath,
|
||||||
data
|
data
|
||||||
));
|
));
|
||||||
@ -203,7 +203,7 @@ describe('eden wasm loader tests', async () => {
|
|||||||
({ exports } = await instantiate(db,
|
({ exports } = await instantiate(db,
|
||||||
indexer,
|
indexer,
|
||||||
provider,
|
provider,
|
||||||
{ event: { block: dummyEventData.block } },
|
{ block: dummyEventData.block },
|
||||||
filePath,
|
filePath,
|
||||||
data
|
data
|
||||||
));
|
));
|
||||||
@ -316,7 +316,7 @@ describe('eden wasm loader tests', async () => {
|
|||||||
db,
|
db,
|
||||||
indexer,
|
indexer,
|
||||||
provider,
|
provider,
|
||||||
{ event: { block: dummyEventData.block } },
|
{ block: dummyEventData.block },
|
||||||
filePath,
|
filePath,
|
||||||
data
|
data
|
||||||
));
|
));
|
||||||
|
70
packages/graph-node/src/eth-abi.test.ts
Normal file
70
packages/graph-node/src/eth-abi.test.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2021 Vulcanize, Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
import path from 'path';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
|
||||||
|
import { BaseProvider } from '@ethersproject/providers';
|
||||||
|
|
||||||
|
import { instantiate } from './loader';
|
||||||
|
import { getTestDatabase, getTestIndexer, getTestProvider } from '../test/utils';
|
||||||
|
import { Database } from './database';
|
||||||
|
import { Indexer } from '../test/utils/indexer';
|
||||||
|
|
||||||
|
describe('ethereum ABI encode decode', () => {
|
||||||
|
let exports: any;
|
||||||
|
let db: Database;
|
||||||
|
let indexer: Indexer;
|
||||||
|
let provider: BaseProvider;
|
||||||
|
let encoded: string;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
db = getTestDatabase();
|
||||||
|
indexer = getTestIndexer();
|
||||||
|
provider = getTestProvider();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load the subgraph example wasm', async () => {
|
||||||
|
const filePath = path.resolve(__dirname, '../test/subgraph/example1/build/Example1/Example1.wasm');
|
||||||
|
const instance = await instantiate(
|
||||||
|
db,
|
||||||
|
indexer,
|
||||||
|
provider,
|
||||||
|
{},
|
||||||
|
filePath
|
||||||
|
);
|
||||||
|
exports = instance.exports;
|
||||||
|
const { _start } = exports;
|
||||||
|
|
||||||
|
// Important to call _start for built subgraphs on instantiation!
|
||||||
|
// TODO: Check api version https://github.com/graphprotocol/graph-node/blob/6098daa8955bdfac597cec87080af5449807e874/runtime/wasm/src/module/mod.rs#L533
|
||||||
|
_start();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should encode data', async () => {
|
||||||
|
const { testEthereumEncode, __getString } = exports;
|
||||||
|
|
||||||
|
const encodedPtr = await testEthereumEncode();
|
||||||
|
encoded = __getString(encodedPtr);
|
||||||
|
|
||||||
|
expect(encoded)
|
||||||
|
.to
|
||||||
|
.equal('0x0000000000000000000000000000000000000000000000000000000000000420583bc7e1bc4799a225663353b82eb36d925399e6ef2799a6a95909f5ab8ac945000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000003f0000000000000000000000000000000000000000000000000000000000000001');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should decode data', async () => {
|
||||||
|
const { testEthereumDecode, __getString, __getArray, __newString } = exports;
|
||||||
|
|
||||||
|
const encodedString = await __newString(encoded);
|
||||||
|
const decodedArrayPtr = await testEthereumDecode(encodedString);
|
||||||
|
const decodedArray = __getArray(decodedArrayPtr);
|
||||||
|
const [addressString, bytesString, bigInt1String, bigInt2String, boolString] = decodedArray.map((value: any) => __getString(value));
|
||||||
|
|
||||||
|
expect(addressString).to.equal('0x0000000000000000000000000000000000000420');
|
||||||
|
expect(bytesString).to.equal('0x583bc7e1bc4799a225663353b82eb36d925399e6ef2799a6a95909f5ab8ac945');
|
||||||
|
expect(bigInt1String).to.equal('62');
|
||||||
|
expect(bigInt2String).to.equal('63');
|
||||||
|
expect(boolString).to.equal('true');
|
||||||
|
});
|
||||||
|
});
|
@ -49,7 +49,7 @@ describe('eth-call wasm tests', () => {
|
|||||||
db,
|
db,
|
||||||
indexer,
|
indexer,
|
||||||
provider,
|
provider,
|
||||||
{ event: { block: dummyEventData.block } },
|
{ block: dummyEventData.block },
|
||||||
filePath,
|
filePath,
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
|
@ -32,7 +32,7 @@ describe('wasm loader tests', () => {
|
|||||||
db,
|
db,
|
||||||
indexer,
|
indexer,
|
||||||
provider,
|
provider,
|
||||||
{ event: {} },
|
{},
|
||||||
filePath
|
filePath
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -109,7 +109,7 @@ describe('wasm loader tests', () => {
|
|||||||
db,
|
db,
|
||||||
indexer,
|
indexer,
|
||||||
provider,
|
provider,
|
||||||
{ event: {} },
|
{},
|
||||||
module
|
module
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -23,7 +23,8 @@ import {
|
|||||||
Block,
|
Block,
|
||||||
fromEthereumValue,
|
fromEthereumValue,
|
||||||
toEthereumValue,
|
toEthereumValue,
|
||||||
resolveEntityFieldConflicts
|
resolveEntityFieldConflicts,
|
||||||
|
getEthereumTypes
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import { Database } from './database';
|
import { Database } from './database';
|
||||||
|
|
||||||
@ -194,6 +195,27 @@ export const instantiate = async (
|
|||||||
console.log('eth_call error', err);
|
console.log('eth_call error', err);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
'ethereum.encode': async (token: number) => {
|
||||||
|
const ethValue = await ethereum.Value.wrap(token);
|
||||||
|
|
||||||
|
const data = await fromEthereumValue(instanceExports, ethValue);
|
||||||
|
const type = await getEthereumTypes(instanceExports, ethValue);
|
||||||
|
|
||||||
|
const encoded = utils.defaultAbiCoder.encode([type], [data]);
|
||||||
|
const encodedString = await __newString(encoded);
|
||||||
|
|
||||||
|
return ByteArray.fromHexString(encodedString);
|
||||||
|
},
|
||||||
|
'ethereum.decode': async (types: number, data: number) => {
|
||||||
|
const typesString = __getString(types);
|
||||||
|
const byteArray = await ByteArray.wrap(data);
|
||||||
|
const bytesHex = await byteArray.toHex();
|
||||||
|
const dataString = __getString(bytesHex);
|
||||||
|
|
||||||
|
const [decoded] = utils.defaultAbiCoder.decode([typesString], dataString);
|
||||||
|
|
||||||
|
return toEthereumValue(instanceExports, utils.ParamType.from(typesString), decoded);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
conversion: {
|
conversion: {
|
||||||
@ -593,6 +615,7 @@ export const instantiate = async (
|
|||||||
const Address: any = instanceExports.Address as any;
|
const Address: any = instanceExports.Address as any;
|
||||||
const ethereum: any = instanceExports.ethereum as any;
|
const ethereum: any = instanceExports.ethereum as any;
|
||||||
const Entity: any = instanceExports.Entity as any;
|
const Entity: any = instanceExports.Entity as any;
|
||||||
|
const ByteArray: any = instanceExports.ByteArray as any;
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
};
|
};
|
||||||
|
@ -42,7 +42,7 @@ describe('numbers wasm tests', () => {
|
|||||||
db,
|
db,
|
||||||
indexer,
|
indexer,
|
||||||
provider,
|
provider,
|
||||||
{ event: {} },
|
{},
|
||||||
filePath
|
filePath
|
||||||
);
|
);
|
||||||
exports = instance.exports;
|
exports = instance.exports;
|
||||||
|
@ -31,7 +31,7 @@ describe('typeConversion wasm tests', () => {
|
|||||||
db,
|
db,
|
||||||
indexer,
|
indexer,
|
||||||
provider,
|
provider,
|
||||||
{ event: {} },
|
{},
|
||||||
filePath
|
filePath
|
||||||
);
|
);
|
||||||
exports = instance.exports;
|
exports = instance.exports;
|
||||||
|
@ -63,6 +63,80 @@ export interface EventData {
|
|||||||
eventIndex: number;
|
eventIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getEthereumTypes = async (instanceExports: any, value: any): Promise<any> => {
|
||||||
|
const {
|
||||||
|
__getArray,
|
||||||
|
Bytes,
|
||||||
|
ethereum
|
||||||
|
} = instanceExports;
|
||||||
|
|
||||||
|
const kind = await value.kind;
|
||||||
|
|
||||||
|
switch (kind) {
|
||||||
|
case EthereumValueKind.ADDRESS:
|
||||||
|
return 'address';
|
||||||
|
|
||||||
|
case EthereumValueKind.BOOL:
|
||||||
|
return 'bool';
|
||||||
|
|
||||||
|
case EthereumValueKind.STRING:
|
||||||
|
return 'string';
|
||||||
|
|
||||||
|
case EthereumValueKind.BYTES:
|
||||||
|
return 'bytes';
|
||||||
|
|
||||||
|
case EthereumValueKind.FIXED_BYTES: {
|
||||||
|
const bytesPtr = await value.toBytes();
|
||||||
|
const bytes = await Bytes.wrap(bytesPtr);
|
||||||
|
const length = await bytes.length;
|
||||||
|
|
||||||
|
return `bytes${length}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EthereumValueKind.INT:
|
||||||
|
return 'int256';
|
||||||
|
|
||||||
|
case EthereumValueKind.UINT: {
|
||||||
|
return 'uint256';
|
||||||
|
}
|
||||||
|
|
||||||
|
case EthereumValueKind.ARRAY: {
|
||||||
|
const valuesPtr = await value.toArray();
|
||||||
|
const [firstValuePtr] = await __getArray(valuesPtr);
|
||||||
|
const firstValue = await ethereum.Value.wrap(firstValuePtr);
|
||||||
|
const type = await getEthereumTypes(instanceExports, firstValue);
|
||||||
|
|
||||||
|
return `${type}[]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EthereumValueKind.FIXED_ARRAY: {
|
||||||
|
const valuesPtr = await value.toArray();
|
||||||
|
const values = await __getArray(valuesPtr);
|
||||||
|
const firstValue = await ethereum.Value.wrap(values[0]);
|
||||||
|
const type = await getEthereumTypes(instanceExports, firstValue);
|
||||||
|
|
||||||
|
return `${type}[${values.length}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EthereumValueKind.TUPLE: {
|
||||||
|
let values = await value.toTuple();
|
||||||
|
values = await __getArray(values);
|
||||||
|
|
||||||
|
const typePromises = values.map(async (value: any) => {
|
||||||
|
value = await ethereum.Value.wrap(value);
|
||||||
|
return getEthereumTypes(instanceExports, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
const types = await Promise.all(typePromises);
|
||||||
|
|
||||||
|
return `tuple(${types.join(',')})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to get value from graph-ts ethereum.Value wasm instance.
|
* Method to get value from graph-ts ethereum.Value wasm instance.
|
||||||
* @param instanceExports
|
* @param instanceExports
|
||||||
@ -71,9 +145,12 @@ export interface EventData {
|
|||||||
*/
|
*/
|
||||||
export const fromEthereumValue = async (instanceExports: any, value: any): Promise<any> => {
|
export const fromEthereumValue = async (instanceExports: any, value: any): Promise<any> => {
|
||||||
const {
|
const {
|
||||||
|
__getArray,
|
||||||
__getString,
|
__getString,
|
||||||
BigInt,
|
BigInt,
|
||||||
Address
|
Address,
|
||||||
|
Bytes,
|
||||||
|
ethereum
|
||||||
} = instanceExports;
|
} = instanceExports;
|
||||||
|
|
||||||
const kind = await value.kind;
|
const kind = await value.kind;
|
||||||
@ -91,9 +168,15 @@ export const fromEthereumValue = async (instanceExports: any, value: any): Promi
|
|||||||
return Boolean(bool);
|
return Boolean(bool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case EthereumValueKind.STRING: {
|
||||||
|
const stringPtr = await value.toString();
|
||||||
|
return __getString(stringPtr);
|
||||||
|
}
|
||||||
|
|
||||||
case EthereumValueKind.BYTES:
|
case EthereumValueKind.BYTES:
|
||||||
case EthereumValueKind.FIXED_BYTES: {
|
case EthereumValueKind.FIXED_BYTES: {
|
||||||
const bytes = await value.toBytes();
|
const bytesPtr = await value.toBytes();
|
||||||
|
const bytes = await Bytes.wrap(bytesPtr);
|
||||||
const bytesStringPtr = await bytes.toHexString();
|
const bytesStringPtr = await bytes.toHexString();
|
||||||
return __getString(bytesStringPtr);
|
return __getString(bytesStringPtr);
|
||||||
}
|
}
|
||||||
@ -107,6 +190,31 @@ export const fromEthereumValue = async (instanceExports: any, value: any): Promi
|
|||||||
return BigNumber.from(bigIntString);
|
return BigNumber.from(bigIntString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case EthereumValueKind.ARRAY:
|
||||||
|
case EthereumValueKind.FIXED_ARRAY: {
|
||||||
|
const valuesPtr = await value.toArray();
|
||||||
|
const values = __getArray(valuesPtr);
|
||||||
|
|
||||||
|
const valuePromises = values.map(async (value: any) => {
|
||||||
|
value = await ethereum.Value.wrap(value);
|
||||||
|
return fromEthereumValue(instanceExports, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(valuePromises);
|
||||||
|
}
|
||||||
|
|
||||||
|
case EthereumValueKind.TUPLE: {
|
||||||
|
let values = await value.toTuple();
|
||||||
|
values = await __getArray(values);
|
||||||
|
|
||||||
|
const valuePromises = values.map(async (value: any) => {
|
||||||
|
value = await ethereum.Value.wrap(value);
|
||||||
|
return fromEthereumValue(instanceExports, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(valuePromises);
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -131,7 +239,26 @@ export const toEthereumValue = async (instanceExports: any, output: utils.ParamT
|
|||||||
id_of_type: getIdOfType
|
id_of_type: getIdOfType
|
||||||
} = instanceExports;
|
} = instanceExports;
|
||||||
|
|
||||||
const { type } = output;
|
const { type, baseType, arrayChildren } = output;
|
||||||
|
|
||||||
|
// For array type.
|
||||||
|
if (baseType === 'array') {
|
||||||
|
const arrayEthereumValueId = await getIdOfType(TypeId.ArrayEthereumValue);
|
||||||
|
|
||||||
|
// Get values for array elements.
|
||||||
|
const ethereumValuePromises = value.map(
|
||||||
|
async (value: any) => toEthereumValue(
|
||||||
|
instanceExports,
|
||||||
|
arrayChildren,
|
||||||
|
value
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const ethereumValues: any[] = await Promise.all(ethereumValuePromises);
|
||||||
|
const ethereumValuesArray = await __newArray(arrayEthereumValueId, ethereumValues);
|
||||||
|
|
||||||
|
return ethereum.Value.fromArray(ethereumValuesArray);
|
||||||
|
}
|
||||||
|
|
||||||
// For tuple type.
|
// For tuple type.
|
||||||
if (type === 'tuple') {
|
if (type === 'tuple') {
|
||||||
@ -140,10 +267,10 @@ export const toEthereumValue = async (instanceExports: any, output: utils.ParamT
|
|||||||
// Get values for struct elements.
|
// Get values for struct elements.
|
||||||
const ethereumValuePromises = output.components
|
const ethereumValuePromises = output.components
|
||||||
.map(
|
.map(
|
||||||
async (component: utils.ParamType) => toEthereumValue(
|
async (component: utils.ParamType, index) => toEthereumValue(
|
||||||
instanceExports,
|
instanceExports,
|
||||||
component,
|
component,
|
||||||
value[component.name]
|
value[index]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Address, log, BigInt, BigDecimal, ByteArray, dataSource, ethereum } from '@graphprotocol/graph-ts';
|
import { Address, log, BigInt, BigDecimal, ByteArray, dataSource, ethereum, Bytes } from '@graphprotocol/graph-ts';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Example1,
|
Example1,
|
||||||
@ -440,3 +440,67 @@ export function testBigIntToHex (value: string): string[] {
|
|||||||
|
|
||||||
return [res1, res2];
|
return [res1, res2];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function testEthereumEncode (): string {
|
||||||
|
const address = ethereum.Value.fromAddress(Address.fromString('0x0000000000000000000000000000000000000420'));
|
||||||
|
const bigInt1 = ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(62));
|
||||||
|
const bigInt2 = ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(63));
|
||||||
|
const bool = ethereum.Value.fromBoolean(true);
|
||||||
|
|
||||||
|
const bytes = ethereum.Value.fromFixedBytes(
|
||||||
|
Bytes.fromByteArray(
|
||||||
|
ByteArray.fromHexString('0x583bc7e1bc4799a225663353b82eb36d925399e6ef2799a6a95909f5ab8ac945')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const fixedSizedArray = ethereum.Value.fromFixedSizedArray([
|
||||||
|
bigInt1,
|
||||||
|
bigInt2
|
||||||
|
]);
|
||||||
|
|
||||||
|
const tupleArray: Array<ethereum.Value> = [
|
||||||
|
fixedSizedArray,
|
||||||
|
bool
|
||||||
|
];
|
||||||
|
|
||||||
|
const tuple = ethereum.Value.fromTuple(changetype<ethereum.Tuple>(tupleArray));
|
||||||
|
|
||||||
|
const token: Array<ethereum.Value> = [
|
||||||
|
address,
|
||||||
|
bytes,
|
||||||
|
tuple
|
||||||
|
];
|
||||||
|
|
||||||
|
const encoded = ethereum.encode(ethereum.Value.fromTuple(changetype<ethereum.Tuple>(token)))!;
|
||||||
|
|
||||||
|
log.debug('encoded: {}', [encoded.toHex()]);
|
||||||
|
|
||||||
|
return encoded.toHex();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function testEthereumDecode (encoded: string): string[] {
|
||||||
|
const decoded = ethereum.decode('(address,bytes32,(uint256[2],bool))', Bytes.fromByteArray(ByteArray.fromHexString(encoded)));
|
||||||
|
const tupleValues = decoded!.toTuple();
|
||||||
|
|
||||||
|
const decodedAddress = tupleValues[0].toAddress();
|
||||||
|
const decodedBytes = tupleValues[1].toBytes();
|
||||||
|
const decodedTuple = tupleValues[2].toTuple();
|
||||||
|
const decodedFixedSizedArray = decodedTuple[0].toArray();
|
||||||
|
const decodedBigInt1 = decodedFixedSizedArray[0].toBigInt();
|
||||||
|
const decodedBigInt2 = decodedFixedSizedArray[1].toBigInt();
|
||||||
|
const decodedBool = decodedTuple[1].toBoolean();
|
||||||
|
|
||||||
|
log.debug('decoded address: {}', [decodedAddress.toHex()]);
|
||||||
|
log.debug('decoded bytes: {}', [decodedBytes.toHex()]);
|
||||||
|
log.debug('decoded bigInt1: {}', [decodedBigInt1.toString()]);
|
||||||
|
log.debug('decoded bigInt2: {}', [decodedBigInt2.toString()]);
|
||||||
|
log.debug('decoded bool: {}', [decodedBool.toString()]);
|
||||||
|
|
||||||
|
return [
|
||||||
|
decodedAddress.toHex(),
|
||||||
|
decodedBytes.toHex(),
|
||||||
|
decodedBigInt1.toString(),
|
||||||
|
decodedBigInt2.toString(),
|
||||||
|
decodedBool.toString()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
@ -21,6 +21,7 @@ export const getDummyEventData = async (): Promise<EventData> => {
|
|||||||
const ethersBlock = await provider.getBlock(blockNumber);
|
const ethersBlock = await provider.getBlock(blockNumber);
|
||||||
|
|
||||||
const block = {
|
const block = {
|
||||||
|
headerId: 0,
|
||||||
blockHash: ethersBlock.hash,
|
blockHash: ethersBlock.hash,
|
||||||
blockNumber: ethersBlock.number.toString(),
|
blockNumber: ethersBlock.number.toString(),
|
||||||
timestamp: '0',
|
timestamp: '0',
|
||||||
@ -41,7 +42,11 @@ export const getDummyEventData = async (): Promise<EventData> => {
|
|||||||
hash: ZERO_HASH,
|
hash: ZERO_HASH,
|
||||||
index: 0,
|
index: 0,
|
||||||
from: ZERO_ADDRESS,
|
from: ZERO_ADDRESS,
|
||||||
to: ZERO_ADDRESS
|
to: ZERO_ADDRESS,
|
||||||
|
value: '0',
|
||||||
|
gasLimit: '0',
|
||||||
|
gasPrice: '0',
|
||||||
|
input: ZERO_HASH
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
Loading…
Reference in New Issue
Block a user