Skip to content

Commit

Permalink
Merge pull request #543 from protofire/gc-indexed-events
Browse files Browse the repository at this point in the history
GC: Indexed events
  • Loading branch information
dbale-altoros authored Jan 19, 2024
2 parents dd64cde + ec8da47 commit df90eba
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 6 deletions.
1 change: 1 addition & 0 deletions conf/rulesets/solhint-all.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ module.exports = Object.freeze({
},
],
'constructor-syntax': 'warn',
'gas-indexed-events': 'warn',
'gas-multitoken1155': 'warn',
'gas-small-strings': 'warn',
'comprehensive-interface': 'warn',
Expand Down
9 changes: 5 additions & 4 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ title: "Rule Index of Solhint"

## Gas Consumption Rules

| Rule Id | Error | Recommended | Deprecated |
| ------------------------------------------------------------------- | --------------------------------------------------- | ----------- | ---------- |
| [gas-multitoken1155](./rules/gas-consumption/gas-multitoken1155.md) | ERC1155 is a cheaper non-fungible token than ERC721 | | |
| [gas-small-strings](./rules/gas-consumption/gas-small-strings.md) | Keep strings smaller than 32 bytes | | |
| Rule Id | Error | Recommended | Deprecated |
| ------------------------------------------------------------------- | -------------------------------------------------------------- | ----------- | ---------- |
| [gas-indexed-events](./rules/gas-consumption/gas-indexed-events.md) | Suggest indexed arguments on events for uint, bool and address | | |
| [gas-multitoken1155](./rules/gas-consumption/gas-multitoken1155.md) | ERC1155 is a cheaper non-fungible token than ERC721 | | |
| [gas-small-strings](./rules/gas-consumption/gas-small-strings.md) | Keep strings smaller than 32 bytes | | |

## Miscellaneous
Expand Down
38 changes: 38 additions & 0 deletions docs/rules/gas-consumption/gas-indexed-events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
warning: "This is a dynamically generated file. Do not edit manually."
layout: "default"
title: "gas-indexed-events | Solhint"
---

