Skip to content

Commit

Permalink
initial commit with first 7 tested optimizations
Browse files Browse the repository at this point in the history
  • Loading branch information
devdacian committed Jan 22, 2025
1 parent 7b4aa92 commit 276de92
Show file tree
Hide file tree
Showing 20 changed files with 531 additions and 57 deletions.
19 changes: 0 additions & 19 deletions script/Counter.s.sol

This file was deleted.

28 changes: 28 additions & 0 deletions src/01-dont-init-def-val/IdRegDontInitDefVal.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {IdRegUnop} from "../IdRegUnop.sol";

contract IdRegDontInitDefVal is IdRegUnop {
function generateIds(uint256 numIds, address[] memory owners) external override {
if(numIds != owners.length)
revert NumIdsOwnersLengthMismatch(numIds, owners.length);

// @audit don't initialize loop variable to zero as solidity
// automatically initializes variable to their default value
// note: test shows no gas savings but does eliminate useless code
for(uint256 i; i<numIds; i++) {
// read next id from storage
uint256 newId = nextId;

// first id should start at 1
if(newId == 0) newId = 1;

// update the mapping
idToOwner[newId] = owners[i];

// update storage to increment next id
nextId = newId + 1;
}
}
}
31 changes: 31 additions & 0 deletions src/02-init-past-def-val/IdRegInitPastDefVal.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {IdRegUnop} from "../IdRegUnop.sol";

contract IdRegInitPastDefVal is IdRegUnop {
// @audit since first id is always 1 initialize
// storage past default 0 value
constructor() {
nextId = 1;
}

function generateIds(uint256 numIds, address[] memory owners) external override {
if(numIds != owners.length)
revert NumIdsOwnersLengthMismatch(numIds, owners.length);

for(uint256 i; i<numIds; i++) {
// read next id from storage
uint256 newId = nextId;

// @audit don't need to check against 0 every time
// as we now initialize past 0

// update the mapping
idToOwner[newId] = owners[i];

// update storage to increment next id
nextId = newId + 1;
}
}
}
27 changes: 27 additions & 0 deletions src/03-calldata-array-input/IdRegCalldataArrayInput.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {IdRegUnop} from "../IdRegUnop.sol";

contract IdRegCalldataArrayInput is IdRegUnop {
constructor() {
nextId = 1;
}

// @audit use `calldata` for read-only array inputs
function generateIds(uint256 numIds, address[] calldata owners) external override {
if(numIds != owners.length)
revert NumIdsOwnersLengthMismatch(numIds, owners.length);

for(uint256 i; i<numIds; i++) {
// read next id from storage
uint256 newId = nextId;

// update the mapping
idToOwner[newId] = owners[i];

// update storage to increment next id
nextId = newId + 1;
}
}
}
33 changes: 33 additions & 0 deletions src/04-read-write-storage-once/IdRegReadWriteStorageOnce.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {IdRegUnop} from "../IdRegUnop.sol";

contract IdRegReadWriteStorageOnce is IdRegUnop {
constructor() {
nextId = 1;
}

function generateIds(uint256 numIds, address[] calldata owners) external override {
if(numIds != owners.length)
revert NumIdsOwnersLengthMismatch(numIds, owners.length);

// @audit read `nextId` from storage once
// read next id from storage
uint256 newId = nextId;

for(uint256 i; i<numIds; i++) {
// update the mapping
// @audit use cached `newId` to set idToOwner storage
// then increment cached `newId`
idToOwner[newId++] = owners[i];
}

// @audit write final `newId` to `nextId` storage once
nextId = newId;

// instead of reading from and to writing `nextId` storage
// during every loop iteration, we now only read and write
// storage once which is cheaper
}
}
12 changes: 12 additions & 0 deletions src/05-del-to-def-val/IdRegDelToDefVal.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {IdRegUnop} from "../IdRegUnop.sol";

contract IdRegDelToDefVal is IdRegUnop {
function resetId(uint256 id) external override {
// @audit instead of writing the default value to storage,
// use `delete` for a potential gas refund
delete idToOwner[id];
}
}
20 changes: 20 additions & 0 deletions src/06-dont-cache-calldata-length/IdRegDontCacheCalldataLength.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {IdRegUnop} from "../IdRegUnop.sol";

contract IdRegDontCacheCalldataLength is IdRegUnop {
function getOwnersForIds(uint256[] calldata ids) external view override returns(address[] memory) {
// @audit don't cache calldata length
// allocate output array in memory
address[] memory owners = new address[](ids.length);

// populate output array
for(uint256 i; i<ids.length; i++) {
owners[i] = idToOwner[ids[i]];
}

// return output array
return owners;
}
}
18 changes: 18 additions & 0 deletions src/07-use-named-return/IdRegUseNamedReturn.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {IdRegUnop} from "../IdRegUnop.sol";

contract IdRegUseNamedReturn is IdRegUnop {
function getOwnersForIds(uint256[] calldata ids) external view override returns(address[] memory owners) {
// @audit using named return, allocate output array in memory
owners = new address[](ids.length);

// populate output array
for(uint256 i; i<ids.length; i++) {
owners[i] = idToOwner[ids[i]];
}

// @audit removed explicit `return` statement
}
}
14 changes: 0 additions & 14 deletions src/Counter.sol

This file was deleted.

19 changes: 19 additions & 0 deletions src/IIdReg.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

