Skip to content

Commit 4d99314

Browse files
committed
gas: increment by one
1 parent 8c2f05a commit 4d99314

12 files changed

+215
-16
lines changed

docs/rules.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@ title: "Rule Index of Solhint"
2727

2828
## Gas Consumption Rules
2929

30-
| Rule Id | Error | Recommended | Deprecated |
31-
| ----------------------------------------------------------------------------- | -------------------------------------------------------------- | ----------- | ---------- |
32-
| [gas-calldata-parameters](./rules/gas-consumption/gas-calldata-parameters.md) | Suggest calldata keyword on function arguments when read only | | |
33-
| [gas-indexed-events](./rules/gas-consumption/gas-indexed-events.md) | Suggest indexed arguments on events for uint, bool and address | | |
34-
| [gas-multitoken1155](./rules/gas-consumption/gas-multitoken1155.md) | ERC1155 is a cheaper non-fungible token than ERC721 | | |
35-
| [gas-small-strings](./rules/gas-consumption/gas-small-strings.md) | Keep strings smaller than 32 bytes | | |
30+
| Rule Id | Error | Recommended | Deprecated |
31+
| ----------------------------------------------------------------------------- | ----------------------------------------------------------------- | ----------- | ---------- |
32+
| [gas-calldata-parameters](./rules/gas-consumption/gas-calldata-parameters.md) | Suggest calldata keyword on function arguments when read only | | |
33+
| [gas-increment-by-one](./rules/gas-consumption/gas-increment-by-one.md) | Suggest incrementation by one like this ++i instead of other type | | |
34+
| [gas-indexed-events](./rules/gas-consumption/gas-indexed-events.md) | Suggest indexed arguments on events for uint, bool and address | | |
35+
| [gas-multitoken1155](./rules/gas-consumption/gas-multitoken1155.md) | ERC1155 is a cheaper non-fungible token than ERC721 | | |
36+
| [gas-small-strings](./rules/gas-consumption/gas-small-strings.md) | Keep strings smaller than 32 bytes | | |
3637
3738

3839
## Miscellaneous