# gas-indexed-events
![Category Badge](https://img.shields.io/badge/-Gas%20Consumption%20Rules-informational)
![Default Severity Badge warn](https://img.shields.io/badge/Default%20Severity-warn-yellow)

## Description
Suggest indexed arguments on events for uint, bool and address

## Options
This rule accepts a string option of rule severity. Must be one of "error", "warn", "off". Default to warn.

### Example Config
```json
{
"rules": {
"gas-indexed-events": "warn"
}
}
```

### Notes
- [source](https://coinsbench.com/comprehensive-guide-tips-and-tricks-for-gas-optimization-in-solidity-5380db734404) of the rule initiciative (see Indexed Events)

## Examples
This rule does not have examples.

## Version
This rule is introduced in the latest version.

## Resources
- [Rule source](https://github.com/protofire/solhint/tree/master/lib/rules/gas-consumption/gas-indexed-events.js)
- [Document source](https://github.com/protofire/solhint/tree/master/docs/rules/gas-consumption/gas-indexed-events.md)
- [Test cases](https://github.com/protofire/solhint/tree/master/test/rules/gas-consumption/gas-indexed-events.js)
68 changes: 68 additions & 0 deletions lib/rules/gas-consumption/gas-indexed-events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
const BaseChecker = require('../base-checker')

const ruleId = 'gas-indexed-events'
const suggestForTypes = ['uint', 'int', 'bool', 'ufixed', 'fixed', 'address']
const meta = {
type: 'gas-consumption',

docs: {
description: 'Suggest indexed arguments on events for uint, bool and address',
category: 'Gas Consumption Rules',
notes: [
{
note: '[source](https://coinsbench.com/comprehensive-guide-tips-and-tricks-for-gas-optimization-in-solidity-5380db734404) of the rule initiciative (see Indexed Events)',
},
],
},

isDefault: false,
recommended: false,
defaultSetup: 'warn',

schema: null,
}

class GasIndexedEvents extends BaseChecker {
constructor(reporter) {
super(reporter, ruleId, meta)
}

EventDefinition(node) {
const qtyArguments = node.parameters.length
const positionsOfPossibleSuggestions = []
let qtyOfIndexedKetwords = 0
let argumentType = ''

// first check if the indexed keyword is used three times to left room for suggestion
for (let i = 0; i < qtyArguments; i++) {
// put the type in the variable for better code reading
argumentType = node.parameters[i].typeName.name

// compare if the type is one of the possible suggestion types
for (const type of suggestForTypes) {
if (argumentType.startsWith(type)) {
// push the position into an array to come back later if there's room for another indexed argument
positionsOfPossibleSuggestions.push(i)
}
}

// count the indexed arguments
if (node.parameters[i].isIndexed) qtyOfIndexedKetwords++
}

// if there's room for more indexed arguments
if (qtyOfIndexedKetwords < 3 && positionsOfPossibleSuggestions.length > 0) {
this.reportError(node, positionsOfPossibleSuggestions)
}
}

reportError(node, positionsOfPossibleSuggestions) {
let parameterName = ''
for (let i = 0; i < positionsOfPossibleSuggestions.length; i++) {
parameterName = node.parameters[positionsOfPossibleSuggestions[i]].name
this.error(node, `GC: [${parameterName}] on Event [${node.name}] could be Indexed`)
}
}
}

module.exports = GasIndexedEvents
4 changes: 2 additions & 2 deletions lib/rules/gas-consumption/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
const GasMultitoken1155 = require('./gas-multitoken1155')
const GasSmallStrings = require('./gas-small-strings')
// const GasIndexedEvents = require('./gas-indexed-events')
const GasIndexedEvents = require('./gas-indexed-events')

// module.exports = function checkers(reporter, config, tokens) {
module.exports = function checkers(reporter, config) {
return [
new GasMultitoken1155(reporter, config),
new GasSmallStrings(reporter, config),
// new GasIndexedEvents(reporter, config),
new GasIndexedEvents(reporter, config),
]
}
82 changes: 82 additions & 0 deletions test/rules/gas-consumption/gas-indexed-events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const assert = require('assert')
const linter = require('../../../lib/index')
const { contractWith } = require('../../common/contract-builder')

const replaceErrorMsg = (variableName, eventName) => {
return `GC: [${variableName}] on Event [${eventName}] could be Indexed`
}

describe('Linter - gas-indexed-events', () => {
it('should raise error on amount and address', () => {
const code = contractWith(
'event LogEvent1(string message, bytes whatever, uint128 amount, address account);'
)

const report = linter.processStr(code, {
rules: { 'gas-indexed-events': 'error' },
})

assert.equal(report.errorCount, 2)
assert.equal(report.messages[0].message, replaceErrorMsg('amount', 'LogEvent1'))
assert.equal(report.messages[1].message, replaceErrorMsg('account', 'LogEvent1'))
})

it('should raise error on account', () => {
const code = contractWith('event LogEvent2(ufixed account);')

const report = linter.processStr(code, {
rules: { 'gas-indexed-events': 'error' },
})

assert.equal(report.errorCount, 1)
assert.equal(report.messages[0].message, replaceErrorMsg('account', 'LogEvent2'))
})

it('should raise error on amount and account', () => {
const code = contractWith(
'event LogEvent4(string indexed message, bytes indexed whatever, bool active, address account);'
)

const report = linter.processStr(code, {
rules: { 'gas-indexed-events': 'error' },
})

assert.equal(report.errorCount, 2)
assert.equal(report.messages[0].message, replaceErrorMsg('active', 'LogEvent4'))
assert.equal(report.messages[1].message, replaceErrorMsg('account', 'LogEvent4'))
})

it('should NOT raise error, all three indexed keyword are used', () => {
const code = contractWith(
'event LogEvent3(string indexed message, bytes indexed whatever, uint256 indexed amount, address account);'
)

const report = linter.processStr(code, {
rules: { 'gas-indexed-events': 'error' },
})

assert.equal(report.errorCount, 0)
})

it('should NOT raise error, all types are not for suggestion', () => {
const code = contractWith(
'event LogEvent3(string indexed message, bytes whatever, string message2);'
)

const report = linter.processStr(code, {
rules: { 'gas-indexed-events': 'error' },
})

assert.equal(report.errorCount, 0)
})

it('should NOT raise error, there are no arguments', () => {
const code = contractWith('event LogEvent5();')

const report = linter.processStr(code, {
rules: { 'gas-indexed-events': 'error' },
})

assert.equal(report.errorCount, 0)
})
})

0 comments on commit df90eba

Please sign in to comment.