mirror of
https://github.com/cerc-io/watcher-ts
synced 2026-09-10 01:34:06 +00:00
Implement eth_call for methods returning struct type (#59)
* Handle tuple return type in ethereum host API * Update graph-cli version to fix eth_call error * Handle all types in struct based on abi Co-authored-by: prathamesh0 <prathamesh.musale0@gmail.com>
This commit is contained in:
@@ -5,8 +5,10 @@
|
||||
import path from 'path';
|
||||
import chai, { assert, expect } from 'chai';
|
||||
import spies from 'chai-spies';
|
||||
import { utils } from 'ethers';
|
||||
|
||||
import { getDummyEventData, getTestDatabase } from '../test/utils';
|
||||
import abi from '../test/subgraph/example1/build/Example1/abis/Example1.json';
|
||||
import { instantiate } from './loader';
|
||||
import { createEvent, createBlock, Block } from './utils';
|
||||
import { Database } from './database';
|
||||
@@ -66,18 +68,14 @@ describe('call handler in mapping code', () => {
|
||||
_start();
|
||||
|
||||
// Create event params data.
|
||||
dummyEventData.eventParams = [
|
||||
{
|
||||
name: 'param1',
|
||||
value: 'abc',
|
||||
kind: 'string'
|
||||
},
|
||||
{
|
||||
name: 'param2',
|
||||
value: BigInt(123),
|
||||
kind: 'uint256'
|
||||
}
|
||||
];
|
||||
const contractInterface = new utils.Interface(abi);
|
||||
const eventFragment = contractInterface.getEvent('Test(string,uint8)');
|
||||
dummyEventData.inputs = eventFragment.inputs;
|
||||
|
||||
dummyEventData.event = {
|
||||
param1: 'abc',
|
||||
param2: BigInt(123)
|
||||
};
|
||||
|
||||
// Dummy contract address string.
|
||||
const contractAddress = '0xCA6D29232D1435D8198E3E5302495417dD073d61';
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//
|
||||
|
||||
import assert from 'assert';
|
||||
import { ethers } from 'ethers';
|
||||
import { ethers, utils } from 'ethers';
|
||||
import path from 'path';
|
||||
import chai from 'chai';
|
||||
import spies from 'chai-spies';
|
||||
@@ -65,6 +65,8 @@ describe('eden wasm loader tests', async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const contractInterface = new utils.Interface(edenNetworkAbi);
|
||||
|
||||
it('should load the subgraph network wasm', async () => {
|
||||
const filePath = path.resolve(__dirname, '../test/subgraph/eden/EdenNetwork/EdenNetwork.wasm');
|
||||
({ exports } = await instantiate(db, { event: { block: dummyEventData.block } }, filePath, data));
|
||||
@@ -78,43 +80,17 @@ describe('eden wasm loader tests', async () => {
|
||||
} = exports;
|
||||
|
||||
// Create dummy SlotClaimedEvent params.
|
||||
dummyEventData.eventParams = [
|
||||
{
|
||||
name: 'slot',
|
||||
kind: 'uint8',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
name: 'owner',
|
||||
kind: 'address',
|
||||
value: ZERO_ADDRESS
|
||||
},
|
||||
{
|
||||
name: 'delegate',
|
||||
kind: 'address',
|
||||
value: ZERO_ADDRESS
|
||||
},
|
||||
{
|
||||
name: 'newBidAmount',
|
||||
kind: 'uint128',
|
||||
value: BigInt(1)
|
||||
},
|
||||
{
|
||||
name: 'oldBidAmount',
|
||||
kind: 'uint128',
|
||||
value: BigInt(1)
|
||||
},
|
||||
{
|
||||
name: 'taxNumerator',
|
||||
kind: 'uint16',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
name: 'taxDenominator',
|
||||
kind: 'uint16',
|
||||
value: 1
|
||||
}
|
||||
];
|
||||
const eventFragment = contractInterface.getEvent('SlotClaimed(indexed uint8,indexed address,indexed address,uint128,uint128,uint16,uint16)');
|
||||
dummyEventData.inputs = eventFragment.inputs;
|
||||
dummyEventData.event = {
|
||||
slot: 0,
|
||||
owner: ZERO_ADDRESS,
|
||||
delegate: ZERO_ADDRESS,
|
||||
newBidAmount: BigInt(1),
|
||||
oldBidAmount: BigInt(1),
|
||||
taxNumerator: 1,
|
||||
taxDenominator: 1
|
||||
};
|
||||
|
||||
// Create an ethereum event SlotClaimedEvent to be passed to handler.
|
||||
const slotClaimedEvent = await createEvent(exports, contractAddress, dummyEventData);
|
||||
@@ -128,28 +104,14 @@ describe('eden wasm loader tests', async () => {
|
||||
} = exports;
|
||||
|
||||
// Create dummy SlotDelegateUpdatedEvent params.
|
||||
dummyEventData.eventParams = [
|
||||
{
|
||||
name: 'slot',
|
||||
kind: 'uint8',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
name: 'owner',
|
||||
kind: 'address',
|
||||
value: ZERO_ADDRESS
|
||||
},
|
||||
{
|
||||
name: 'newDelegate',
|
||||
kind: 'address',
|
||||
value: ZERO_ADDRESS
|
||||
},
|
||||
{
|
||||
name: 'oldDelegate',
|
||||
kind: 'address',
|
||||
value: ZERO_ADDRESS
|
||||
}
|
||||
];
|
||||
const eventFragment = contractInterface.getEvent('SlotDelegateUpdated(indexed uint8,indexed address,indexed address,address)');
|
||||
dummyEventData.inputs = eventFragment.inputs;
|
||||
dummyEventData.event = {
|
||||
slot: 0,
|
||||
owner: ZERO_ADDRESS,
|
||||
newDelegate: ZERO_ADDRESS,
|
||||
oldDelegate: ZERO_ADDRESS
|
||||
};
|
||||
|
||||
// Create an ethereum event SlotDelegateUpdatedEvent to be passed to handler.
|
||||
const slotClaimedEvent = await createEvent(exports, contractAddress, dummyEventData);
|
||||
@@ -163,18 +125,12 @@ describe('eden wasm loader tests', async () => {
|
||||
} = exports;
|
||||
|
||||
// Create dummy StakeEvent params.
|
||||
dummyEventData.eventParams = [
|
||||
{
|
||||
name: 'staker',
|
||||
kind: 'address',
|
||||
value: '0xDC7d7A8920C8Eecc098da5B7522a5F31509b5Bfc'
|
||||
},
|
||||
{
|
||||
name: 'stakeAmount',
|
||||
kind: 'uint256',
|
||||
value: BigInt(1)
|
||||
}
|
||||
];
|
||||
const eventFragment = contractInterface.getEvent('Stake(indexed address,uint256)');
|
||||
dummyEventData.inputs = eventFragment.inputs;
|
||||
dummyEventData.event = {
|
||||
staker: '0xDC7d7A8920C8Eecc098da5B7522a5F31509b5Bfc',
|
||||
stakeAmount: BigInt(1)
|
||||
};
|
||||
|
||||
// Create an ethereum event StakeEvent to be passed to handler.
|
||||
const stakeEvent = await createEvent(exports, contractAddress, dummyEventData);
|
||||
@@ -188,18 +144,12 @@ describe('eden wasm loader tests', async () => {
|
||||
} = exports;
|
||||
|
||||
// Create dummy UnstakeEvent params.
|
||||
dummyEventData.eventParams = [
|
||||
{
|
||||
name: 'staker',
|
||||
kind: 'address',
|
||||
value: ZERO_ADDRESS
|
||||
},
|
||||
{
|
||||
name: 'unstakedAmount',
|
||||
kind: 'uin256',
|
||||
value: BigInt(1)
|
||||
}
|
||||
];
|
||||
const eventFragment = contractInterface.getEvent('Unstake(indexed address,uint256)');
|
||||
dummyEventData.inputs = eventFragment.inputs;
|
||||
dummyEventData.event = {
|
||||
staker: ZERO_ADDRESS,
|
||||
unstakedAmount: BigInt(1)
|
||||
};
|
||||
|
||||
// Create an ethereum event UnstakeEvent to be passed to handler.
|
||||
const unstakeEvent = await createEvent(exports, contractAddress, dummyEventData);
|
||||
@@ -224,6 +174,8 @@ describe('eden wasm loader tests', async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const contractInterface = new utils.Interface(merkleDistributorAbi);
|
||||
|
||||
it('should load the subgraph network distribution wasm', async () => {
|
||||
const filePath = path.resolve(__dirname, '../test/subgraph/eden/EdenNetworkDistribution/EdenNetworkDistribution.wasm');
|
||||
({ exports } = await instantiate(db, { event: { block: dummyEventData.block } }, filePath, data));
|
||||
@@ -237,28 +189,14 @@ describe('eden wasm loader tests', async () => {
|
||||
} = exports;
|
||||
|
||||
// Create dummy ClaimedEvent params.
|
||||
dummyEventData.eventParams = [
|
||||
{
|
||||
name: 'index',
|
||||
kind: 'uint256',
|
||||
value: BigInt(1)
|
||||
},
|
||||
{
|
||||
name: 'totalEarned',
|
||||
kind: 'uint256',
|
||||
value: BigInt(1)
|
||||
},
|
||||
{
|
||||
name: 'account',
|
||||
kind: 'address',
|
||||
value: ZERO_ADDRESS
|
||||
},
|
||||
{
|
||||
name: 'claimed',
|
||||
kind: 'uint256',
|
||||
value: BigInt(1)
|
||||
}
|
||||
];
|
||||
const eventFragment = contractInterface.getEvent('Claimed(uint256,uint256,indexed address,uint256)');
|
||||
dummyEventData.inputs = eventFragment.inputs;
|
||||
dummyEventData.event = {
|
||||
index: BigInt(1),
|
||||
totalEarned: BigInt(1),
|
||||
account: ZERO_ADDRESS,
|
||||
claimed: BigInt(1)
|
||||
};
|
||||
|
||||
// Create an ethereum event ClaimedEvent to be passed to handler.
|
||||
const claimedEvent = await createEvent(exports, contractAddress, dummyEventData);
|
||||
@@ -272,18 +210,12 @@ describe('eden wasm loader tests', async () => {
|
||||
} = exports;
|
||||
|
||||
// Create dummy SlashedEvent params.
|
||||
dummyEventData.eventParams = [
|
||||
{
|
||||
name: 'account',
|
||||
kind: 'address',
|
||||
value: ZERO_ADDRESS
|
||||
},
|
||||
{
|
||||
name: 'slashed',
|
||||
kind: 'uint256',
|
||||
value: BigInt(1)
|
||||
}
|
||||
];
|
||||
const eventFragment = contractInterface.getEvent('Slashed(indexed address,uint256)');
|
||||
dummyEventData.inputs = eventFragment.inputs;
|
||||
dummyEventData.event = {
|
||||
account: ZERO_ADDRESS,
|
||||
slashed: BigInt(1)
|
||||
};
|
||||
|
||||
// Create an ethereum event SlashedEvent to be passed to handler.
|
||||
const slashedEvent = await createEvent(exports, contractAddress, dummyEventData);
|
||||
@@ -297,23 +229,13 @@ describe('eden wasm loader tests', async () => {
|
||||
} = exports;
|
||||
|
||||
// Create dummy MerkleRootUpdatedEvent params.
|
||||
dummyEventData.eventParams = [
|
||||
{
|
||||
name: 'merkleRoot',
|
||||
kind: 'bytes32',
|
||||
value: ethers.utils.hexlify(ethers.utils.randomBytes(32))
|
||||
},
|
||||
{
|
||||
name: 'distributionNumber',
|
||||
kind: 'uint256',
|
||||
value: BigInt(1)
|
||||
},
|
||||
{
|
||||
name: 'metadataURI',
|
||||
kind: 'string',
|
||||
value: 'abc'
|
||||
}
|
||||
];
|
||||
const eventFragment = contractInterface.getEvent('MerkleRootUpdated(bytes32,uint256,string)');
|
||||
dummyEventData.inputs = eventFragment.inputs;
|
||||
dummyEventData.event = {
|
||||
merkleRoot: ethers.utils.hexlify(ethers.utils.randomBytes(32)),
|
||||
distributionNumber: BigInt(1),
|
||||
metadataURI: 'abc'
|
||||
};
|
||||
|
||||
// Create an ethereum event MerkleRootUpdatedEvent to be passed to handler.
|
||||
const merkleRootUpdatedEvent = await createEvent(exports, contractAddress, dummyEventData);
|
||||
@@ -327,23 +249,13 @@ describe('eden wasm loader tests', async () => {
|
||||
} = exports;
|
||||
|
||||
// Create dummy AccountUpdatedEvent params.
|
||||
dummyEventData.eventParams = [
|
||||
{
|
||||
name: 'account',
|
||||
kind: 'address',
|
||||
value: ZERO_ADDRESS
|
||||
},
|
||||
{
|
||||
name: 'totalClaimed',
|
||||
kind: 'uint256',
|
||||
value: BigInt(1)
|
||||
},
|
||||
{
|
||||
name: 'totalSlashed',
|
||||
kind: 'uint256',
|
||||
value: BigInt(1)
|
||||
}
|
||||
];
|
||||
const eventFragment = contractInterface.getEvent('AccountUpdated(indexed address,uint256,uint256)');
|
||||
dummyEventData.inputs = eventFragment.inputs;
|
||||
dummyEventData.event = {
|
||||
account: ZERO_ADDRESS,
|
||||
totalClaimed: BigInt(1),
|
||||
totalSlashed: BigInt(1)
|
||||
};
|
||||
|
||||
// Create an ethereum event AccountUpdatedEvent to be passed to handler.
|
||||
const accountUpdatedEvent = await createEvent(exports, contractAddress, dummyEventData);
|
||||
@@ -368,6 +280,8 @@ describe('eden wasm loader tests', async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const contractInterface = new utils.Interface(distributorGovernanceAbi);
|
||||
|
||||
it('should load the subgraph network governance wasm', async () => {
|
||||
const filePath = path.resolve(__dirname, '../test/subgraph/eden/EdenNetworkGovernance/EdenNetworkGovernance.wasm');
|
||||
({ exports } = await instantiate(db, { event: { block: dummyEventData.block } }, filePath, data));
|
||||
@@ -381,13 +295,9 @@ describe('eden wasm loader tests', async () => {
|
||||
} = exports;
|
||||
|
||||
// Create dummy BlockProducerAddedEvent params.
|
||||
dummyEventData.eventParams = [
|
||||
{
|
||||
name: 'produces',
|
||||
kind: 'address',
|
||||
value: ZERO_ADDRESS
|
||||
}
|
||||
];
|
||||
const eventFragment = contractInterface.getEvent('BlockProducerAdded(indexed address)');
|
||||
dummyEventData.inputs = eventFragment.inputs;
|
||||
dummyEventData.event = { produces: ZERO_ADDRESS };
|
||||
|
||||
// Create an ethereum event BlockProducerAddedEvent to be passed to handler.
|
||||
const blockProducerAddedEvent = await createEvent(exports, contractAddress, dummyEventData);
|
||||
@@ -401,13 +311,9 @@ describe('eden wasm loader tests', async () => {
|
||||
} = exports;
|
||||
|
||||
// Create dummy BlockProducerRemovedEvent params.
|
||||
dummyEventData.eventParams = [
|
||||
{
|
||||
name: 'producer',
|
||||
kind: 'address',
|
||||
value: ZERO_ADDRESS
|
||||
}
|
||||
];
|
||||
const eventFragment = contractInterface.getEvent('BlockProducerRemoved(indexed address)');
|
||||
dummyEventData.inputs = eventFragment.inputs;
|
||||
dummyEventData.event = { producer: ZERO_ADDRESS };
|
||||
|
||||
// Create an ethereum event BlockProducerRemovedEvent to be passed to handler.
|
||||
const blockProducerRemovedEvent = await createEvent(exports, contractAddress, dummyEventData);
|
||||
@@ -421,23 +327,13 @@ describe('eden wasm loader tests', async () => {
|
||||
} = exports;
|
||||
|
||||
// Create dummy BlockProducerRewardCollectorChangedEvent params.
|
||||
dummyEventData.eventParams = [
|
||||
{
|
||||
name: 'producer',
|
||||
kind: 'address',
|
||||
value: ZERO_ADDRESS
|
||||
},
|
||||
{
|
||||
name: 'collector',
|
||||
kind: 'address',
|
||||
value: ZERO_ADDRESS
|
||||
},
|
||||
{
|
||||
name: 'metadataURI',
|
||||
kind: 'string',
|
||||
value: 'abc'
|
||||
}
|
||||
];
|
||||
const eventFragment = contractInterface.getEvent('BlockProducerRewardCollectorChanged(indexed address,indexed address)');
|
||||
dummyEventData.inputs = eventFragment.inputs;
|
||||
dummyEventData.event = {
|
||||
producer: ZERO_ADDRESS,
|
||||
collector: ZERO_ADDRESS,
|
||||
metadataURI: 'abc'
|
||||
};
|
||||
|
||||
// Create an ethereum event BlockProducerRewardCollectorChangedEvent to be passed to handler.
|
||||
const blockProducerRewardCollectorChangedEvent = await createEvent(exports, contractAddress, dummyEventData);
|
||||
@@ -450,7 +346,9 @@ describe('eden wasm loader tests', async () => {
|
||||
rewardScheduleChanged
|
||||
} = exports;
|
||||
|
||||
dummyEventData.eventParams = [];
|
||||
const eventFragment = contractInterface.getEvent('RewardScheduleChanged()');
|
||||
dummyEventData.inputs = eventFragment.inputs;
|
||||
dummyEventData.event = {};
|
||||
|
||||
// Create an ethereum event RewardScheduleChangedEvent to be passed to handler.
|
||||
const rewardScheduleChangedEvent = await createEvent(exports, contractAddress, dummyEventData);
|
||||
|
||||
@@ -138,18 +138,27 @@ export const instantiate = async (database: Database, indexer: IndexerInterface,
|
||||
// TODO: Check for function overloading.
|
||||
let result = await contract[functionName](...functionParams);
|
||||
|
||||
if (!Array.isArray(result)) {
|
||||
// TODO: Check for function overloading.
|
||||
// Using function signature does not work.
|
||||
const { outputs } = contract.interface.getFunction(functionName);
|
||||
assert(outputs);
|
||||
|
||||
// If method returns a single value, ethers returns it directly compared to returning multiple values in an array.
|
||||
if (outputs.length === 1) {
|
||||
// Put result in an array to map with the outputs array from abi.
|
||||
result = [result];
|
||||
}
|
||||
|
||||
// TODO: Check for function overloading.
|
||||
// Using function signature does not work.
|
||||
const outputs = contract.interface.getFunction(functionName).outputs;
|
||||
|
||||
const resultPtrArrayPromise = result.map(async (value: any, index: number) => {
|
||||
assert(outputs);
|
||||
return toEthereumValue(exports, value, outputs[index].type);
|
||||
});
|
||||
const resultPtrArrayPromise = outputs.map(
|
||||
async (
|
||||
output: any,
|
||||
index: number
|
||||
) => toEthereumValue(
|
||||
exports,
|
||||
output,
|
||||
result[index]
|
||||
)
|
||||
);
|
||||
|
||||
const resultPtrArray: any[] = await Promise.all(resultPtrArrayPromise);
|
||||
const arrayEthereumValueId = await getIdOfType(TypeId.ArrayEthereumValue);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BigNumber } from 'ethers';
|
||||
import { BigNumber, utils } from 'ethers';
|
||||
import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import debug from 'debug';
|
||||
@@ -11,12 +11,6 @@ import { TypeId, EthereumValueKind, ValueKind } from './types';
|
||||
|
||||
const log = debug('vulcanize:utils');
|
||||
|
||||
interface EventParam {
|
||||
name: string;
|
||||
value: any;
|
||||
kind: string;
|
||||
}
|
||||
|
||||
interface Transaction {
|
||||
hash: string;
|
||||
index: number;
|
||||
@@ -42,7 +36,8 @@ export interface Block {
|
||||
export interface EventData {
|
||||
block: Block;
|
||||
tx: Transaction;
|
||||
eventParams: EventParam[];
|
||||
inputs: utils.ParamType[];
|
||||
event: { [key: string]: any }
|
||||
eventIndex: number;
|
||||
}
|
||||
|
||||
@@ -102,16 +97,41 @@ export const fromEthereumValue = async (instanceExports: any, value: any): Promi
|
||||
* @param type
|
||||
* @returns
|
||||
*/
|
||||
export const toEthereumValue = async (instanceExports: any, value: any, type: string): Promise<any> => {
|
||||
export const toEthereumValue = async (instanceExports: any, output: utils.ParamType, value: any): Promise<any> => {
|
||||
const {
|
||||
__newString,
|
||||
__newArray,
|
||||
ByteArray,
|
||||
Bytes,
|
||||
Address,
|
||||
ethereum,
|
||||
BigInt
|
||||
BigInt,
|
||||
id_of_type: getIdOfType
|
||||
} = instanceExports;
|
||||
|
||||
const { type } = output;
|
||||
|
||||
// For tuple type.
|
||||
if (type === 'tuple') {
|
||||
const arrayEthereumValueId = await getIdOfType(TypeId.ArrayEthereumValue);
|
||||
|
||||
// Get values for struct elements.
|
||||
const ethereumValuePromises = output.components
|
||||
.map(
|
||||
async (component: utils.ParamType) => toEthereumValue(
|
||||
instanceExports,
|
||||
component,
|
||||
value[component.name]
|
||||
)
|
||||
);
|
||||
|
||||
const ethereumValues: any[] = await Promise.all(ethereumValuePromises);
|
||||
const ethereumValuesArrayPtr = await __newArray(arrayEthereumValueId, ethereumValues);
|
||||
const ethereumTuple = await ethereum.Tuple.wrap(ethereumValuesArrayPtr);
|
||||
|
||||
return ethereum.Value.fromTuple(ethereumTuple);
|
||||
}
|
||||
|
||||
// For boolean type.
|
||||
if (type === 'bool') {
|
||||
return ethereum.Value.fromBoolean(value ? 1 : 0);
|
||||
@@ -163,7 +183,8 @@ export const createEvent = async (instanceExports: any, contractAddress: string,
|
||||
const {
|
||||
tx,
|
||||
eventIndex,
|
||||
eventParams: eventParamsData,
|
||||
inputs,
|
||||
event,
|
||||
block: blockData
|
||||
} = eventData;
|
||||
|
||||
@@ -214,10 +235,10 @@ export const createEvent = async (instanceExports: any, contractAddress: string,
|
||||
txinputPtr
|
||||
);
|
||||
|
||||
const eventParamArrayPromise = eventParamsData.map(async data => {
|
||||
const { name, value, kind } = data;
|
||||
const eventParamArrayPromise = inputs.map(async input => {
|
||||
const { name } = input;
|
||||
|
||||
const ethValue = await toEthereumValue(instanceExports, value, kind);
|
||||
const ethValue = await toEthereumValue(instanceExports, input, event[name]);
|
||||
const namePtr = await __newString(name);
|
||||
|
||||
return ethereum.EventParam.__new(
|
||||
|
||||
@@ -133,17 +133,10 @@ export class GraphWatcher {
|
||||
|
||||
const eventFragment = contractInterface.getEvent(eventSignature);
|
||||
|
||||
const eventParams = eventFragment.inputs.map((input) => {
|
||||
return {
|
||||
name: input.name,
|
||||
value: event[input.name],
|
||||
kind: input.type
|
||||
};
|
||||
});
|
||||
|
||||
const data = {
|
||||
eventParams: eventParams,
|
||||
block: blockData,
|
||||
inputs: eventFragment.inputs,
|
||||
event,
|
||||
tx,
|
||||
eventIndex
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user