Skip to content

Commit

Permalink
Merge pull request #545 from protofire/gc-increment-by-one
Browse files Browse the repository at this point in the history
GC: Increment by one
  • Loading branch information
dbale-altoros authored Feb 28, 2024
2 parents 8c2f05a + bda8bca commit a1cbcec
Show file tree
Hide file tree
Showing 15 changed files with 320 additions and 17 deletions.
1 change: 1 addition & 0 deletions conf/rulesets/solhint-all.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module.exports = Object.freeze({
],
'constructor-syntax': 'warn',
'gas-calldata-parameters': 'warn',
'gas-increment-by-one': 'warn',
'gas-indexed-events': 'warn',
'gas-multitoken1155': 'warn',
'gas-small-strings': 'warn',
Expand Down
13 changes: 7 additions & 6 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ title: "Rule Index of Solhint"

## Gas Consumption Rules

| Rule Id | Error | Recommended | Deprecated |
| ----------------------------------------------------------------------------- | -------------------------------------------------------------- | ----------- | ---------- |
| [gas-calldata-parameters](./rules/gas-consumption/gas-calldata-parameters.md) | Suggest calldata keyword on function arguments when read only | | |
| [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 | | |
| Rule Id | Error | Recommended | Deprecated |
| ----------------------------------------------------------------------------- | ----------------------------------------------------------------- | ----------- | ---------- |
| [gas-calldata-parameters](./rules/gas-consumption/gas-calldata-parameters.md) | Suggest calldata keyword on function arguments when read only | | |
| [gas-increment-by-one](./rules/gas-consumption/gas-increment-by-one.md) | Suggest incrementation by one like this ++i instead of other type | | |
| [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
4 changes: 2 additions & 2 deletions docs/rules/gas-consumption/gas-calldata-parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ This rule accepts a string option of rule severity. Must be one of "error", "war
### Notes
- Only applies for external functions when receiving arguments with [memory] keyword
- This rule makes a soft check to see if argument is readOnly to make the suggestion. Check it manually before changing it.
- [source 1](https://coinsbench.com/comprehensive-guide-tips-and-tricks-for-gas-optimization-in-solidity-5380db734404) of the rule initiciative (see Calldata vs Memory)
- [source 2](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-6acr7) of the rule initiciative
- [source 1](https://coinsbench.com/comprehensive-guide-tips-and-tricks-for-gas-optimization-in-solidity-5380db734404) of the rule initiative (see Calldata vs Memory)
- [source 2](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-6acr7) of the rule initiative

## Examples
This rule does not have examples.
Expand Down
40 changes: 40 additions & 0 deletions docs/rules/gas-consumption/gas-increment-by-one.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
warning: "This is a dynamically generated file. Do not edit manually."
layout: "default"
title: "gas-increment-by-one | Solhint"
---

# gas-increment-by-one
![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 incrementation by one like this ++i instead of other type

## 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-increment-by-one": "warn"
}
}
```

### Notes
- This rule only works for expressions like this: [ j = j + 1 ] but will fail is the code is written like this: [ j = 1 + j ]
- [source 1](https://coinsbench.com/comprehensive-guide-tips-and-tricks-for-gas-optimization-in-solidity-5380db734404) of the rule initiative (Incrementing/Decrementing By 1)
- [source 2](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-8rekj) of the rule initiative

## 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-increment-by-one.js)
- [Document source](https://github.com/protofire/solhint/tree/master/docs/rules/gas-consumption/gas-increment-by-one.md)
- [Test cases](https://github.com/protofire/solhint/tree/master/test/rules/gas-consumption/gas-increment-by-one.js)
2 changes: 1 addition & 1 deletion docs/rules/gas-consumption/gas-indexed-events.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ This rule accepts a string option of rule severity. Must be one of "error", "war
```

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

## Examples
This rule does not have examples.
Expand Down
2 changes: 1 addition & 1 deletion docs/rules/gas-consumption/gas-multitoken1155.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ This rule accepts a string option of rule severity. Must be one of "error", "war
```

### Notes
- [source](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-8v8t9) of the rule initiciative
- [source](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-8v8t9) of the rule initiative

## Examples
This rule does not have examples.
Expand Down
2 changes: 1 addition & 1 deletion docs/rules/gas-consumption/gas-small-strings.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ This rule accepts a string option of rule severity. Must be one of "error", "war
```

### Notes
- [source](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-ck1vq) of the rule initiciative
- [source](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-ck1vq) of the rule initiative

## Examples
This rule does not have examples.
Expand Down
4 changes: 2 additions & 2 deletions lib/rules/gas-consumption/gas-calldata-parameters.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ const meta = {
note: 'This rule makes a soft check to see if argument is readOnly to make the suggestion. Check it manually before changing it.',
},
{
note: '[source 1](https://coinsbench.com/comprehensive-guide-tips-and-tricks-for-gas-optimization-in-solidity-5380db734404) of the rule initiciative (see Calldata vs Memory)',
note: '[source 1](https://coinsbench.com/comprehensive-guide-tips-and-tricks-for-gas-optimization-in-solidity-5380db734404) of the rule initiative (see Calldata vs Memory)',
},
{
note: '[source 2](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-6acr7) of the rule initiciative',
note: '[source 2](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-6acr7) of the rule initiative',
},
],
},
Expand Down
152 changes: 152 additions & 0 deletions lib/rules/gas-consumption/gas-increment-by-one.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/* eslint-disable */
const { isArray } = require('lodash')
const BaseChecker = require('../base-checker')

const operators = ['+', '-', '++', '--']
const binaryOperators = ['+', '-', '+=', '-=']

const ruleId = 'gas-increment-by-one'
const meta = {
type: 'gas-consumption',

docs: {
description: 'Suggest incrementation by one like this ++i instead of other type',
category: 'Gas Consumption Rules',
notes: [
{
note: 'This rule only works for expressions like this: [ j = j + 1 ] but will fail is the code is written like this: [ j = 1 + j ]',
},
{
note: '[source 1](https://coinsbench.com/comprehensive-guide-tips-and-tricks-for-gas-optimization-in-solidity-5380db734404) of the rule initiative (Incrementing/Decrementing By 1)',
},
{
note: '[source 2](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-8rekj) of the rule initiative',
},
],
},

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

schema: null,
}

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

UnaryOperation(node) {
if (node.isPrefix) {
// Ignore pre-increment and pre-decrement
return
}

if (node.operator === '++' || node.operator === '--') {
const variableName = this.extractVariableName(node.subExpression)
if (variableName) {
this.reportError(node, variableName)
}
}
}

BinaryOperation(node) {
if (node.operator === '=' || node.operator === '+=' || node.operator === '-=') {
const resultObject = this.isVariableIncrementOrDecrement(node.left, node.right, node.operator)
if (resultObject.result) {
this.reportError(node, resultObject.varName)
}
}
}

isVariableIncrementOrDecrement(left, right, operator) {
const leftVar = this.extractVariableName(left)
let rightValue

// asignment and operation
if (operator === '+=' || operator === '-=') {
rightValue = right.type === 'NumberLiteral' && parseInt(right.number) === 1
return { result: leftVar && rightValue, varName: leftVar }
}
// regular asignment
else if (operator === '=') {
const rightVar = this.extractVariableName(right.left)
rightValue =
right.right && right.right.type === 'NumberLiteral' && parseInt(right.right.number) === 1
return { result: leftVar === rightVar && rightValue, varName: leftVar }
}

return false
}

extractVariableName(node) {
if (node.type === 'Identifier') {
return node.name
} else if (node.type === 'MemberAccess') {
return this.extractVariableName(node.expression) + '.' + node.memberName
} else if (node.type === 'IndexAccess') {
return this.extractVariableName(node.base)
}
return null
}

reportError(node, fullVariableName) {
const variableName = fullVariableName.split('.')[0]
this.error(
node,
`GC: For [ ${variableName} ] variable, increment/decrement by 1 using: [ ++variable ] to save gas`
)
}
}

module.exports = GasIncrementByOne

// SourceUnit(node) {
// this.findVariableUpdates(node)
// }

// findVariableUpdates(node) {
// if (node === null || typeof node !== 'object') {
// return
// }

// // Check for assignment in a function body
// if (node.type === 'FunctionDefinition') {
// this.findVariableUpdates(node.body)
// return
// }

// if (node.type === 'ExpressionStatement' && node.expression.type === 'BinaryOperation') {
// const expr = node.expression
// if (expr.operator === '=' || expr.operator === '+=' || expr.operator === '-=') {
// if (this.isVariableIncrementOrDecrement(expr.left, expr.right, expr.operator)) {
// this.reportError(node, this.extractVariableName(expr.left))
// }
// }
// }

// Object.values(node).forEach((value) => {
// if (typeof value === 'object') {
// this.findVariableUpdates(value)
// }
// })
// }

// isVariableIncrementOrDecrement(left, right, operator) {
// const leftVar = this.extractVariableName(left)
// let rightVar, rightValue

// if (operator === '+=' || operator === '-=') {
// // For compound assignment, the operation is directly on the variable
// rightValue = right.type === 'NumberLiteral' && parseInt(right.number) === 1
// return leftVar && rightValue
// } else if (operator === '=') {
// rightVar = this.extractVariableName(right.left)
// rightValue =
// right.right && right.right.type === 'NumberLiteral' && parseInt(right.right.number) === 1
// return leftVar === rightVar && rightValue
// }

// return false
// }
2 changes: 1 addition & 1 deletion lib/rules/gas-consumption/gas-indexed-events.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const meta = {
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)',
note: '[source](https://coinsbench.com/comprehensive-guide-tips-and-tricks-for-gas-optimization-in-solidity-5380db734404) of the rule initiative (see Indexed Events)',
},
],
},
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/gas-consumption/gas-multitoken1155.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const meta = {
category: 'Gas Consumption Rules',
notes: [
{
note: '[source](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-8v8t9) of the rule initiciative',
note: '[source](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-8v8t9) of the rule initiative',
},
],
},
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/gas-consumption/gas-small-strings.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const meta = {
category: 'Gas Consumption Rules',
notes: [
{
note: '[source](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-ck1vq) of the rule initiciative',
note: '[source](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-ck1vq) of the rule initiative',
},
],
},
Expand Down
2 changes: 2 additions & 0 deletions lib/rules/gas-consumption/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const GasMultitoken1155 = require('./gas-multitoken1155')
const GasSmallStrings = require('./gas-small-strings')
const GasIndexedEvents = require('./gas-indexed-events')
const GasCalldataParameters = require('./gas-calldata-parameters')
const IncrementByOne = require('./gas-increment-by-one')

// module.exports = function checkers(reporter, config, tokens) {
module.exports = function checkers(reporter, config) {
Expand All @@ -10,5 +11,6 @@ module.exports = function checkers(reporter, config) {
new GasSmallStrings(reporter, config),
new GasIndexedEvents(reporter, config),
new GasCalldataParameters(reporter, config),
new IncrementByOne(reporter, config),
]
}
Loading

0 comments on commit a1cbcec

Please sign in to comment.