Skip to content

Commit

Permalink
Decode WASM ContractEmitted + show tx (#367)
Browse files Browse the repository at this point in the history
  • Loading branch information
sourabhniyogi committed Jul 18, 2023
1 parent 3d38c82 commit 088cf66
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 43 deletions.
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2261,4 +2261,4 @@ Promise.all([x]).then(() => {
query.autoUpdate()
}).catch(err => {
// handle error here
});
});
2 changes: 1 addition & 1 deletion public/uihelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -1621,4 +1621,4 @@ function get_accountState(asset, chainID, assetChain) {
render: n
};
"undefined" != typeof module && (module.exports = i), "undefined" != typeof window && (window.blockies = i)
}();
}();
105 changes: 68 additions & 37 deletions substrate/chains/astar.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ module.exports = class AstarParser extends ChainParser {
let c_args = c.args
//console.log(`[${extrinsic.extrinsicID}] call`, i, call_section, call_method, c);
i++;
this.processWasmContracts(indexer, extrinsic, feed, fromAddress, call_section, call_method, c_args, depth + 1, i)
await this.processWasmContracts(indexer, extrinsic, feed, fromAddress, call_section, call_method, c_args, depth + 1, i)
}
} else if (args.call != undefined) {
let call = args.call
Expand All @@ -54,11 +54,12 @@ module.exports = class AstarParser extends ChainParser {
//console.log(`[${extrinsic.extrinsicID}] descend into call`, call)
if (!isHexEncoded && call_args != undefined) {
//if (this.debugLevel >= paraTool.debugTracing) console.log(`[${extrinsic.extrinsicID}] descend into call=${call}, call_section=${call_section}, call_method=${call_method}, call_args`, call_args)
this.processWasmContracts(indexer, extrinsic, feed, fromAddress, call_section, call_method, call_args, depth + 1)
await this.processWasmContracts(indexer, extrinsic, feed, fromAddress, call_section, call_method, call_args, depth + 1)
} else {
//if (this.debugLevel >= paraTool.debugTracing) console.log(`[${extrinsic.extrinsicID}] skip call=${call}, call_section=${call_section}, call_method=${call_method}, call.args`, call_args)
}
}

switch (section_method) {
case 'contracts:call': //contract write
//0xd4ffe56c661d718cd4ca6039c0d5519aa3d20b0f32d6b18dc4809d60dd7b3d03
Expand All @@ -69,13 +70,13 @@ module.exports = class AstarParser extends ChainParser {
break;
case 'contracts:instantiate': //contract deploy with available codehash
//0x7e020cd122a51d4f037f95a86761f6af33228d0a8c68f8f79fd1f27c17885914
let wasmWithoutCode = this.processContractsInstantiate(indexer, extrinsic, feed, fromAddress, section_method, args)
let wasmWithoutCode = await this.processContractsInstantiate(indexer, extrinsic, feed, fromAddress, section_method, args)
//if (this.debugLevel >= paraTool.debugInfo) console.log(`[${extrinsic.extrinsicID}] [${extrinsic.extrinsicHash}] contracts:instantiate`, wasmWithoutCode)
indexer.addWasmContract(wasmWithoutCode, wasmWithoutCode.withCode);
break;
case 'contracts:instantiateWithCode': //contract deploy with wasm code
//0x2c986a6cb47b94a9e50f5d3f660e0f37177989594eb087bf7309c2e15e2340c8
let wasmWithCode = this.processContractsInstantiateWithCode(indexer, extrinsic, feed, fromAddress, section_method, args)
let wasmWithCode = await this.processContractsInstantiateWithCode(indexer, extrinsic, feed, fromAddress, section_method, args)
//if (this.debugLevel >= paraTool.debugInfo) console.log(`[${extrinsic.extrinsicID}] [${extrinsic.extrinsicHash}] contracts:instantiateWithCode`, wasmWithCode)
indexer.addWasmContract(wasmWithCode, wasmWithCode.withCode);
break;
Expand Down Expand Up @@ -141,24 +142,63 @@ module.exports = class AstarParser extends ChainParser {
}
}

getWasmContractsEvent(indexer, extrinsic) {
async getWasmContractsEvent(indexer, extrinsic) {
let wasmContractsEmitted = [];
let wasmContractsEvents = [];
extrinsic.events.forEach((ev) => {
let metadata = {};
for (let i = 0; i < extrinsic.events.length; i++) {
let ev = extrinsic.events[i];
let palletMethod = `${ev.section}(${ev.method})`
if (this.contractsEventFilter(palletMethod)) {
wasmContractsEvents.push(ev)
}
})
return wasmContractsEvents
if (palletMethod == 'contracts(ContractEmitted)') {
let evdata = ev.data;
try {
// Important: address here may not the same as called contract!!!
let address = evdata[0];
let data = evdata[1];
if (metadata[address] == undefined) {
metadata[address] = await this.fetchWASMContractMetadata(address, indexer);
}
if (metadata[address]) {
const contract = new ContractPromise(indexer.api, metadata[address], address);
const bytes = hexToU8a(data);
let result = contract.abi.decodeEvent(bytes);
let args = result.args;
let names = result.event.args;
let out = {
identifier: result.event.identifier
};
args.forEach((a, idx) => {
out[names[idx].name] = a.toHuman();
})
wasmContractsEmitted.push(out);
}
} catch (err) {
console.log(err)
}
} else {
wasmContractsEvents.push(ev);
}
}
return [wasmContractsEvents, wasmContractsEmitted];
}

async fetchWASMContractMetadata(address_ss58, indexer) {
// TODO: add chainID?
let sql = `select CONVERT(metadata using utf8) metadata from wasmCode, contract where address_ss58 = '${address_ss58}' and wasmCode.codeHash = contract.codeHash`
let recs = await indexer.poolREADONLY.query(sql)
if (recs.length == 1) {
return recs[0].metadata;
}
return null;
}

async processContractsCall(indexer, extrinsic, feed, fromAddress, section_method, args, callID) {
console.log(`[${extrinsic.extrinsicID}] [${extrinsic.extrinsicHash}] [${section_method}] EXTINRIC`, extrinsic, `ARGS:`, args)
let wasmContractsEvents = this.getWasmContractsEvent(indexer, extrinsic)
let [wasmContractsEvents, wasmContractsEmitted] = await this.getWasmContractsEvent(indexer, extrinsic)
let {
address,
address_ss58
} = this.processWasmDest(args.dest)

let r = {
callID: callID,
chainID: indexer.chainID,
Expand All @@ -175,17 +215,14 @@ module.exports = class AstarParser extends ChainParser {
caller: paraTool.getPubKey(extrinsic.signer),
caller_ss58: extrinsic.signer,
data: args.data,
events: wasmContractsEvents,
identifier: null,
decodedCall: null
decodedCall: null,
}
let metadata = {};
try {
let sql = `select CONVERT(metadata using utf8) metadata from wasmCode, contract where address_ss58 = '${address_ss58}' and wasmCode.codeHash = contract.codeHash`
let recs = await indexer.poolREADONLY.query(sql)
if (recs.length > 0) {
let metadata = recs[0].metadata;
console.log("FOUND METADATA", sql, metadata);
const contract = new ContractPromise(indexer.api, metadata, address_ss58);
metadata[address_ss58] = await this.fetchWASMContractMetadata(address_ss58, indexer);
if (metadata[address_ss58]) {
const contract = new ContractPromise(indexer.api, metadata[address_ss58], address_ss58);
const bytes = hexToU8a(args.data);
let result = contract.abi.decodeMessage(compactAddLength(bytes));
r.decodedCall = result.args.map((a) => {
Expand All @@ -194,25 +231,19 @@ module.exports = class AstarParser extends ChainParser {
let message = result.message
r.identifier = message.identifier;
console.log("DECODED", r.identifier, r.decodedCall);
extrinsic.identifier = r.identifier;
extrinsic.decodedCall = r.decodedCall;
extrinsic.decodedEvents = wasmContractsEmitted;
} else {
console.log("NOT FOUND", sql);
console.log("NOT FOUND", address_ss58);
}
} catch (err) {
console.log("processContractsCall", err);
}

for (const ev of wasmContractsEvents) {
let eventMethodSection = `${ev.section}(${ev.method})`
console.log(ev);
if (eventMethodSection == 'contracts(ContractEmitted)') {
// WARNING: contract address here is not the same as called contract
/* contractAddr, encodedEvents ["anCpiHdWuGUiQbsrqsbmYyRzdG4zP8LzmnyDy9GZxQS28Yq","0x000001d2ae8d7ab7db366b2451da59e1af3eb2398315c512d0cc400a9d70566f76e96040420f00000000000000000000000000"]*/
}
}
return r
}

processContractsInstantiate(indexer, extrinsic, feed, fromAddress, section_method, args) {
async processContractsInstantiate(indexer, extrinsic, feed, fromAddress, section_method, args) {
//contract deploy with available codehash
//0x7e020cd122a51d4f037f95a86761f6af33228d0a8c68f8f79fd1f27c17885914 //indexPeriods 22007 2022-07-14 16
/*
Expand All @@ -225,8 +256,7 @@ module.exports = class AstarParser extends ChainParser {
"salt": "0x"
}
*/
//console.log(`[${extrinsic.extrinsicID}] [${extrinsic.extrinsicHash}] [${section_method}]`, args)
let wasmContractsEvents = this.getWasmContractsEvent(indexer, extrinsic)
let [wasmContractsEvents, wasmContractsEmitted] = await this.getWasmContractsEvent(indexer, extrinsic)
let r = {
chainID: indexer.chainID,
network: indexer.getIDByChainID(indexer.chainID),
Expand Down Expand Up @@ -260,7 +290,7 @@ module.exports = class AstarParser extends ChainParser {
return r
}

processContractsInstantiateWithCode(indexer, extrinsic, feed, fromAddress, section_method, args) {
async processContractsInstantiateWithCode(indexer, extrinsic, feed, fromAddress, section_method, args) {
//contract deploy with wasm byte code
//0x2c986a6cb47b94a9e50f5d3f660e0f37177989594eb087bf7309c2e15e2340c8 //indexPeriods 22007 2022-08-18 23
/*
Expand All @@ -274,7 +304,8 @@ module.exports = class AstarParser extends ChainParser {
}
*/
//console.log(`[${extrinsic.extrinsicID}] [${extrinsic.extrinsicHash}] [${section_method}]`, args)
let wasmContractsEvents = this.getWasmContractsEvent(indexer, extrinsic)
let [wasmContractsEvents, wasmContractsEmitted] = await this.getWasmContractsEvent(indexer, extrinsic)
console.log("processContractsInstantiateWithCode", wasmContractsEvents);
let r = {
chainID: indexer.chainID,
network: indexer.getIDByChainID(indexer.chainID),
Expand Down Expand Up @@ -927,4 +958,4 @@ module.exports = class AstarParser extends ChainParser {
//console.log(`astar processAsset ${pallet_section}`)
return super.processAsset(indexer, p, s, e2)
}
}
}
2 changes: 1 addition & 1 deletion substrate/crawler.js
Original file line number Diff line number Diff line change
Expand Up @@ -2463,4 +2463,4 @@ module.exports = class Crawler extends Indexer {
return [unsubscribeFinalizedHeads, unsubscribeStorage, unsubscribeRuntimeVersion];
}

}
}
5 changes: 3 additions & 2 deletions substrate/indexer.js
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,7 @@ module.exports = class Indexer extends AssetManager {
}
await this.flushWSProviderQueue();
let immediateFlushStartTS = new Date().getTime();

await this.immediateFlushBlockAndAddressExtrinsics(isTip)
let immediateFlushTS = (new Date().getTime() - immediateFlushStartTS) / 1000

Expand Down Expand Up @@ -5233,7 +5234,6 @@ module.exports = class Indexer extends AssetManager {
if (this.chainID == paraTool.chainIDAstar || this.chainID == paraTool.chainIDShiden || this.chainID == paraTool.chainIDShibuya) {
await this.chainParser.processWasmContracts(this, rExtrinsic, feed, fromAddress, false, false, false);
}

//check the "missed" xcm case - see if it contains xTokens event not triggered by pallet
this.chainParser.processOutgoingXCMFromXTokensEvent(this, rExtrinsic, feed, fromAddress, false, false, false);
if (rExtrinsic.xcms == undefined) {
Expand Down Expand Up @@ -9394,6 +9394,7 @@ module.exports = class Indexer extends AssetManager {
}
this.dump_update_block_stats(chain.chainID, statRows, indexTS)
let elapsedTS = (new Date().getTime() - elapsedStartTS) / 1000

await this.flush(indexTS, blockNumber, false, false); //ts, bn, isFullPeriod, isTip

// errors, warns within this block ..
Expand Down Expand Up @@ -9884,4 +9885,4 @@ module.exports = class Indexer extends AssetManager {
await this.update_spec_version(chain.chainID, specVersion);
}
}
}
}
10 changes: 10 additions & 0 deletions substrate/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -5018,6 +5018,16 @@ module.exports = class Query extends AssetManager {
decoratedExt.callIndex = ext.callIndex
}

if (ext.decodedEvents != undefined) {
decoratedExt.decodedEvents = ext.decodedEvents;
}
if (ext.identifier != undefined) {
decoratedExt.identifier = ext.identifier;
}
if (ext.decodedCall != undefined) {
decoratedExt.decodedCall = ext.decodedCall;
}

if (ext.events != undefined) {
decoratedExt.events = []
for (const evt of ext.events) {
Expand Down
80 changes: 80 additions & 0 deletions substrate/test/testWASMContractDecodeEvent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const {
ApiPromise,
WsProvider,
Keyring
} = require("@polkadot/api");
const {
CodePromise,
ContractPromise
} = require('@polkadot/api-contract');
const {
u8aToU8a,
hexToU8a,
u8aToHex,
compactAddLength
} = require('@polkadot/util');

const Query = require("../query");

async function main() {
let chainID = 30000;
let endpoint = "wss:https://rpc.shibuya.astar.network";

const provider = new WsProvider(endpoint);
provider.on('disconnected', () => {
console.log('CHAIN API DISCONNECTED', chainID);
});
provider.on('connected', () => console.log('chain API connected', chainID));
provider.on('error', (error) => console.log('chain API error', chainID, error));
let api = await ApiPromise.create({
provider: provider
});

let debugLevel = 0
var query = new Query(debugLevel);
await query.init();

console.log(`You are connected to ASTAR/SHIDEN/SHIBUYA chain ${chainID} endpoint=${endpoint} with options`);

let testcases = [
// Transfer
{
"extrinsicHash": "0x8a46c728d9d6993a70a08619b2f508d2345e1c686f1e9603d2fc39d4d645591a",
"address_ss58": "a3kMGnw16gZLbjtKSWrsPJz8BQ2vV5bGfYEVKxowHjiVvbC",
"data": "0x0001d2473025c560e31b005151ebadbc3e1f14a2af8fa60ed87e2b35fa930523cd3c01b5dc1f4c2d5fb2fb14ea92d824f7d440dd1114df1391fff67f1a7f7803c97fc376b20100000000000000000000000000",
"codeHash": "0xa498fd3d0073459ddbed3446dea975f001a3a9669814fae694a2201452952ab4"
}
];


for (const t of testcases) {
let address = t.address_ss58;
let data = t.data;
let wasmContract = await query.getWASMContract(address, chainID);
let metadata = wasmContract.metadata;
try {
const contract = new ContractPromise(api, metadata, address);
const bytes = hexToU8a(data);
let result = contract.abi.decodeEvent(bytes); // compactAddLength(bytes));
let args = result.args;
let names = result.event.args;
console.log(names);
let out = {};
args.forEach((a, idx) => {
out[names[idx].name] = a.toHuman();
})
let method = result.event.identifier;
console.log(method, out);
} catch (err) {
console.log(err)
}
}
}


main()
.then(() => process.exit(0))
.catch((e) => {
console.error('ERROR', e);
process.exit(1);
});
8 changes: 7 additions & 1 deletion views/tx.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,16 @@ let chainSymbol = (f.chainSymbol != undefined)? f.chainSymbol: ''
<% } %>
</td>
</tr>
<% if ( f.decodedEvents && f.decodedEvents.length > 0 ) { %>
<tr>
<td><%- include("tooltip", {k: "params"}) %>Decoded WASM Events</td>
<td><%- include("decode", {id: "events" + f.extrinsicHash, obj: f.decodedEvents, verify: null, size: [] }); %></td>
</tr>
<% } %>
<% } %>
<tr>
<td><%- include("tooltip", {k: "params"}) %>Params</td>
<td><%- include("decode", {id: f.extrinsicHash, obj: f.params, verify: { verification: "extrinsic", blockNumber: f.blockNumber, extrinsicHash: f.extrinsicHash, extrinsicID: f.extrinsicID }, size: [] }); %></td>
<td><%- include("decode", {id: f.extrinsicHash, obj: f.params, verify: null, size: [] }); %></td>
</tr>
<% if ( f.lifetime ) { %>
<tr>
Expand Down

0 comments on commit 088cf66

Please sign in to comment.