From f4801edf7309c76f1d0db0a8799734990546a879 Mon Sep 17 00:00:00 2001 From: Uncle Stretch Date: Thu, 23 Jul 2026 18:22:57 +0300 Subject: [PATCH] implementation of weaver idea for bridge in Signed-off-by: Uncle Stretch --- src/Gatekeeper.sol | 147 ++++++- src/interfaces/IGatekeeper.sol | 2 +- src/interfaces/IGatekeeperWeaver.sol | 8 + src/libraries/Checkpoints.sol | 601 +++++++++++++++++++++++++++ src/libraries/Hashes.sol | 19 + src/libraries/Math.sol | 66 +++ 6 files changed, 839 insertions(+), 4 deletions(-) create mode 100644 src/interfaces/IGatekeeperWeaver.sol create mode 100644 src/libraries/Checkpoints.sol create mode 100644 src/libraries/Hashes.sol create mode 100644 src/libraries/Math.sol diff --git a/src/Gatekeeper.sol b/src/Gatekeeper.sol index c7d5fa0..7997966 100644 --- a/src/Gatekeeper.sol +++ b/src/Gatekeeper.sol @@ -1,20 +1,36 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; +import {Hashes} from "./libraries/Hashes.sol"; import {IGatekeeper} from "./interfaces/IGatekeeper.sol"; -import {IGatekeeperMetadata} from "./interfaces/IGatekeeperMetadata.sol"; +import {IGatekeeperWeaver} from "./interfaces/IGatekeeperWeaver.sol"; + +import {IGatekeeperMetadata} from "./interfaces/IGatekeeperMetadata.sol"; +import {Checkpoints} from "./libraries/Checkpoints.sol"; + +contract Gatekeeper is IGatekeeper, IGatekeeperMetadata, IGatekeeperWeaver { + using Checkpoints for Checkpoints.Trace256; + using Checkpoints for Checkpoints.Trace160; -contract Gatekeeper is IGatekeeper, IGatekeeperMetadata { uint256 public constant DIVISOR = 1e6; + uint256 public constant DEPTH = 8; + uint256 public constant SLOTS = 2 ** DEPTH; + uint256 public constant ENTRIES = DEPTH * SLOTS; uint256 public override existentialDeposit; uint256 public override ghostedSupply; + uint256 public currentSession; address public immutable staking; // forge-lint: disable-line(screaming-snake-case-immutable) GatekeeperMetadata private _metadata; uint256 private _aggregatedPublicKey; mapping(uint256 => mapping(uint256 => bool)) private _executedTransaction; + mapping(uint256 => mapping(uint256 => Checkpoints.Trace256)) internal _treeNodes; + mapping(uint256 => mapping(uint256 => Checkpoints.Trace160)) internal _slotLenghts; + mapping(uint256 => mapping(uint256 => bytes32[])) internal _slotValues; + mapping(uint256 => uint256) internal _filledEntries; + constructor( address _staking, uint256 _ghostedSupply, @@ -37,12 +53,106 @@ contract Gatekeeper is IGatekeeper, IGatekeeperMetadata { return _metadata; } - function ghost(bytes32 receiver, uint256 amount) external override { + function getSlotValues( + uint256 globalIndex, + uint256 session, + uint256 atBlock + ) external view returns (bytes32[] memory) { + uint256 slotIndex = globalIndex % SLOTS; + // forge-lint: disable-next-line(unsafe-typecast) + uint256 length = _slotLenghts[session][slotIndex].upperLookupRecent(uint96(atBlock)); + bytes32[] memory values = new bytes32[](length); + + uint256 i; + for (; i < length; ) { + values[i] = _slotValues[session][slotIndex][i]; + unchecked { ++i; } + } + + return values; + } + + function getRoot(uint256 session, uint256 atBlock) public view returns (bytes32, uint256) { + uint256 currentLevelCount = SLOTS >> 1; + bytes32[] memory currentLevel = new bytes32[](currentLevelCount); + + uint256 i; + for (; i < currentLevelCount;) { + uint256 j = i << 1; + bytes32 left = bytes32(_treeNodes[session][j].upperLookupRecent(atBlock)); + bytes32 right = bytes32(_treeNodes[session][j | 1].upperLookupRecent(atBlock)); + + currentLevel[i] = Hashes.efficientKeccak256(left, right); + unchecked { ++i; } + } + + while (currentLevelCount > 1) { + currentLevelCount >>= 1; + i = 0; + for (; i < currentLevelCount;) { + uint256 j = i << 1; + bytes32 left = currentLevel[j]; + bytes32 right = currentLevel[j | 1]; + + currentLevel[i] = Hashes.efficientKeccak256(left, right); + unchecked { ++i; } + } + } + + return (currentLevel[0], _filledEntries[session]); + } + + function getProof( + uint256 globalIndex, + uint256 session, + uint256 atBlock + ) external view returns (bytes32[] memory) { + uint256 currentLevelCount = SLOTS; + uint256 currentIndex = globalIndex % SLOTS; + + bytes32[] memory proof = new bytes32[](DEPTH); + bytes32[] memory currentLevel = new bytes32[](currentLevelCount); + uint256 i; + + for (; i < currentLevelCount;) { + currentLevel[i] = bytes32(_treeNodes[session][i].upperLookupRecent(atBlock)); + unchecked { ++i; } + } + + uint256 proofIndex; + while (currentLevelCount > 1) { + currentLevelCount >>= 1; + uint256 siblingIndex = currentIndex ^ 1; + + proof[proofIndex] = currentLevel[siblingIndex]; + i = 0; + + for (; i < currentLevelCount;) { + uint256 j = i << 1; + bytes32 left = currentLevel[j]; + bytes32 right = currentLevel[j | 1]; + + currentLevel[i] = Hashes.efficientKeccak256(left, right); + unchecked { ++i; } + } + + currentIndex >>= 1; + unchecked { ++proofIndex; } + } + + return proof; + } + + function ghost(bytes32 receiver, uint256 amount) external override returns (uint256) { if (msg.sender != staking) revert NotStaking(); if (amount < existentialDeposit) revert NonExistentAmount(); + ghostedSupply += amount; _metadata.amountIn += uint104(amount); // forge-lint: disable-line(unsafe-typecast) + emit Ghosted(receiver, amount); + + return _insertTreeNode(receiver, amount); } function materialize( @@ -88,6 +198,7 @@ contract Gatekeeper is IGatekeeper, IGatekeeperMetadata { if (_incorrectSignature(rx, s, message)) revert WrongSignature(); _aggregatedPublicKey = aggregatedPublicKey; + emit Rotated(aggregatedPublicKey); } @@ -105,4 +216,34 @@ contract Gatekeeper is IGatekeeper, IGatekeeperMetadata { // always bad signature for now return true; } + + function _insertTreeNode(bytes32 who, uint256 amount) private returns (uint256 globalIndex) { + uint256 session = currentSession; + globalIndex = _filledEntries[session]; + if (globalIndex >= ENTRIES) { + unchecked { ++session; } + currentSession = session; + globalIndex = 0; + } + + uint256 slotIndex = globalIndex % SLOTS; + uint256 valueIndex = globalIndex / SLOTS; + uint160 length = uint160(_slotValues[session][slotIndex].length + 1); + + bytes32 preimage = _computeArgumentsHash(valueIndex, amount, who); + bytes32 currHash = Hashes.efficientKeccak256(preimage); + bytes32 slotHash = bytes32(_treeNodes[session][slotIndex].latest()); + + slotHash = Hashes.efficientKeccak256(slotHash, currHash); + + _treeNodes[session][slotIndex].push(block.number, uint256(slotHash)); + _slotLenghts[session][slotIndex].push(uint96(block.number), length); + _slotValues[session][slotIndex].push(preimage); + + unchecked { ++_filledEntries[session]; } + } + + function _computeArgumentsHash(uint256 i, uint256 a, bytes32 r) internal pure returns (bytes32) { + return Hashes.efficientKeccak256(bytes32(i), Hashes.efficientKeccak256(bytes32(a), r)); + } } diff --git a/src/interfaces/IGatekeeper.sol b/src/interfaces/IGatekeeper.sol index fdd2a80..65849ef 100644 --- a/src/interfaces/IGatekeeper.sol +++ b/src/interfaces/IGatekeeper.sol @@ -13,7 +13,7 @@ interface IGatekeeper { function ghostedSupply() external view returns (uint256); function existentialDeposit() external view returns (uint256); - function ghost(bytes32 receiver, uint256 amount) external; + function ghost(bytes32 receiver, uint256 amount) external returns (uint256); function materialize(address receiver, uint256 amount, uint256 rx, uint256 s) external; function rotate(uint256 aggregatedPublicKey, uint256 rx, uint256 s) external; } diff --git a/src/interfaces/IGatekeeperWeaver.sol b/src/interfaces/IGatekeeperWeaver.sol new file mode 100644 index 0000000..b077e11 --- /dev/null +++ b/src/interfaces/IGatekeeperWeaver.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +interface IGatekeeperWeaver { + function getSlotValues(uint256 globalIndex, uint256 session, uint256 atBlock) external returns (bytes32[] memory); + function getProof(uint256 globalIndex, uint256 session, uint256 atBlock) external returns (bytes32[] memory); + function getRoot(uint256 session, uint256 atBlock) external returns (bytes32, uint256); +} diff --git a/src/libraries/Checkpoints.sol b/src/libraries/Checkpoints.sol new file mode 100644 index 0000000..61b532b --- /dev/null +++ b/src/libraries/Checkpoints.sol @@ -0,0 +1,601 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Math} from "./Math.sol"; + +library Checkpoints { + error CheckpointUnorderedInsertion(); + + struct Trace256 { + Checkpoint256[] _checkpoints; + } + + struct Checkpoint256 { + uint256 _key; + uint256 _value; + } + + function push( + Trace256 storage self, + uint256 key, + uint256 value + ) internal returns (uint256 oldValue, uint256 newValue) { + return _insert(self._checkpoints, key, value); + } + + function lowerLookup(Trace256 storage self, uint256 key) internal view returns (uint256) { + uint256 len = self._checkpoints.length; + uint256 index = _lowerBinaryLookup(self._checkpoints, key, 0, len); + return index == len ? 0 : _unsafeAccess(self._checkpoints, index)._value; + } + + function upperLookup(Trace256 storage self, uint256 key) internal view returns (uint256) { + uint256 len = self._checkpoints.length; + uint256 index = _upperBinaryLookup(self._checkpoints, key, 0, len); + return index == 0 ? 0 : _unsafeAccess(self._checkpoints, index - 1)._value; + } + + function upperLookupRecent(Trace256 storage self, uint256 key) internal view returns (uint256) { + uint256 len = self._checkpoints.length; + + uint256 low = 0; + uint256 high = len; + + if (len > 5) { + uint256 mid = len - Math.sqrt(len); + if (key < _unsafeAccess(self._checkpoints, mid)._key) { + high = mid; + } else { + low = mid + 1; + } + } + + uint256 index = _upperBinaryLookup(self._checkpoints, key, low, high); + + return index == 0 ? 0 : _unsafeAccess(self._checkpoints, index - 1)._value; + } + + function latest(Trace256 storage self) internal view returns (uint256) { + uint256 len = self._checkpoints.length; + return len == 0 ? 0 : _unsafeAccess(self._checkpoints, len - 1)._value; + } + + function latestCheckpoint(Trace256 storage self) internal view returns (bool exists, uint256 _key, uint256 _value) { + uint256 len = self._checkpoints.length; + if (len == 0) { + return (false, 0, 0); + } else { + Checkpoint256 storage ckpt = _unsafeAccess(self._checkpoints, len - 1); + return (true, ckpt._key, ckpt._value); + } + } + + function length(Trace256 storage self) internal view returns (uint256) { + return self._checkpoints.length; + } + + function at(Trace256 storage self, uint32 index) internal view returns (Checkpoint256 memory) { + return pos(self, index); + } + + function pos(Trace256 storage self, uint32 index) internal view returns (Checkpoint256 memory) { + return self._checkpoints[index]; + } + + function _insert( + Checkpoint256[] storage self, + uint256 key, + uint256 value + ) private returns (uint256 oldValue, uint256 newValue) { + uint256 len = self.length; + + if (len > 0) { + Checkpoint256 storage last = _unsafeAccess(self, len - 1); + uint256 lastKey = last._key; + uint256 lastValue = last._value; + + if (lastKey > key) { + revert CheckpointUnorderedInsertion(); + } + + if (lastKey == key) { + last._value = value; + } else { + self.push(Checkpoint256({_key: key, _value: value})); + } + return (lastValue, value); + } else { + self.push(Checkpoint256({_key: key, _value: value})); + return (0, value); + } + } + + function _upperBinaryLookup( + Checkpoint256[] storage self, + uint256 key, + uint256 low, + uint256 high + ) private view returns (uint256) { + while (low < high) { + uint256 mid = Math.average(low, high); + if (_unsafeAccess(self, mid)._key > key) { + high = mid; + } else { + low = mid + 1; + } + } + return high; + } + + function _lowerBinaryLookup( + Checkpoint256[] storage self, + uint256 key, + uint256 low, + uint256 high + ) private view returns (uint256) { + while (low < high) { + uint256 mid = Math.average(low, high); + if (_unsafeAccess(self, mid)._key < key) { + low = mid + 1; + } else { + high = mid; + } + } + return high; + } + + function _unsafeAccess( + Checkpoint256[] storage self, + uint256 index + ) private pure returns (Checkpoint256 storage result) { + assembly { + mstore(0x00, self.slot) + result.slot := add(keccak256(0x00, 0x20), mul(index, 2)) + } + } + + struct Trace224 { + Checkpoint224[] _checkpoints; + } + + struct Checkpoint224 { + uint32 _key; + uint224 _value; + } + + function push( + Trace224 storage self, + uint32 key, + uint224 value + ) internal returns (uint224 oldValue, uint224 newValue) { + return _insert(self._checkpoints, key, value); + } + + function lowerLookup(Trace224 storage self, uint32 key) internal view returns (uint224) { + uint256 len = self._checkpoints.length; + uint256 index = _lowerBinaryLookup(self._checkpoints, key, 0, len); + return index == len ? 0 : _unsafeAccess(self._checkpoints, index)._value; + } + + function upperLookup(Trace224 storage self, uint32 key) internal view returns (uint224) { + uint256 len = self._checkpoints.length; + uint256 index = _upperBinaryLookup(self._checkpoints, key, 0, len); + return index == 0 ? 0 : _unsafeAccess(self._checkpoints, index - 1)._value; + } + + function upperLookupRecent(Trace224 storage self, uint32 key) internal view returns (uint224) { + uint256 len = self._checkpoints.length; + + uint256 low = 0; + uint256 high = len; + + if (len > 5) { + uint256 mid = len - Math.sqrt(len); + if (key < _unsafeAccess(self._checkpoints, mid)._key) { + high = mid; + } else { + low = mid + 1; + } + } + + uint256 index = _upperBinaryLookup(self._checkpoints, key, low, high); + + return index == 0 ? 0 : _unsafeAccess(self._checkpoints, index - 1)._value; + } + + function latest(Trace224 storage self) internal view returns (uint224) { + uint256 len = self._checkpoints.length; + return len == 0 ? 0 : _unsafeAccess(self._checkpoints, len - 1)._value; + } + + function latestCheckpoint(Trace224 storage self) internal view returns (bool exists, uint32 _key, uint224 _value) { + uint256 len = self._checkpoints.length; + if (len == 0) { + return (false, 0, 0); + } else { + Checkpoint224 storage ckpt = _unsafeAccess(self._checkpoints, len - 1); + return (true, ckpt._key, ckpt._value); + } + } + + function length(Trace224 storage self) internal view returns (uint256) { + return self._checkpoints.length; + } + + function at(Trace224 storage self, uint32 index) internal view returns (Checkpoint224 memory) { + return pos(self, index); + } + + function pos(Trace224 storage self, uint32 index) internal view returns (Checkpoint224 memory) { + return self._checkpoints[index]; + } + + function _insert( + Checkpoint224[] storage self, + uint32 key, + uint224 value + ) private returns (uint224 oldValue, uint224 newValue) { + uint256 len = self.length; + + if (len > 0) { + Checkpoint224 storage last = _unsafeAccess(self, len - 1); + uint32 lastKey = last._key; + uint224 lastValue = last._value; + + if (lastKey > key) { + revert CheckpointUnorderedInsertion(); + } + + if (lastKey == key) { + last._value = value; + } else { + self.push(Checkpoint224({_key: key, _value: value})); + } + return (lastValue, value); + } else { + self.push(Checkpoint224({_key: key, _value: value})); + return (0, value); + } + } + + function _upperBinaryLookup( + Checkpoint224[] storage self, + uint32 key, + uint256 low, + uint256 high + ) private view returns (uint256) { + while (low < high) { + uint256 mid = Math.average(low, high); + if (_unsafeAccess(self, mid)._key > key) { + high = mid; + } else { + low = mid + 1; + } + } + return high; + } + + function _lowerBinaryLookup( + Checkpoint224[] storage self, + uint32 key, + uint256 low, + uint256 high + ) private view returns (uint256) { + while (low < high) { + uint256 mid = Math.average(low, high); + if (_unsafeAccess(self, mid)._key < key) { + low = mid + 1; + } else { + high = mid; + } + } + return high; + } + + function _unsafeAccess( + Checkpoint224[] storage self, + uint256 index + ) private pure returns (Checkpoint224 storage result) { + assembly { + mstore(0x00, self.slot) + result.slot := add(keccak256(0x00, 0x20), index) + } + } + + struct Trace208 { + Checkpoint208[] _checkpoints; + } + + struct Checkpoint208 { + uint48 _key; + uint208 _value; + } + + function push( + Trace208 storage self, + uint48 key, + uint208 value + ) internal returns (uint208 oldValue, uint208 newValue) { + return _insert(self._checkpoints, key, value); + } + + function lowerLookup(Trace208 storage self, uint48 key) internal view returns (uint208) { + uint256 len = self._checkpoints.length; + uint256 index = _lowerBinaryLookup(self._checkpoints, key, 0, len); + return index == len ? 0 : _unsafeAccess(self._checkpoints, index)._value; + } + + function upperLookup(Trace208 storage self, uint48 key) internal view returns (uint208) { + uint256 len = self._checkpoints.length; + uint256 index = _upperBinaryLookup(self._checkpoints, key, 0, len); + return index == 0 ? 0 : _unsafeAccess(self._checkpoints, index - 1)._value; + } + + function upperLookupRecent(Trace208 storage self, uint48 key) internal view returns (uint208) { + uint256 len = self._checkpoints.length; + + uint256 low = 0; + uint256 high = len; + + if (len > 5) { + uint256 mid = len - Math.sqrt(len); + if (key < _unsafeAccess(self._checkpoints, mid)._key) { + high = mid; + } else { + low = mid + 1; + } + } + + uint256 index = _upperBinaryLookup(self._checkpoints, key, low, high); + + return index == 0 ? 0 : _unsafeAccess(self._checkpoints, index - 1)._value; + } + + function latest(Trace208 storage self) internal view returns (uint208) { + uint256 len = self._checkpoints.length; + return len == 0 ? 0 : _unsafeAccess(self._checkpoints, len - 1)._value; + } + + function latestCheckpoint(Trace208 storage self) internal view returns (bool exists, uint48 _key, uint208 _value) { + uint256 len = self._checkpoints.length; + if (len == 0) { + return (false, 0, 0); + } else { + Checkpoint208 storage ckpt = _unsafeAccess(self._checkpoints, len - 1); + return (true, ckpt._key, ckpt._value); + } + } + + function length(Trace208 storage self) internal view returns (uint256) { + return self._checkpoints.length; + } + + function at(Trace208 storage self, uint32 index) internal view returns (Checkpoint208 memory) { + return pos(self, index); + } + + function pos(Trace208 storage self, uint32 index) internal view returns (Checkpoint208 memory) { + return self._checkpoints[index]; + } + + function _insert( + Checkpoint208[] storage self, + uint48 key, + uint208 value + ) private returns (uint208 oldValue, uint208 newValue) { + uint256 len = self.length; + + if (len > 0) { + Checkpoint208 storage last = _unsafeAccess(self, len - 1); + uint48 lastKey = last._key; + uint208 lastValue = last._value; + + if (lastKey > key) { + revert CheckpointUnorderedInsertion(); + } + + if (lastKey == key) { + last._value = value; + } else { + self.push(Checkpoint208({_key: key, _value: value})); + } + return (lastValue, value); + } else { + self.push(Checkpoint208({_key: key, _value: value})); + return (0, value); + } + } + + function _upperBinaryLookup( + Checkpoint208[] storage self, + uint48 key, + uint256 low, + uint256 high + ) private view returns (uint256) { + while (low < high) { + uint256 mid = Math.average(low, high); + if (_unsafeAccess(self, mid)._key > key) { + high = mid; + } else { + low = mid + 1; + } + } + return high; + } + + function _lowerBinaryLookup( + Checkpoint208[] storage self, + uint48 key, + uint256 low, + uint256 high + ) private view returns (uint256) { + while (low < high) { + uint256 mid = Math.average(low, high); + if (_unsafeAccess(self, mid)._key < key) { + low = mid + 1; + } else { + high = mid; + } + } + return high; + } + + function _unsafeAccess( + Checkpoint208[] storage self, + uint256 index + ) private pure returns (Checkpoint208 storage result) { + assembly { + mstore(0x00, self.slot) + result.slot := add(keccak256(0x00, 0x20), index) + } + } + + struct Trace160 { + Checkpoint160[] _checkpoints; + } + + struct Checkpoint160 { + uint96 _key; + uint160 _value; + } + + function push( + Trace160 storage self, + uint96 key, + uint160 value + ) internal returns (uint160 oldValue, uint160 newValue) { + return _insert(self._checkpoints, key, value); + } + + function lowerLookup(Trace160 storage self, uint96 key) internal view returns (uint160) { + uint256 len = self._checkpoints.length; + uint256 index = _lowerBinaryLookup(self._checkpoints, key, 0, len); + return index == len ? 0 : _unsafeAccess(self._checkpoints, index)._value; + } + + function upperLookup(Trace160 storage self, uint96 key) internal view returns (uint160) { + uint256 len = self._checkpoints.length; + uint256 index = _upperBinaryLookup(self._checkpoints, key, 0, len); + return index == 0 ? 0 : _unsafeAccess(self._checkpoints, index - 1)._value; + } + + function upperLookupRecent(Trace160 storage self, uint96 key) internal view returns (uint160) { + uint256 len = self._checkpoints.length; + + uint256 low = 0; + uint256 high = len; + + if (len > 5) { + uint256 mid = len - Math.sqrt(len); + if (key < _unsafeAccess(self._checkpoints, mid)._key) { + high = mid; + } else { + low = mid + 1; + } + } + + uint256 index = _upperBinaryLookup(self._checkpoints, key, low, high); + + return index == 0 ? 0 : _unsafeAccess(self._checkpoints, index - 1)._value; + } + + function latest(Trace160 storage self) internal view returns (uint160) { + uint256 len = self._checkpoints.length; + return len == 0 ? 0 : _unsafeAccess(self._checkpoints, len - 1)._value; + } + + function latestCheckpoint(Trace160 storage self) internal view returns (bool exists, uint96 _key, uint160 _value) { + uint256 len = self._checkpoints.length; + if (len == 0) { + return (false, 0, 0); + } else { + Checkpoint160 storage ckpt = _unsafeAccess(self._checkpoints, len - 1); + return (true, ckpt._key, ckpt._value); + } + } + + function length(Trace160 storage self) internal view returns (uint256) { + return self._checkpoints.length; + } + + function at(Trace160 storage self, uint32 index) internal view returns (Checkpoint160 memory) { + return pos(self, index); + } + + function pos(Trace160 storage self, uint32 index) internal view returns (Checkpoint160 memory) { + return self._checkpoints[index]; + } + + function _insert( + Checkpoint160[] storage self, + uint96 key, + uint160 value + ) private returns (uint160 oldValue, uint160 newValue) { + uint256 len = self.length; + + if (len > 0) { + Checkpoint160 storage last = _unsafeAccess(self, len - 1); + uint96 lastKey = last._key; + uint160 lastValue = last._value; + + if (lastKey > key) { + revert CheckpointUnorderedInsertion(); + } + + if (lastKey == key) { + last._value = value; + } else { + self.push(Checkpoint160({_key: key, _value: value})); + } + return (lastValue, value); + } else { + self.push(Checkpoint160({_key: key, _value: value})); + return (0, value); + } + } + + function _upperBinaryLookup( + Checkpoint160[] storage self, + uint96 key, + uint256 low, + uint256 high + ) private view returns (uint256) { + while (low < high) { + uint256 mid = Math.average(low, high); + if (_unsafeAccess(self, mid)._key > key) { + high = mid; + } else { + low = mid + 1; + } + } + return high; + } + + function _lowerBinaryLookup( + Checkpoint160[] storage self, + uint96 key, + uint256 low, + uint256 high + ) private view returns (uint256) { + while (low < high) { + uint256 mid = Math.average(low, high); + if (_unsafeAccess(self, mid)._key < key) { + low = mid + 1; + } else { + high = mid; + } + } + return high; + } + + function _unsafeAccess( + Checkpoint160[] storage self, + uint256 index + ) private pure returns (Checkpoint160 storage result) { + assembly { + mstore(0x00, self.slot) + result.slot := add(keccak256(0x00, 0x20), index) + } + } +} diff --git a/src/libraries/Hashes.sol b/src/libraries/Hashes.sol new file mode 100644 index 0000000..e4ebf09 --- /dev/null +++ b/src/libraries/Hashes.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +library Hashes { + function efficientKeccak256(bytes32 a) internal pure returns (bytes32 value) { + assembly ("memory-safe") { + mstore(0x00, a) + value := keccak256(0x00, 0x20) + } + } + + function efficientKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32 value) { + assembly ("memory-safe") { + mstore(0x00, a) + mstore(0x20, b) + value := keccak256(0x00, 0x40) + } + } +} diff --git a/src/libraries/Math.sol b/src/libraries/Math.sol new file mode 100644 index 0000000..d9cf5b1 --- /dev/null +++ b/src/libraries/Math.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +library Math { + function average(uint256 a, uint256 b) internal pure returns (uint256) { + unchecked { + return (a & b) + (a ^ b) / 2; + } + } + + function sqrt(uint256 a) internal pure returns (uint256) { + unchecked { + if (a <= 1) { + return a; + } + + uint256 aa = a; + uint256 xn = 1; + + if (aa >= (1 << 128)) { + aa >>= 128; + xn <<= 64; + } + if (aa >= (1 << 64)) { + aa >>= 64; + xn <<= 32; + } + if (aa >= (1 << 32)) { + aa >>= 32; + xn <<= 16; + } + if (aa >= (1 << 16)) { + aa >>= 16; + xn <<= 8; + } + if (aa >= (1 << 8)) { + aa >>= 8; + xn <<= 4; + } + if (aa >= (1 << 4)) { + aa >>= 4; + xn <<= 2; + } + if (aa >= (1 << 2)) { + xn <<= 1; + } + + xn = (3 * xn) >> 1; + + xn = (xn + a / xn) >> 1; + xn = (xn + a / xn) >> 1; + xn = (xn + a / xn) >> 1; + xn = (xn + a / xn) >> 1; + xn = (xn + a / xn) >> 1; + xn = (xn + a / xn) >> 1; + + return xn - toUint(xn > a / xn); + } + } + + function toUint(bool b) internal pure returns (uint256 u) { + assembly ("memory-safe") { + u := iszero(iszero(b)) + } + } +}