diff --git a/contracts/utils/Arrays.sol b/contracts/utils/Arrays.sol index 0783614cd3e..3cfa44f5ffa 100644 --- a/contracts/utils/Arrays.sol +++ b/contracts/utils/Arrays.sol @@ -31,7 +31,7 @@ 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; @@ -39,10 +39,21 @@ library Arrays { } // 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)) + } + } } diff --git a/contracts/utils/Checkpoints.sol b/contracts/utils/Checkpoints.sol index c0f6c82b3c3..495be349c3b 100644 --- a/contracts/utils/Checkpoints.sol +++ b/contracts/utils/Checkpoints.sol @@ -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( @@ -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})); } @@ -56,20 +56,20 @@ 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; } @@ -77,7 +77,7 @@ library Checkpoints { 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( @@ -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; @@ -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; @@ -114,6 +114,13 @@ 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; @@ -121,7 +128,7 @@ library Checkpoints { 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( @@ -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})); } @@ -154,20 +161,20 @@ 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; } @@ -175,7 +182,7 @@ library Checkpoints { 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( @@ -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; @@ -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; @@ -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; } @@ -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)); } /** diff --git a/scripts/generate/templates/Checkpoints.js b/scripts/generate/templates/Checkpoints.js index dbe33610575..46404ddeaaa 100644 --- a/scripts/generate/templates/Checkpoints.js +++ b/scripts/generate/templates/Checkpoints.js @@ -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( @@ -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})); } @@ -107,20 +107,20 @@ 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; } @@ -128,7 +128,7 @@ function upperLookupRecent(Checkpoint${length}[] storage self, uint${256 - lengt 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( @@ -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; @@ -156,7 +156,7 @@ 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; @@ -164,6 +164,13 @@ function _lowerDichotomicLookup( } 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 */