Compare commits
2 Commits
ebd23d9829
...
cb7c52f655
| Author | SHA1 | Date | |
|---|---|---|---|
| cb7c52f655 | |||
| f4801edf73 |
@ -2,6 +2,7 @@
|
|||||||
src = "src"
|
src = "src"
|
||||||
out = "out"
|
out = "out"
|
||||||
libs = ["lib", "dependencies"]
|
libs = ["lib", "dependencies"]
|
||||||
|
gas_limit = "18446744073709551615"
|
||||||
|
|
||||||
optimizer = true
|
optimizer = true
|
||||||
optimizer_runs = 10000
|
optimizer_runs = 10000
|
||||||
@ -18,6 +19,7 @@ gas_reports = [
|
|||||||
"GhostBondingCalculator",
|
"GhostBondingCalculator",
|
||||||
"GhostTreasury",
|
"GhostTreasury",
|
||||||
"GhostGovernorExposed",
|
"GhostGovernorExposed",
|
||||||
|
"GatekeeperVerification",
|
||||||
]
|
]
|
||||||
remappings = [
|
remappings = [
|
||||||
"@openzeppelin-contracts/=dependencies/@openzeppelin-contracts-5.0.2/",
|
"@openzeppelin-contracts/=dependencies/@openzeppelin-contracts-5.0.2/",
|
||||||
|
|||||||
@ -1,20 +1,36 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
pragma solidity ^0.8.20;
|
pragma solidity ^0.8.20;
|
||||||
|
|
||||||
|
import {Hashes} from "./libraries/Hashes.sol";
|
||||||
import {IGatekeeper} from "./interfaces/IGatekeeper.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 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 existentialDeposit;
|
||||||
uint256 public override ghostedSupply;
|
uint256 public override ghostedSupply;
|
||||||
|
uint256 public currentSession;
|
||||||
address public immutable staking; // forge-lint: disable-line(screaming-snake-case-immutable)
|
address public immutable staking; // forge-lint: disable-line(screaming-snake-case-immutable)
|
||||||
|
|
||||||
GatekeeperMetadata private _metadata;
|
GatekeeperMetadata private _metadata;
|
||||||
uint256 private _aggregatedPublicKey;
|
uint256 private _aggregatedPublicKey;
|
||||||
mapping(uint256 => mapping(uint256 => bool)) private _executedTransaction;
|
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(
|
constructor(
|
||||||
address _staking,
|
address _staking,
|
||||||
uint256 _ghostedSupply,
|
uint256 _ghostedSupply,
|
||||||
@ -37,12 +53,106 @@ contract Gatekeeper is IGatekeeper, IGatekeeperMetadata {
|
|||||||
return _metadata;
|
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 (msg.sender != staking) revert NotStaking();
|
||||||
if (amount < existentialDeposit) revert NonExistentAmount();
|
if (amount < existentialDeposit) revert NonExistentAmount();
|
||||||
|
|
||||||
ghostedSupply += amount;
|
ghostedSupply += amount;
|
||||||
_metadata.amountIn += uint104(amount); // forge-lint: disable-line(unsafe-typecast)
|
_metadata.amountIn += uint104(amount); // forge-lint: disable-line(unsafe-typecast)
|
||||||
|
|
||||||
emit Ghosted(receiver, amount);
|
emit Ghosted(receiver, amount);
|
||||||
|
|
||||||
|
return _insertTreeNode(receiver, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
function materialize(
|
function materialize(
|
||||||
@ -88,6 +198,7 @@ contract Gatekeeper is IGatekeeper, IGatekeeperMetadata {
|
|||||||
|
|
||||||
if (_incorrectSignature(rx, s, message)) revert WrongSignature();
|
if (_incorrectSignature(rx, s, message)) revert WrongSignature();
|
||||||
_aggregatedPublicKey = aggregatedPublicKey;
|
_aggregatedPublicKey = aggregatedPublicKey;
|
||||||
|
|
||||||
emit Rotated(aggregatedPublicKey);
|
emit Rotated(aggregatedPublicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,4 +216,34 @@ contract Gatekeeper is IGatekeeper, IGatekeeperMetadata {
|
|||||||
// always bad signature for now
|
// always bad signature for now
|
||||||
return true;
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ interface IGatekeeper {
|
|||||||
|
|
||||||
function ghostedSupply() external view returns (uint256);
|
function ghostedSupply() external view returns (uint256);
|
||||||
function existentialDeposit() 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 materialize(address receiver, uint256 amount, uint256 rx, uint256 s) external;
|
||||||
function rotate(uint256 aggregatedPublicKey, uint256 rx, uint256 s) external;
|
function rotate(uint256 aggregatedPublicKey, uint256 rx, uint256 s) external;
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/interfaces/IGatekeeperWeaver.sol
Normal file
8
src/interfaces/IGatekeeperWeaver.sol
Normal file
@ -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);
|
||||||
|
}
|
||||||
601
src/libraries/Checkpoints.sol
Normal file
601
src/libraries/Checkpoints.sol
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/libraries/Hashes.sol
Normal file
19
src/libraries/Hashes.sol
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/libraries/Math.sol
Normal file
66
src/libraries/Math.sol
Normal file
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
212
test/gatekeeper/GatekeeperWeaver.t.sol
Normal file
212
test/gatekeeper/GatekeeperWeaver.t.sol
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
pragma solidity 0.8.20;
|
||||||
|
|
||||||
|
import {Test} from "forge-std/Test.sol";
|
||||||
|
|
||||||
|
import {Gatekeeper} from "../../src/Gatekeeper.sol";
|
||||||
|
import {Hashes} from "../../src/libraries/Hashes.sol";
|
||||||
|
import {Checkpoints} from "../../src/libraries/Checkpoints.sol";
|
||||||
|
|
||||||
|
contract GatekeeperVerification is Gatekeeper {
|
||||||
|
using Checkpoints for Checkpoints.Trace256;
|
||||||
|
using Checkpoints for Checkpoints.Trace160;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
address staking,
|
||||||
|
uint256 ghostedSupply,
|
||||||
|
uint256 existential,
|
||||||
|
uint48 deployedAt,
|
||||||
|
uint104 amountIn,
|
||||||
|
uint104 amountOut
|
||||||
|
) Gatekeeper(
|
||||||
|
staking,
|
||||||
|
ghostedSupply,
|
||||||
|
existential,
|
||||||
|
deployedAt,
|
||||||
|
amountIn,
|
||||||
|
amountOut
|
||||||
|
) {}
|
||||||
|
|
||||||
|
function filledEntries(uint256 session) public view returns (uint256) {
|
||||||
|
return _filledEntries[session];
|
||||||
|
}
|
||||||
|
|
||||||
|
function slotLengths(uint256 session, uint256 globalIndex) public view returns (uint160) {
|
||||||
|
uint256 slotIndex = globalIndex % SLOTS;
|
||||||
|
return _slotLenghts[session][slotIndex].latest();
|
||||||
|
}
|
||||||
|
|
||||||
|
function slotValues(uint256 session, uint256 globalIndex) public view returns (bytes32) {
|
||||||
|
uint256 slotIndex = globalIndex % SLOTS;
|
||||||
|
uint256 valueIndex = globalIndex / SLOTS;
|
||||||
|
return _slotValues[session][slotIndex][valueIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
function treeNodesLatest(uint256 session, uint256 globalIndex) public view returns (bytes32) {
|
||||||
|
uint256 slotIndex = globalIndex % SLOTS;
|
||||||
|
return bytes32(_treeNodes[session][slotIndex].latest());
|
||||||
|
}
|
||||||
|
|
||||||
|
function computePreimage(uint256 globalIndex, uint256 a, bytes32 r) public pure returns (bytes32) {
|
||||||
|
uint256 valueIndex = globalIndex / SLOTS;
|
||||||
|
return _computeArgumentsHash(valueIndex, a, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testVerify(
|
||||||
|
bytes32[] calldata proof,
|
||||||
|
bytes32[] calldata values,
|
||||||
|
uint256 globalIndex,
|
||||||
|
uint256 amount,
|
||||||
|
bytes32 who
|
||||||
|
) public pure returns (bytes32) {
|
||||||
|
if (proof.length != DEPTH) { return bytes32(0); }
|
||||||
|
if (values.length == 0) { return bytes32(0); }
|
||||||
|
|
||||||
|
uint256 slotIndex = globalIndex % SLOTS;
|
||||||
|
uint256 valueIndex = globalIndex / SLOTS;
|
||||||
|
if (valueIndex >= values.length) { return bytes32(0); }
|
||||||
|
|
||||||
|
bytes32 computedHash = _computeArgumentsHash(valueIndex, amount, who);
|
||||||
|
if (computedHash != values[valueIndex]) { return bytes32(0); }
|
||||||
|
computedHash = bytes32(0);
|
||||||
|
|
||||||
|
uint256 i;
|
||||||
|
for (; i < values.length;) {
|
||||||
|
bytes32 currHash = Hashes.efficientKeccak256(values[i]);
|
||||||
|
computedHash = Hashes.efficientKeccak256(computedHash, currHash);
|
||||||
|
unchecked { ++i; }
|
||||||
|
}
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
for (; i < proof.length; ) {
|
||||||
|
if (slotIndex % 2 == 0) {
|
||||||
|
computedHash = Hashes.efficientKeccak256(computedHash, proof[i]);
|
||||||
|
} else {
|
||||||
|
computedHash = Hashes.efficientKeccak256(proof[i], computedHash);
|
||||||
|
}
|
||||||
|
slotIndex >>= 1;
|
||||||
|
unchecked { ++i; }
|
||||||
|
}
|
||||||
|
|
||||||
|
return computedHash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract GatekeeperWeaverTest is Test {
|
||||||
|
address constant ALICE = 0x0000000000000000000000000000000000000001;
|
||||||
|
address constant BOB = 0x0000000000000000000000000000000000000002;
|
||||||
|
uint256 constant EXISTENTIAL = 1337;
|
||||||
|
uint256 constant AMOUNT = 1 * 1e7;
|
||||||
|
|
||||||
|
GatekeeperVerification gatekeeper;
|
||||||
|
|
||||||
|
function setUp() public {
|
||||||
|
gatekeeper = new GatekeeperVerification(ALICE, 0, EXISTENTIAL, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_insertationWorksAsExpected() public {
|
||||||
|
uint256 currentSession = gatekeeper.currentSession();
|
||||||
|
uint256 maxCount = gatekeeper.ENTRIES();
|
||||||
|
uint256 globalIndex;
|
||||||
|
|
||||||
|
(bytes32[] memory whos, uint256[] memory amounts) = _prepareArrays(maxCount);
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < maxCount; i++) {
|
||||||
|
if (i % 5 == 0) { vm.roll(block.number + 1); }
|
||||||
|
globalIndex = _insertWithAssert(currentSession, amounts[i], whos[i]);
|
||||||
|
assertTrue(_verifyProof(globalIndex, currentSession, block.number, amounts[i], whos[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
globalIndex = _insertWithAssert(currentSession, amounts[69], whos[69]);
|
||||||
|
uint256 newSession = gatekeeper.currentSession();
|
||||||
|
|
||||||
|
assertEq(currentSession + 1, newSession);
|
||||||
|
vm.roll(block.number + 1337);
|
||||||
|
|
||||||
|
assertTrue(_verifyProof(globalIndex, newSession, block.number, amounts[69], whos[69]));
|
||||||
|
assertTrue(_verifyProof(0, currentSession, block.number, amounts[0], whos[0]));
|
||||||
|
assertTrue(_verifyProof(69, currentSession, block.number, amounts[69], whos[69]));
|
||||||
|
assertTrue(_verifyProof(420, currentSession, block.number, amounts[420], whos[420]));
|
||||||
|
assertTrue(_verifyProof(1337, currentSession, block.number, amounts[1337], whos[1337]));
|
||||||
|
assertTrue(_verifyProof(2047, currentSession, block.number, amounts[2047], whos[2047]));
|
||||||
|
|
||||||
|
assertTrue(_verifyProof(0, currentSession, 100, amounts[0], whos[0]));
|
||||||
|
assertTrue(_verifyProof(69, currentSession, 100, amounts[69], whos[69]));
|
||||||
|
assertTrue(_verifyProof(420, currentSession, 100, amounts[420], whos[420]));
|
||||||
|
|
||||||
|
assertFalse(_verifyProof(globalIndex, newSession, 100, amounts[69], whos[69]));
|
||||||
|
assertFalse(_verifyProof(1337, currentSession, 100, amounts[1337], whos[1337]));
|
||||||
|
assertFalse(_verifyProof(2047, currentSession, 100, amounts[2047], whos[2047]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function _verifyProof(
|
||||||
|
uint256 globalIndex,
|
||||||
|
uint256 session,
|
||||||
|
uint256 atBlock,
|
||||||
|
uint256 amount,
|
||||||
|
bytes32 who
|
||||||
|
) private view returns (bool) {
|
||||||
|
bytes32[] memory proof = gatekeeper.getProof(globalIndex, session, atBlock);
|
||||||
|
bytes32[] memory values = gatekeeper.getSlotValues(globalIndex, session, atBlock);
|
||||||
|
|
||||||
|
bytes32 root1 = gatekeeper.testVerify(proof, values, globalIndex, amount, who);
|
||||||
|
(bytes32 root2, uint256 entries) = gatekeeper.getRoot(session, atBlock);
|
||||||
|
assertTrue(entries <= gatekeeper.ENTRIES());
|
||||||
|
|
||||||
|
return root1 == root2;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _insertWithAssert(
|
||||||
|
uint256 session,
|
||||||
|
uint256 amount,
|
||||||
|
bytes32 who
|
||||||
|
) private returns (uint256 globalIndex) {
|
||||||
|
uint256 targetSlot = gatekeeper.filledEntries(session);
|
||||||
|
bytes32 previousHash = gatekeeper.treeNodesLatest(session, targetSlot);
|
||||||
|
uint256 prevEntries = gatekeeper.filledEntries(session);
|
||||||
|
uint160 prevLength = gatekeeper.slotLengths(session, targetSlot);
|
||||||
|
|
||||||
|
vm.prank(ALICE);
|
||||||
|
globalIndex = gatekeeper.ghost(who, amount);
|
||||||
|
|
||||||
|
if (session + 1 == gatekeeper.currentSession()) {
|
||||||
|
assertEq(prevLength, gatekeeper.DEPTH());
|
||||||
|
assertEq(gatekeeper.slotLengths(session + 1, 0), 1);
|
||||||
|
assertEq(prevEntries, gatekeeper.ENTRIES());
|
||||||
|
assertEq(gatekeeper.filledEntries(session + 1), 1);
|
||||||
|
|
||||||
|
session += 1;
|
||||||
|
previousHash = bytes32(0);
|
||||||
|
prevLength = gatekeeper.slotLengths(session, 0);
|
||||||
|
} else {
|
||||||
|
assertEq(prevLength + 1, gatekeeper.slotLengths(session, targetSlot));
|
||||||
|
assertEq(prevEntries + 1, gatekeeper.filledEntries(session));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
bytes32 preimage1 = gatekeeper.slotValues(session, globalIndex);
|
||||||
|
bytes32 preimage2 = gatekeeper.computePreimage(globalIndex, amount, who);
|
||||||
|
assertEq(preimage1, preimage2);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
bytes32 nodeHash1 = gatekeeper.treeNodesLatest(session, globalIndex);
|
||||||
|
bytes32 preimageHash = Hashes.efficientKeccak256(
|
||||||
|
gatekeeper.computePreimage(globalIndex, amount, who)
|
||||||
|
);
|
||||||
|
bytes32 nodeHash2 = Hashes.efficientKeccak256(previousHash, preimageHash);
|
||||||
|
assertEq(nodeHash1, nodeHash2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _prepareArrays(uint256 count) private pure returns (bytes32[] memory, uint256[] memory) {
|
||||||
|
bytes32[] memory whos = new bytes32[](count);
|
||||||
|
uint256[] memory amounts = new uint256[](count);
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < count; i++) {
|
||||||
|
whos[i] = keccak256(abi.encodePacked("user", i));
|
||||||
|
amounts[i] = EXISTENTIAL + 1 + i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (whos, amounts);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user