docs/rules/gas-consumption/gas-calldata-parameters.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ This rule accepts a string option of rule severity. Must be one of "error", "war
2626
### Notes
2727
- Only applies for external functions when receiving arguments with [memory] keyword
2828
- This rule makes a soft check to see if argument is readOnly to make the suggestion. Check it manually before changing it.
29-
- [source 1](https://coinsbench.com/comprehensive-guide-tips-and-tricks-for-gas-optimization-in-solidity-5380db734404) of the rule initiciative (see Calldata vs Memory)
30-
- [source 2](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-6acr7) of the rule initiciative
29+
- [source 1](https://coinsbench.com/comprehensive-guide-tips-and-tricks-for-gas-optimization-in-solidity-5380db734404) of the rule initiative (see Calldata vs Memory)
30+
- [source 2](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-6acr7) of the rule initiative
3131

3232
## Examples
3333
This rule does not have examples.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
warning: "This is a dynamically generated file. Do not edit manually."
3+
layout: "default"
4+
title: "gas-increment-by-one | Solhint"
5+
---
6+
7+
# gas-increment-by-one
8+
![Category Badge](https://img.shields.io/badge/-Gas%20Consumption%20Rules-informational)
9+
![Default Severity Badge warn](https://img.shields.io/badge/Default%20Severity-warn-yellow)
10+
11+
## Description
12+
Suggest incrementation by one like this ++i instead of other type
13+
14+
## Options
15+
This rule accepts a string option of rule severity. Must be one of "error", "warn", "off". Default to warn.
16+
17+
### Example Config
18+
```json
19+
{
20+
"rules": {
21+
"gas-increment-by-one": "warn"
22+
}
23+
}
24+
```
25+
26+
### Notes
27+
- This rule only works for expressions like this: [ j = j + 1 ] but will fail is the code is written like this: [ j = 1 + j ]
28+
- [source 1](https://coinsbench.com/comprehensive-guide-tips-and-tricks-for-gas-optimization-in-solidity-5380db734404) of the rule initiative (Incrementing/Decrementing By 1)
29+
- [source 2](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-8rekj) of the rule initiative
30+
31+
## Examples
32+
This rule does not have examples.
33+
34+
## Version
35+
This rule is introduced in the latest version.
36+
37+
## Resources
38+
- [Rule source](https://github.com/protofire/solhint/tree/master/lib/rules/gas-consumption/gas-increment-by-one.js)
39+
- [Document source](https://github.com/protofire/solhint/tree/master/docs/rules/gas-consumption/gas-increment-by-one.md)
40+
- [Test cases](https://github.com/protofire/solhint/tree/master/test/rules/gas-consumption/gas-increment-by-one.js)

docs/rules/gas-consumption/gas-indexed-events.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ This rule accepts a string option of rule severity. Must be one of "error", "war
2424
```
2525

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

2929
## Examples
3030
This rule does not have examples.

docs/rules/gas-consumption/gas-multitoken1155.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ This rule accepts a string option of rule severity. Must be one of "error", "war
2424
```
2525

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

2929
## Examples
3030
This rule does not have examples.

docs/rules/gas-consumption/gas-small-strings.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ This rule accepts a string option of rule severity. Must be one of "error", "war
2424
```
2525

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

2929
## Examples
3030
This rule does not have examples.

lib/rules/gas-consumption/gas-calldata-parameters.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ const meta = {
3030
note: 'This rule makes a soft check to see if argument is readOnly to make the suggestion. Check it manually before changing it.',
3131
},
3232
{
33-
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)',
33+
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)',
3434
},
3535
{
36-
note: '[source 2](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-6acr7) of the rule initiciative',
36+
note: '[source 2](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-6acr7) of the rule initiative',
3737
},
3838
],
3939
},
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/* eslint-disable */
2+
const { isArray } = require('lodash')
3+
const BaseChecker = require('../base-checker')
4+
5+
const operators = ['+', '-', '++', '--']
6+
const binaryOperators = ['+', '-', '+=', '-=']
7+
8+
const ruleId = 'gas-increment-by-one'
9+
const meta = {
10+
type: 'gas-consumption',
11+
12+
docs: {
13+
description: 'Suggest incrementation by one like this ++i instead of other type',
14+
category: 'Gas Consumption Rules',
15+
notes: [
16+
{
17+
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 ]',
18+
},
19+
{
20+
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)',
21+
},
22+
{
23+
note: '[source 2](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-8rekj) of the rule initiative',
24+
},
25+
],
26+
},
27+
28+
isDefault: false,
29+
recommended: false,
30+
defaultSetup: 'warn',
31+
32+
schema: null,
33+
}
34+
35+
class GasIncrementByOne extends BaseChecker {
36+
constructor(reporter) {
37+
super(reporter, ruleId, meta)
38+
}
39+
40+
UnaryOperation(node) {
41+
if (node.isPrefix) {
42+
// Ignore pre-increment and pre-decrement
43+
return
44+
}
45+
46+
if (node.operator === '++' || node.operator === '--') {
47+
const variableName = this.extractVariableName(node.subExpression)
48+
if (variableName) {
49+
this.reportError(node, variableName)
50+
}
51+
}
52+
}
53+
54+
BinaryOperation(node) {
55+
if (node.operator === '=' || node.operator === '+=' || node.operator === '-=') {
56+
const resultObject = this.isVariableIncrementOrDecrement(node.left, node.right, node.operator)
57+
if (resultObject.result) {
58+
this.reportError(node, resultObject.varName)
59+
}
60+
}
61+
}
62+
63+
isVariableIncrementOrDecrement(left, right, operator) {
64+
const leftVar = this.extractVariableName(left)
65+
let rightValue
66+
67+
// asignment and operation
68+
if (operator === '+=' || operator === '-=') {
69+
rightValue = right.type === 'NumberLiteral' && parseInt(right.number) === 1
70+
return { result: leftVar && rightValue, varName: leftVar }
71+
}
72+
// regular asignment
73+
else if (operator === '=') {
74+
const rightVar = this.extractVariableName(right.left)
75+
rightValue =
76+
right.right && right.right.type === 'NumberLiteral' && parseInt(right.right.number) === 1
77+
return { result: leftVar === rightVar && rightValue, varName: leftVar }
78+
}
79+
80+
return false
81+
}
82+
83+
extractVariableName(node) {
84+
if (node.type === 'Identifier') {
85+
return node.name
86+
} else if (node.type === 'MemberAccess') {
87+
return this.extractVariableName(node.expression) + '.' + node.memberName
88+
} else if (node.type === 'IndexAccess') {
89+
return this.extractVariableName(node.base)
90+
}
91+
return null
92+
}
93+
94+
reportError(node, fullVariableName) {
95+
const variableName = fullVariableName.split('.')[0]
96+
this.error(
97+
node,
98+
`GC: For [ ${variableName} ] variable, increment/decrement by 1 using: [ ++variable ] to save gas`
99+
)
100+
}
101+
}
102+
103+
module.exports = GasIncrementByOne
104+
105+
// unary operations
106+
// if (node.expression.type === 'UnaryOperation') {
107+
// }
108+
109+
// SourceUnit(node) {
110+
// this.findVariableUpdates(node)
111+
// }
112+
113+
// findVariableUpdates(node) {
114+
// if (node === null || typeof node !== 'object') {
115+
// return
116+
// }
117+
118+
// // Check for assignment in a function body
119+
// if (node.type === 'FunctionDefinition') {
120+
// this.findVariableUpdates(node.body)
121+
// return
122+
// }
123+
124+
// if (node.type === 'ExpressionStatement' && node.expression.type === 'BinaryOperation') {
125+
// const expr = node.expression
126+
// if (expr.operator === '=' || expr.operator === '+=' || expr.operator === '-=') {
127+
// if (this.isVariableIncrementOrDecrement(expr.left, expr.right, expr.operator)) {
128+
// this.reportError(node, this.extractVariableName(expr.left))
129+
// }
130+
// }
131+
// }
132+
133+
// Object.values(node).forEach((value) => {
134+
// if (typeof value === 'object') {
135+
// this.findVariableUpdates(value)
136+
// }
137+
// })
138+
// }
139+
140+
// isVariableIncrementOrDecrement(left, right, operator) {
141+
// const leftVar = this.extractVariableName(left)
142+
// let rightVar, rightValue
143+
144+
// if (operator === '+=' || operator === '-=') {
145+
// // For compound assignment, the operation is directly on the variable
146+
// rightValue = right.type === 'NumberLiteral' && parseInt(right.number) === 1
147+
// return leftVar && rightValue
148+
// } else if (operator === '=') {
149+
// rightVar = this.extractVariableName(right.left)
150+
// rightValue =
151+
// right.right && right.right.type === 'NumberLiteral' && parseInt(right.right.number) === 1
152+
// return leftVar === rightVar && rightValue
153+
// }
154+
155+
// return false
156+
// }

lib/rules/gas-consumption/gas-indexed-events.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const meta = {
1010
category: 'Gas Consumption Rules',
1111
notes: [
1212
{
13-
note: '[source](https://coinsbench.com/comprehensive-guide-tips-and-tricks-for-gas-optimization-in-solidity-5380db734404) of the rule initiciative (see Indexed Events)',
13+
note: '[source](https://coinsbench.com/comprehensive-guide-tips-and-tricks-for-gas-optimization-in-solidity-5380db734404) of the rule initiative (see Indexed Events)',
1414
},
1515
],
1616
},

lib/rules/gas-consumption/gas-multitoken1155.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const meta = {
99
category: 'Gas Consumption Rules',
1010
notes: [
1111
{
12-
note: '[source](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-8v8t9) of the rule initiciative',
12+
note: '[source](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-8v8t9) of the rule initiative',
1313
},
1414
],
1515
},

lib/rules/gas-consumption/gas-small-strings.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const meta = {
99
category: 'Gas Consumption Rules',
1010
notes: [
1111
{
12-
note: '[source](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-ck1vq) of the rule initiciative',
12+
note: '[source](https://www.rareskills.io/post/gas-optimization?postId=c9db474a-ff97-4fa3-a51d-fe13ccb8fe3b#viewer-ck1vq) of the rule initiative',
1313
},
1414
],
1515
},

lib/rules/gas-consumption/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const GasMultitoken1155 = require('./gas-multitoken1155')
22
const GasSmallStrings = require('./gas-small-strings')
33
const GasIndexedEvents = require('./gas-indexed-events')
44
const GasCalldataParameters = require('./gas-calldata-parameters')
5+
const IncrementByOne = require('./gas-increment-by-one')
56

67
// module.exports = function checkers(reporter, config, tokens) {
78
module.exports = function checkers(reporter, config) {
@@ -10,5 +11,6 @@ module.exports = function checkers(reporter, config) {
1011
new GasSmallStrings(reporter, config),
1112
new GasIndexedEvents(reporter, config),
1213
new GasCalldataParameters(reporter, config),
14+
new IncrementByOne(reporter, config),
1315
]
1416
}

0 commit comments

Comments
 (0)