interface IIdReg {
// errors
error NumIdsOwnersLengthMismatch(uint256 numIds, uint256 ownersLength);

// public API
//
// view functions
function nextId() external view returns(uint256);
function idToOwner(uint256 id) external view returns(address);
function getOwnersForIds(uint256[] calldata ids) external view returns(address[] memory);

// non-view functions which change state
function generateIds(uint256 numIds, address[] memory owners) external;
function resetId(uint256 id) external;

}
58 changes: 58 additions & 0 deletions src/IdRegUnop.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import { IIdReg } from "./IIdReg.sol";

// unoptimized implementation
contract IdRegUnop is IIdReg {
// next available id
uint256 public nextId;

// mapping of valid ids to their owner
mapping(uint256 id => address owner) public idToOwner;

// creates `numIds` new valid ids in ascending
// order from previously created ids
// @audit external and public result in same gas cost, tested
// manually as this would break compilation with existing structure
function generateIds(uint256 numIds, address[] memory owners) external virtual {
if(numIds != owners.length)
revert NumIdsOwnersLengthMismatch(numIds, owners.length);

for(uint256 i=0; i<numIds; i++) {
// read next id from storage
uint256 newId = nextId;

// first id should start at 1
if(newId == 0) newId = 1;

// update the mapping
idToOwner[newId] = owners[i];

// update storage to increment next id
nextId = newId + 1;
}
}

// reset an id back to no owner
function resetId(uint256 id) external virtual {
idToOwner[id] = address(0);
}

// get owners for given list of ids
function getOwnersForIds(uint256[] calldata ids) external view virtual returns(address[] memory) {
// cache length
uint256 idsLength = ids.length;

// allocate output array in memory
address[] memory owners = new address[](idsLength);

// populate output array
for(uint256 i; i<idsLength; i++) {
owners[i] = idToOwner[ids[i]];
}

// return output array
return owners;
}
}
28 changes: 28 additions & 0 deletions test/01-dont-init-def-val/IdRegDontInitDefValTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {IdRegUnopTest} from "../IdRegUnopTest.sol";
import {IdRegDontInitDefVal} from "../../src/01-dont-init-def-val/IdRegDontInitDefVal.sol";

// Optimizer ON, 10000 runs
// ========================
// Pre : 142049 gas
// forge test --optimizer-runs 10000 --match-contract IdRegUnopTest --match-test test_generateIds -vvv
// Post : 142049 gas (no improvement)
// forge test --optimizer-runs 10000 --match-contract IdRegDontInitDefValTest --match-test test_generateIds -vvv
//
// Optimizer OFF
// =============
// Pre : 142325 gas
// forge test --match-contract IdRegUnopTest --match-test test_generateIds -vvv
// Post : 142325 gas (no improvement)
// forge test --match-contract IdRegDontInitDefValTest --match-test test_generateIds -vvv
//
// Conclusion
// ==========
// `for(uint256 i=0;)` costs the same as `for(uint256 i;)`
contract IdRegDontInitDefValTest is IdRegUnopTest {
function setUp() external override {
idReg = new IdRegDontInitDefVal();
}
}
28 changes: 28 additions & 0 deletions test/02-init-past-def-val/IdRegInitPastDefValTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {IdRegUnopTest} from "../IdRegUnopTest.sol";
import {IdRegInitPastDefVal} from "../../src/02-init-past-def-val/IdRegInitPastDefVal.sol";

// Optimizer ON, 10000 runs
// ========================
// Pre : 142049 gas
// forge test --optimizer-runs 10000 --match-contract IdRegUnopTest --match-test test_generateIds -vvv
// Post : 124829 gas (12% cheaper)
// forge test --optimizer-runs 10000 --match-contract IdRegInitPastDefValTest --match-test test_generateIds -vvv
//
// Optimizer OFF
// =============
// Pre : 142325 gas
// forge test --match-contract IdRegUnopTest --match-test test_generateIds -vvv
// Post : 125105 gas (12% cheaper)
// forge test --match-contract IdRegInitPastDefValTest --match-test test_generateIds -vvv
//
// Conclusion
// ==========
// Initializing values past their default is cheaper
contract IdRegInitPastDefValTest is IdRegUnopTest {
function setUp() external override {
idReg = new IdRegInitPastDefVal();
}
}
28 changes: 28 additions & 0 deletions test/03-calldata-array-input/IdRegCalldataArrayInputTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {IdRegUnopTest} from "../IdRegUnopTest.sol";
import {IdRegCalldataArrayInput} from "../../src/03-calldata-array-input/IdRegCalldataArrayInput.sol";

// Optimizer ON, 10000 runs
// ========================
// Pre : 124829 gas
// forge test --optimizer-runs 10000 --match-contract IdRegInitPastDefValTest --match-test test_generateIds -vvv
// Post : 124554 gas (0.22% cheaper)
// forge test --optimizer-runs 10000 --match-contract IdRegCalldataArrayInputTest --match-test test_generateIds -vvv
//
// Optimizer OFF
// =============
// Pre : 125105 gas
// forge test --match-contract IdRegInitPastDefValTest --match-test test_generateIds -vvv
// Post : 124827 gas (0.22% cheaper)
// forge test --match-contract IdRegCalldataArrayInputTest --match-test test_generateIds -vvv
//
// Conclusion
// ==========
// Using `calldata` for array inputs is cheaper than `memory`
contract IdRegCalldataArrayInputTest is IdRegUnopTest {
function setUp() external override {
idReg = new IdRegCalldataArrayInput();
}
}
Loading

0 comments on commit 276de92

Please sign in to comment.