Skip to content

Commit

Permalink
Use unsafeAccess to save gas
Browse files Browse the repository at this point in the history
  • Loading branch information
Amxx committed Jul 29, 2022
1 parent 2a6adb3 commit 28f6e6f
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 30 deletions.
15 changes: 13 additions & 2 deletions contracts/utils/Arrays.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,29 @@ library Arrays {

// Note that mid will always be strictly less than high (i.e. it will be a valid array index)
// because Math.average rounds down (it does integer division with truncation).
if (array[mid] > element) {
if (unsafeAccess(array, mid) > element) {
high = mid;
} else {
low = mid + 1;
}
}

// At this point `low` is the exclusive upper bound. We will return the inclusive upper bound.
if (low > 0 && array[low - 1] == element) {
if (low > 0 && unsafeAccess(array, low - 1) == element) {
return low - 1;
} else {
return low;
}
}

/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
* @notice WARNING: only use if you are certain pos is lower then the array length.
*/
function unsafeAccess(uint256[] storage arr, uint256 pos) internal view returns (uint256 result) {
assembly {
mstore(0, arr.slot)
result := sload(add(keccak256(0, 0x20), pos))
}
}
}
52 changes: 33 additions & 19 deletions contracts/utils/Checkpoints.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ library Checkpoints {

function latest(Checkpoint224[] storage self) internal view returns (uint224) {
uint256 pos = self.length;
return pos == 0 ? 0 : self[pos - 1]._value;
return pos == 0 ? 0 : _unsafeAccess(self, pos - 1)._value;
}

function push(
Expand All @@ -35,14 +35,14 @@ library Checkpoints {

if (pos > 0) {
// Use of memory is important here.
Checkpoint224 memory last = self[pos - 1];
Checkpoint224 memory last = _unsafeAccess(self, pos - 1);

// Checkpoints keys must be increassing.
require(last._key <= key, "Checkpoint: invalid key");

// Update or push new checkpoint
if (last._key == key) {
self[pos - 1]._value = value;
_unsafeAccess(self, pos - 1)._value = value;
} else {
self.push(Checkpoint224({_key: key, _value: value}));
}
Expand All @@ -56,28 +56,28 @@ library Checkpoints {
function lowerLookup(Checkpoint224[] storage self, uint32 key) internal view returns (uint224) {
uint256 length = self.length;
uint256 pos = _lowerDichotomicLookup(self, key, 0, length);
return pos == length ? 0 : self[pos]._value;
return pos == length ? 0 : _unsafeAccess(self, pos)._value;
}

function upperLookup(Checkpoint224[] storage self, uint32 key) internal view returns (uint224) {
uint256 length = self.length;
uint256 pos = _upperDichotomicLookup(self, key, 0, length);
return pos == 0 ? 0 : self[pos - 1]._value;
return pos == 0 ? 0 : _unsafeAccess(self, pos - 1)._value;
}

function upperLookupRecent(Checkpoint224[] storage self, uint32 key) internal view returns (uint224) {
uint256 length = self.length;
uint256 offset = 1;

while (offset <= length && self[length - offset]._key > key) {
while (offset <= length && _unsafeAccess(self, length - offset)._key > key) {
offset <<= 1;
}

uint256 low = offset < length ? length - offset : 0;
uint256 high = length - (offset >> 1);
uint256 pos = _upperDichotomicLookup(self, key, low, high);

return pos == 0 ? 0 : self[pos - 1]._value;
return pos == 0 ? 0 : _unsafeAccess(self, pos - 1)._value;
}

function _upperDichotomicLookup(
Expand All @@ -88,7 +88,7 @@ library Checkpoints {
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (self[mid]._key > key) {
if (_unsafeAccess(self, mid)._key > key) {
high = mid;
} else {
low = mid + 1;
Expand All @@ -105,7 +105,7 @@ library Checkpoints {
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (self[mid]._key < key) {
if (_unsafeAccess(self, mid)._key < key) {
low = mid + 1;
} else {
high = mid;
Expand All @@ -114,14 +114,21 @@ library Checkpoints {
return high;
}

function _unsafeAccess(Checkpoint224[] storage self, uint256 pos) private view returns (Checkpoint224 storage result) {
assembly {
mstore(0, self.slot)
result.slot := add(keccak256(0, 0x20), pos)
}
}

struct Checkpoint160 {
uint96 _key;
uint160 _value;
}

function latest(Checkpoint160[] storage self) internal view returns (uint160) {
uint256 pos = self.length;
return pos == 0 ? 0 : self[pos - 1]._value;
return pos == 0 ? 0 : _unsafeAccess(self, pos - 1)._value;
}

function push(
Expand All @@ -133,14 +140,14 @@ library Checkpoints {

if (pos > 0) {
// Use of memory is important here.
Checkpoint160 memory last = self[pos - 1];
Checkpoint160 memory last = _unsafeAccess(self, pos - 1);

// Checkpoints keys must be increassing.
require(last._key <= key, "Checkpoint: invalid key");

// Update or push new checkpoint
if (last._key == key) {
self[pos - 1]._value = value;
_unsafeAccess(self, pos - 1)._value = value;
} else {
self.push(Checkpoint160({_key: key, _value: value}));
}
Expand All @@ -154,28 +161,28 @@ library Checkpoints {
function lowerLookup(Checkpoint160[] storage self, uint96 key) internal view returns (uint160) {
uint256 length = self.length;
uint256 pos = _lowerDichotomicLookup(self, key, 0, length);
return pos == length ? 0 : self[pos]._value;
return pos == length ? 0 : _unsafeAccess(self, pos)._value;
}

function upperLookup(Checkpoint160[] storage self, uint96 key) internal view returns (uint160) {
uint256 length = self.length;
uint256 pos = _upperDichotomicLookup(self, key, 0, length);
return pos == 0 ? 0 : self[pos - 1]._value;
return pos == 0 ? 0 : _unsafeAccess(self, pos - 1)._value;
}

function upperLookupRecent(Checkpoint160[] storage self, uint96 key) internal view returns (uint224) {
uint256 length = self.length;
uint256 offset = 1;

while (offset <= length && self[length - offset]._key > key) {
while (offset <= length && _unsafeAccess(self, length - offset)._key > key) {
offset <<= 1;
}

uint256 low = offset < length ? length - offset : 0;
uint256 high = length - (offset >> 1);
uint256 pos = _upperDichotomicLookup(self, key, low, high);

return pos == 0 ? 0 : self[pos - 1]._value;
return pos == 0 ? 0 : _unsafeAccess(self, pos - 1)._value;
}

function _upperDichotomicLookup(
Expand All @@ -186,7 +193,7 @@ library Checkpoints {
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (self[mid]._key > key) {
if (_unsafeAccess(self, mid)._key > key) {
high = mid;
} else {
low = mid + 1;
Expand All @@ -203,7 +210,7 @@ library Checkpoints {
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (self[mid]._key < key) {
if (_unsafeAccess(self, mid)._key < key) {
low = mid + 1;
} else {
high = mid;
Expand All @@ -212,6 +219,13 @@ library Checkpoints {
return high;
}

function _unsafeAccess(Checkpoint160[] storage self, uint256 pos) private view returns (Checkpoint160 storage result) {
assembly {
mstore(0, self.slot)
result.slot := add(keccak256(0, 0x20), pos)
}
}

struct History {
Checkpoint224[] _checkpoints;
}
Expand All @@ -230,7 +244,7 @@ library Checkpoints {
function getAtBlock(History storage self, uint256 blockNumber) internal view returns (uint256) {
require(blockNumber < block.number, "Checkpoints: block not yet mined");

return upperLookup(self._checkpoints, SafeCast.toUint32(blockNumber));
return upperLookupRecent(self._checkpoints, SafeCast.toUint32(blockNumber));
}

/**
Expand Down
25 changes: 16 additions & 9 deletions scripts/generate/templates/Checkpoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ struct Checkpoint${length} {
function latest(Checkpoint${length}[] storage self) internal view returns (uint${length}) {
uint256 pos = self.length;
return pos == 0 ? 0 : self[pos - 1]._value;
return pos == 0 ? 0 : _unsafeAccess(self, pos - 1)._value;
}
function push(
Expand All @@ -86,14 +86,14 @@ function push(
if (pos > 0) {
// Use of memory is important here.
Checkpoint${length} memory last = self[pos - 1];
Checkpoint${length} memory last = _unsafeAccess(self, pos - 1);
// Checkpoints keys must be increassing.
require(last._key <= key, "Checkpoint: invalid key");
// Update or push new checkpoint
if (last._key == key) {
self[pos - 1]._value = value;
_unsafeAccess(self, pos - 1)._value = value;
} else {
self.push(Checkpoint${length}({_key: key, _value: value}));
}
Expand All @@ -107,28 +107,28 @@ function push(
function lowerLookup(Checkpoint${length}[] storage self, uint${256 - length} key) internal view returns (uint${length}) {
uint256 length = self.length;
uint256 pos = _lowerDichotomicLookup(self, key, 0, length);
return pos == length ? 0 : self[pos]._value;
return pos == length ? 0 : _unsafeAccess(self, pos)._value;
}
function upperLookup(Checkpoint${length}[] storage self, uint${256 - length} key) internal view returns (uint${length}) {
uint256 length = self.length;
uint256 pos = _upperDichotomicLookup(self, key, 0, length);
return pos == 0 ? 0 : self[pos - 1]._value;
return pos == 0 ? 0 : _unsafeAccess(self, pos - 1)._value;
}
function upperLookupRecent(Checkpoint${length}[] storage self, uint${256 - length} key) internal view returns (uint224) {
uint256 length = self.length;
uint256 offset = 1;
while (offset <= length && self[length - offset]._key > key) {
while (offset <= length && _unsafeAccess(self, length - offset)._key > key) {
offset <<= 1;
}
uint256 low = offset < length ? length - offset : 0;
uint256 high = length - (offset >> 1);
uint256 pos = _upperDichotomicLookup(self, key, low, high);
return pos == 0 ? 0 : self[pos - 1]._value;
return pos == 0 ? 0 : _unsafeAccess(self, pos - 1)._value;
}
function _upperDichotomicLookup(
Expand All @@ -139,7 +139,7 @@ function _upperDichotomicLookup(
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (self[mid]._key > key) {
if (_unsafeAccess(self, mid)._key > key) {
high = mid;
} else {
low = mid + 1;
Expand All @@ -156,14 +156,21 @@ function _lowerDichotomicLookup(
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (self[mid]._key < key) {
if (_unsafeAccess(self, mid)._key < key) {
low = mid + 1;
} else {
high = mid;
}
}
return high;
}
function _unsafeAccess(Checkpoint${length}[] storage self, uint256 pos) private view returns (Checkpoint${length} storage result) {
assembly {
mstore(0, self.slot)
result.slot := add(keccak256(0, 0x20), pos)
}
}
`;
/* eslint-enable max-len */

Expand Down

0 comments on commit 28f6e6f

Please sign in to comment.