Skip to content

Commit

Permalink
Add files via upload
Browse files Browse the repository at this point in the history
  • Loading branch information
Dharma-09 authored Nov 2, 2023
1 parent 18ebb90 commit 7d69006
Show file tree
Hide file tree
Showing 80 changed files with 1,331 additions and 0 deletions.
10 changes: 10 additions & 0 deletions src/issues/GAS/0.8.19.ts
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;
12 changes: 12 additions & 0 deletions src/issues/GAS/DeleteUse.ts
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;
11 changes: 11 additions & 0 deletions src/issues/GAS/ERC721A.ts
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;
11 changes: 11 additions & 0 deletions src/issues/GAS/SafeMath.ts
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;
12 changes: 12 additions & 0 deletions src/issues/GAS/abiEncodePacked.ts
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;
33 changes: 33 additions & 0 deletions src/issues/GAS/addressBalance.ts
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;
39 changes: 39 additions & 0 deletions src/issues/GAS/addressZero.ts
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;
12 changes: 12 additions & 0 deletions src/issues/GAS/assemblyCall.ts
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;
24 changes: 24 additions & 0 deletions src/issues/GAS/assemblyEmit.ts
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) }
*/
12 changes: 12 additions & 0 deletions src/issues/GAS/assemblySmallHashes.ts
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;
64 changes: 64 additions & 0 deletions src/issues/GAS/assignUpdateArray.ts
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;
14 changes: 14 additions & 0 deletions src/issues/GAS/blockTimestampEmit.ts
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;

13 changes: 13 additions & 0 deletions src/issues/GAS/boolIncursOverhead.ts
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;
13 changes: 13 additions & 0 deletions src/issues/GAS/cacheArrayLength.ts
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;
92 changes: 92 additions & 0 deletions src/issues/GAS/cacheVariable.ts
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;
Loading

0 comments on commit 7d69006

Please sign in to comment.