@soloseng/ganache-ethereum-utils
Version:
210 lines • 7.87 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BlockLogs = void 0;
const utils_1 = require("@ganache/utils");
const utils_2 = require("@ganache/utils");
const rlp_1 = require("@ganache/rlp");
const ethereum_address_1 = require("@ganache/ethereum-address");
const _raw = Symbol("raw");
const _logs = Symbol("logs");
const filterByTopic = (expectedTopics, logTopics) => {
// Exclude log if its number of topics is less than the number expected
if (expectedTopics.length > logTopics.length)
return false;
// for every expectedTopic, we must much the log topic in the same position
return expectedTopics.every((expectedTopic, logPosition) => {
// a `null` topic means "anything"
if (expectedTopic === null)
return true;
let expectedTopicSet;
if (!Array.isArray(expectedTopic)) {
return logTopics[logPosition].equals(utils_1.Data.toBuffer(expectedTopic));
}
// an empty rule set means "anything"
if (expectedTopic.length === 0)
return true;
expectedTopicSet = expectedTopic;
const logTopic = logTopics[logPosition];
// "OR" logic, e.g., [[A, B]] means log topic in the first position matching either "A" OR "B":
return expectedTopicSet.some(expectedTopic => logTopic.equals(utils_1.Data.toBuffer(expectedTopic)));
});
};
class BlockLogs {
constructor(data) {
if (data) {
const decoded = (0, rlp_1.decode)(data);
this[_raw] = decoded;
}
}
/**
*
* @param blockHash - Creates an BlogLogs entity with an empty internal logs
* array.
*/
static create(blockHash) {
const blockLog = Object.create(BlockLogs.prototype);
blockLog[_raw] = [blockHash.toBuffer(), []];
return blockLog;
}
/**
* rlpEncode's the blockHash and logs array for db storage
*/
serialize() {
return (0, rlp_1.encode)(this[_raw]);
}
/**
* Appends the data to the internal logs array
* @param transactionIndex -
* @param transactionHash -
* @param log -
*/
append(
/*removed: boolean, */ transactionIndex, transactionHash, log) {
this[_raw][1].push([
utils_2.BUFFER_ZERO,
transactionIndex.toBuffer(),
transactionHash.toBuffer(),
log[0],
log[1],
log[2] // `data`
]);
}
/**
* Returns the number of logs in the internal logs array.
*/
get length() {
return this[_raw][1].length;
}
static fromJSON(json) {
if (!json || json.length === 0) {
return null;
}
const blockHash = json[0].blockHash;
const blockNumber = json[0].blockNumber;
const blockLogs = BlockLogs.create(utils_1.Data.from(blockHash, 32));
blockLogs.blockNumber = utils_1.Quantity.from(blockNumber);
json.forEach(log => {
const address = ethereum_address_1.Address.from(log.address);
const blockNumber = log.blockNumber;
const data = Array.isArray(log.data)
? log.data.map(d => utils_1.Data.toBuffer(d))
: utils_1.Data.toBuffer(log.data);
const logIndex = log.logIndex;
const removed = log.removed === false ? utils_2.BUFFER_ZERO : utils_1.Quantity.One.toBuffer();
const topics = Array.isArray(log.topics)
? log.topics.map(t => utils_1.Data.toBuffer(t, 32))
: utils_1.Data.toBuffer(log.topics, 32);
const transactionHash = utils_1.Data.from(log.transactionHash, 32);
const transactionIndex = utils_1.Quantity.from(log.transactionIndex);
blockLogs.append(transactionIndex, transactionHash, [
address.toBuffer(),
topics,
data
]);
});
return blockLogs;
}
toJSON() {
return this[_logs]().toJSON();
}
[_logs]() {
const blockNumber = this.blockNumber;
const raw = this[_raw];
const logs = raw[1];
const l = this.length;
const blockHash = utils_1.Data.from(raw[0]);
return {
toJSON() {
return {
*[Symbol.iterator]() {
for (let i = 0; i < l; i++) {
yield BlockLogs.logToJSON(logs[i], utils_1.Quantity.from(i), blockHash, blockNumber);
}
}
};
},
*[Symbol.iterator]() {
for (let i = 0; i < l; i++) {
const log = logs[i];
const address = log[3];
const topics = log[4];
yield {
address,
topics,
toJSON: () => BlockLogs.logToJSON(log, utils_1.Quantity.from(i), blockHash, blockNumber)
};
}
}
};
}
/**
*
* @param log -
* @param logIndex - The index this log appears in the block
* @param blockHash - The hash of the block
* @param blockNumber - The block number
*/
static logToJSON(log, logIndex, blockHash, blockNumber) {
const topics = log[4];
const data = log[5];
return {
address: ethereum_address_1.Address.from(log[3]),
blockHash,
blockNumber,
data: Array.isArray(data)
? data.map(d => utils_1.Data.from(d, d.length))
: utils_1.Data.from(data, data.length),
logIndex,
removed: log[0].equals(utils_2.BUFFER_ZERO) ? false : true,
topics: Array.isArray(topics)
? topics.map(t => utils_1.Data.from(t, 32))
: utils_1.Data.from(topics, 32),
transactionHash: utils_1.Data.from(log[2], 32),
transactionIndex: utils_1.Quantity.from(log[1])
};
}
/**
* Note: you must set `this.blockNumber: Quantity` first!
*
* Topics are order-dependent. A transaction with a log with topics [A, B] will be matched by the following topic
* filters:
* ▸ [] "anything"
* ▸ [A] "A in first position (and anything after)"
* ▸ [null, B] "anything in first position AND B in second position (and anything after)"
* ▸ [A, B] "A" in first position AND B in second position (and anything after)"
* ▸ [[A, B], [A, B]] "(A OR B) in first position AND (A OR B) in second position (and anything after)"
* @param expectedAddresses -
* @param expectedTopics -
* @returns JSON representation of the filtered logs
*/
*filter(expectedAddresses, expectedTopics) {
const logs = this[_logs]();
if (expectedAddresses.length !== 0) {
if (expectedTopics.length === 0) {
for (const log of logs) {
if (expectedAddresses.some(address => address.equals(log.address)))
yield log.toJSON();
}
}
else {
for (const log of logs) {
if (!expectedAddresses.some(address => address.equals(log.address)))
continue;
if (filterByTopic(expectedTopics, log.topics))
yield log.toJSON();
}
}
}
else if (expectedTopics.length !== 0) {
for (const log of logs) {
if (filterByTopic(expectedTopics, log.topics))
yield log.toJSON();
}
}
else {
yield* logs.toJSON();
}
}
}
exports.BlockLogs = BlockLogs;
//# sourceMappingURL=blocklogs.js.map