-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
80 changed files
with
1,331 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { IssueTypes, RegexIssue } from '../../types'; | ||
|
||
const issue: RegexIssue = { | ||
regexOrAST: 'Regex', | ||
type: IssueTypes.GAS, | ||
title: 'Reduce gas usage by moving to Solidity 0.8.19 or later', | ||
regex: /pragma solidity 0.8.1[1-8];/g, | ||
}; | ||
|
||
export default issue; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { IssueTypes, RegexIssue } from '../../types'; | ||
|
||
const issue: RegexIssue = { | ||
regexOrAST: 'Regex', | ||
type: IssueTypes.GAS, | ||
title: 'Using delete instead of setting mapping/struct to 0 saves gas', | ||
regex: /^\s*\b(?:\w+|(\w+\.\w+))\b\s*=\s*0;/g, | ||
description:"Avoid an assignment by deleting the value instead of setting it to zero.", | ||
gasCost:5, | ||
}; | ||
|
||
export default issue; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { IssueTypes, RegexIssue } from '../../types'; | ||
|
||
const issue: RegexIssue = { | ||
regexOrAST: 'Regex', | ||
type: IssueTypes.GAS, | ||
title: 'Use ERC721A instead ERC721', | ||
description:"ERC721A standard, ERC721A is an improvement standard for ERC721 tokens. It was proposed by the Azuki team and used for developing their NFT collection. Compared with ERC721, ERC721A is a more gas-efficient standard to mint a lot of of NFTs simultaneously. It allows developers to mint multiple NFTs at the same gas price. This has been a great improvement due to Ethereum's sky-rocketing gas fee. [Reffrence](https://nextrope.com/erc721-vs-erc721a-2/)", | ||
regex: /ERC721\.sol/g, | ||
}; | ||
|
||
export default issue; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { IssueTypes, RegexIssue } from '../../types'; | ||
|
||
const issue: RegexIssue = { | ||
regexOrAST: 'Regex', | ||
type: IssueTypes.GAS, | ||
title: "Don't use SafeMath once the solidity version is 0.8.0 or greater", | ||
description:" 0.8.0 introduces internal overflow checks, so using SafeMath is redundant and adds overhead", | ||
regex: /import\s+.*from\s+".*\/SafeMath.sol";/g, | ||
}; | ||
|
||
export default issue; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { IssueTypes, RegexIssue } from '../../types'; | ||
|
||
const issue: RegexIssue = { | ||
regexOrAST: 'Regex', | ||
type: IssueTypes.GAS, | ||
title: '`abi.encode` is more efficient than `abi.encodePacked`', | ||
description:'`abi.encode` uses less gas than `abi.encodePacked`: the gas saved depends on the number of arguments, with an average of ~90 per argument. Test available here.', | ||
gasCost:5, | ||
regex: /\babi\.encodePacked\b/g, | ||
}; | ||
|
||
export default issue; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { InputType, IssueTypes, Instance, ASTIssue } from '../../types'; | ||
import { findAll } from 'solidity-ast/utils'; | ||
import { instanceFromSRC } from '../../utils'; | ||
//@audit-issue | ||
const issue: ASTIssue = { | ||
regexOrAST: 'AST', | ||
type: IssueTypes.GAS, | ||
title: 'Use `selfbalance()` instead of `address(this).balance`', | ||
description: | ||
"Use assembly when getting a contract's balance of ETH.\n\nYou can use `selfbalance()` instead of `address(this).balance` when getting your contract's balance of ETH to save gas.\nAdditionally, you can use `balance(address)` instead of `address.balance()` when getting an external contract's balance of ETH.\n\n*Saves 15 gas when checking internal balance, 6 for external*", | ||
detector: (files: InputType): Instance[] => { | ||
let instances: Instance[] = []; | ||
|
||
for (const file of files) { | ||
if (!!file.ast) { | ||
for (const node of findAll('MemberAccess', file.ast)) { | ||
// Look for Address(X).balance | ||
if ( | ||
node.nodeType === 'MemberAccess' && | ||
node.memberName === 'balance' && | ||
node.expression.nodeType === 'FunctionCall' && | ||
node.expression.typeDescriptions.typeString === 'address' | ||
) { | ||
instances.push(instanceFromSRC(file, node.src)); | ||
} | ||
} | ||
} | ||
} | ||
return instances; | ||
}, | ||
}; | ||
|
||
export default issue; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { InputType, IssueTypes, Instance, ASTIssue } from '../../types'; | ||
import { findAll } from 'solidity-ast/utils'; | ||
import { instanceFromSRC } from '../../utils'; | ||
import { Expression, SourceUnit } from 'solidity-ast'; | ||
//@audit-issue | ||
const issue: ASTIssue = { | ||
regexOrAST: 'AST', | ||
type: IssueTypes.GAS, | ||
title: 'Use assembly to check for `address(0)`', | ||
description: '*Saves 6 gas per instance*', | ||
gasCost:6, | ||
detector: (files: InputType): Instance[] => { | ||
let instances: Instance[] = []; | ||
|
||
const addressZero = (node: Expression): boolean => { | ||
return ( | ||
node.nodeType === 'FunctionCall' && | ||
node.kind === 'typeConversion' && | ||
node.arguments?.[0].nodeType === 'Literal' && | ||
node.arguments?.[0].value === '0' | ||
); | ||
}; | ||
|
||
for (const file of files) { | ||
if (!!file.ast) { | ||
for (const node of findAll('BinaryOperation', file.ast)) { | ||
if (node.operator === '!=' || node.operator === '==') { | ||
if (addressZero(node.rightExpression) || addressZero(node.leftExpression)) { | ||
instances.push(instanceFromSRC(file, node.src)); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
return instances; | ||
}, | ||
}; | ||
|
||
export default issue; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { IssueTypes, RegexIssue } from '../../types'; | ||
|
||
const issue: RegexIssue = { | ||
regexOrAST: 'Regex', | ||
type: IssueTypes.GAS, | ||
title:'With assembly, .call (bool success) transfer can be done gas-optimized', | ||
gasCost:248, | ||
description:"When using assembly language, it is possible to call the transfer function of an Ethereum contract in a gas-optimized way by using the .call function with specific input parameters. The .call function takes a number of input parameters, including the address of the contract to call, the amount of Ether to transfer, and a specification of the gas limit for the call. By specifying a lower gas limit than the default, it is possible to reduce the gas cost of the transfer.", | ||
regex: /\.call/g, | ||
}; | ||
|
||
export default issue; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { IssueTypes, RegexIssue } from '../../types'; | ||
|
||
const issue: RegexIssue = { | ||
regexOrAST: 'Regex', | ||
type: IssueTypes.GAS, | ||
title:'Use assembly to emit events', | ||
description:"We can use assembly to emit events efficiently by utilizing `scratch space` and the `free memory pointer`. This will allow us to potentially avoid memory expansion costs. Note: In order to do this optimization safely, we will need to cache and restore the free memory pointer.", | ||
gasCost:38, | ||
regex: /emit/g, | ||
}; | ||
|
||
export default issue; | ||
/** | ||
// uint256 id, uint256 value, uint256 amount | ||
emit eventSentAmountExample(id, value, amount); | ||
assembly { | ||
let memptr := mload(0x40) | ||
mstore(0x00, calldataload(0x44)) | ||
mstore(0x20, calldataload(0xa4)) | ||
mstore(0x40, amount) log1( 0x00, 0x60, // keccak256("eventSentAmountExample(uint256,uint256,uint256)") 0xa622cf392588fbf2cd020ff96b2f4ebd9c76d7a4bc7f3e6b2f18012312e76bc3 ) | ||
mstore(0x40, memptr) } | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { IssueTypes, RegexIssue } from '../../types'; | ||
|
||
const issue: RegexIssue = { | ||
regexOrAST: 'Regex', | ||
type: IssueTypes.GAS, | ||
title: 'Use assembly for small keccak256 hashes, in order to save gas', | ||
description:'Use assembly for small keccak256 hashes, in order to save gas', | ||
regex: /keccak256\s*\(\s*abi\.encode\s*\(\s*[^)]{1,40}\)\s*\)/gm, | ||
gasCost:120, | ||
}; | ||
|
||
export default issue; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { InputType, IssueTypes, Instance, ASTIssue } from '../../types'; | ||
import { findAll } from 'solidity-ast/utils'; | ||
import { instanceFromSRC } from '../../utils'; | ||
//@audit-issue | ||
const issue: ASTIssue = { | ||
regexOrAST: 'AST', | ||
type: IssueTypes.GAS, | ||
title: '`array[index] += amount` is cheaper than `array[index] = array[index] + amount` (or related variants)', | ||
description: | ||
'When updating a value in an array with arithmetic, using `array[index] += amount` is cheaper than `array[index] = array[index] + amount`.\nThis is because you avoid an additonal `mload` when the array is stored in memory, and an `sload` when the array is stored in storage.\nThis can be applied for any arithmetic operation including `+=`, `-=`,`/=`,`*=`,`^=`,`&=`, `%=`, `<<=`,`>>=`, and `>>>=`.\nThis optimization can be particularly significant if the pattern occurs during a loop.\n\n*Saves 28 gas for a storage array, 38 for a memory array*', | ||
detector: (files: InputType): Instance[] => { | ||
let instances: Instance[] = []; | ||
|
||
for (const file of files) { | ||
if (!!file.ast) { | ||
for (const node of findAll('Assignment', file.ast)) { | ||
if ( | ||
node.operator === '=' && | ||
node.leftHandSide.nodeType === 'IndexAccess' && | ||
node.rightHandSide.nodeType === 'BinaryOperation' | ||
// (node.leftHandSide.typeDescriptions.typeString?.includes('[] storage') || | ||
// node.leftHandSide.typeDescriptions.typeString?.includes('[] memory')) | ||
) { | ||
let array; | ||
if (node.leftHandSide.baseExpression.nodeType === 'Identifier') { | ||
array = node.leftHandSide.baseExpression.name; | ||
} | ||
let index; | ||
if (node.leftHandSide.indexExpression?.nodeType === 'Literal') { | ||
index = node.leftHandSide.indexExpression.value; | ||
} | ||
|
||
if (node.rightHandSide.rightExpression.nodeType === 'IndexAccess') { | ||
if ( | ||
(!array || | ||
(node.rightHandSide.rightExpression.baseExpression.nodeType === 'Identifier' && | ||
array === node.rightHandSide.rightExpression.baseExpression.name)) && | ||
(!index || | ||
(node.rightHandSide.rightExpression.indexExpression?.nodeType === 'Literal' && | ||
index === node.rightHandSide.rightExpression.indexExpression?.value)) | ||
) { | ||
instances.push(instanceFromSRC(file, node.src)); | ||
} | ||
} else if (node.rightHandSide.leftExpression.nodeType === 'IndexAccess') { | ||
if ( | ||
(!array || | ||
(node.rightHandSide.leftExpression.baseExpression.nodeType === 'Identifier' && | ||
array === node.rightHandSide.leftExpression.baseExpression.name)) && | ||
(!index || | ||
(node.rightHandSide.leftExpression.indexExpression?.nodeType === 'Literal' && | ||
index === node.rightHandSide.leftExpression.indexExpression?.value)) | ||
) { | ||
instances.push(instanceFromSRC(file, node.src)); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
return instances; | ||
}, | ||
}; | ||
|
||
export default issue; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
|
||
import { IssueTypes, RegexIssue } from '../../types'; | ||
|
||
const issue: RegexIssue = { | ||
regexOrAST: 'Regex', | ||
type: IssueTypes.GAS, | ||
title:'Redundant event fields can be removed', | ||
description:"Some parameters (block.timestamp and block.number) are added to event information by default so re-adding them wastes gas, as they are already included.", | ||
regex: /(emit.*block\.timestamp|block\.timestamp.*emit)/gm, | ||
gasCost:358, | ||
}; | ||
|
||
export default issue; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { IssueTypes, RegexIssue } from '../../types'; | ||
|
||
const issue: RegexIssue = { | ||
regexOrAST: 'Regex', | ||
type: IssueTypes.GAS, | ||
title: 'Use uint256(1)/uint256(2) instead for true and false boolean states', | ||
gasCost:17100, | ||
description: | ||
'Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas), and to avoid Gsset (20000 gas) when changing from ‘false’ to ‘true’, after having been ‘true’ in the past. See [source](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/58f635312aa21f947cae5f8578638a85aa2519f5/contracts/security/ReentrancyGuard.sol#L23-L27).', | ||
regex: /(bool.?(public|private|internal).?.?[a-zA-Z]*|\=\>.*bool|struct?.{[^}]*bool)/g, // storage variable | part of mapping | struct | ||
}; | ||
|
||
export default issue; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { IssueTypes, RegexIssue } from '../../types'; | ||
|
||
const issue: RegexIssue = { | ||
regexOrAST: 'Regex', | ||
type: IssueTypes.GAS, | ||
title: '<array>.length should not be looked up in every loop of a for-loop', | ||
description: | ||
'If not cached, the solidity compiler will always read the length of the array during each iteration. That is, if it is a storage array, this is an extra sload operation (100 additional extra gas for each iteration except for the first) and if it is a memory array, this is an extra mload operation (3 additional gas for each iteration except for the first).', | ||
regex: /for?.(.*\.length)/g, | ||
gasCost:3, | ||
}; | ||
|
||
export default issue; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import { InputType, IssueTypes, Instance, ASTIssue } from '../../types'; | ||
import { findAll } from 'solidity-ast/utils'; | ||
import { getStorageVariable, instanceFromSRC } from '../../utils'; | ||
import { Identifier } from 'solidity-ast'; | ||
//@audit-issue | ||
const issue: ASTIssue = { | ||
regexOrAST: 'AST', | ||
type: IssueTypes.GAS, | ||
title: 'State variables should be cached in stack variables rather than re-reading them from storage', | ||
gasCost:248, | ||
description: | ||
'The instances below point to the second+ access of a state variable within a function. Caching of a state variable replaces each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.\n\n*Saves 100 gas per instance*', | ||
detector: (files: InputType): Instance[] => { | ||
let instances: Instance[] = []; | ||
|
||
for (const file of files) { | ||
if (!!file.ast) { | ||
for (const contract of findAll('ContractDefinition', file.ast)) { | ||
/** Build list of storage variables */ | ||
let storageVariables = getStorageVariable(contract); | ||
|
||
for (const func of findAll('FunctionDefinition', contract)) { | ||
/** Check all storage reads */ | ||
let identifiers: Identifier[] = []; | ||
|
||
/** Could be an expression */ | ||
for (const expr of findAll('ExpressionStatement', func)) { | ||
for (const op of [...findAll('Assignment', expr)]) { | ||
if (op.rightHandSide.nodeType === 'Identifier' && storageVariables.includes(op.rightHandSide.name)) { | ||
identifiers.push(op.rightHandSide); | ||
} | ||
if (op.rightHandSide.nodeType === 'BinaryOperation') { | ||
const bin = op.rightHandSide; | ||
if ( | ||
bin.rightExpression.nodeType === 'Identifier' && | ||
storageVariables.includes(bin.rightExpression.name) | ||
) { | ||
identifiers.push(bin.rightExpression); | ||
} | ||
|
||
if ( | ||
bin.leftExpression.nodeType === 'Identifier' && | ||
storageVariables.includes(bin.leftExpression.name) | ||
) { | ||
identifiers.push(bin.leftExpression); | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** Could be a declaration */ | ||
for (const decl of findAll('VariableDeclarationStatement', func)) { | ||
if (decl.initialValue?.nodeType === 'Identifier') { | ||
identifiers.push(decl.initialValue); | ||
} | ||
} | ||
|
||
/** Could be an Call */ | ||
for (const decl of findAll('FunctionCall', func)) { | ||
for (const id of decl.arguments) { | ||
if (id.nodeType === 'Identifier') { | ||
identifiers.push(id); | ||
} | ||
} | ||
} | ||
|
||
/** Sort */ | ||
identifiers.sort((a, b) => (a.src < b.src ? -1 : 1)); | ||
|
||
for (const variableName of storageVariables) { | ||
/** Check variable isn't local */ | ||
let isStorage = true; | ||
for (const decl of findAll('VariableDeclaration', func)) { | ||
if (decl.name === variableName) { | ||
isStorage = false; | ||
} | ||
} | ||
/** Check that there is more than 2 reads */ | ||
const occurences = identifiers.filter(r => r.name === variableName); | ||
if (isStorage && occurences?.length > 1) { | ||
instances.push(instanceFromSRC(file, occurences[1].src)); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
return instances; | ||
}, | ||
}; | ||
|
||
export default issue; |
Oops, something went wrong.