ghost-dao-contracts/test/gatekeeper/GatekeeperWeaver.t.sol
Uncle Fatso b1e9be968e
emit event during the weaver insertion
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2026-09-14 10:46:40 +03:00

287 lines
12 KiB
Solidity

pragma solidity 0.8.20;
import {Test, Vm} 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";
import {IGatekeeper} from "../../src/interfaces/IGatekeeper.sol";
import {IStorageHistory} from "../../src/interfaces/IStorageHistory.sol";
import {StorageHistory} from "../../src/types/StorageHistory.sol";
contract MockStaking {
GatekeeperWeaver public gatekeeper;
address public governor;
constructor() {
StorageHistory history = new StorageHistory();
gatekeeper = new GatekeeperWeaver(address(history));
gatekeeper.initialize(address(0));
history.setOwner(address(gatekeeper));
governor = msg.sender;
}
function ghost(bytes32 receiver, uint256 amount) external {
require(msg.sender == governor);
gatekeeper.ghost(receiver, amount);
}
function createNewGatekeeper() external {
require(msg.sender == governor);
GatekeeperWeaver newGatekeeper = new GatekeeperWeaver(address(0));
newGatekeeper.initialize(address(gatekeeper));
address storageHistory = IGatekeeper(gatekeeper).storageHistory();
IStorageHistory(storageHistory).setOwner(address(newGatekeeper));
gatekeeper = newGatekeeper;
}
}
contract GatekeeperWeaver is Gatekeeper {
using Checkpoints for Checkpoints.Trace256;
using Checkpoints for Checkpoints.Trace160;
constructor(address storageHistory) Gatekeeper(storageHistory) {}
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; }
}
computedHash = Hashes.efficientKeccak256Prefixed(computedHash);
i = 0;
for (; i < proof.length; ) {
if (slotIndex % 2 == 0) {
computedHash = Hashes.efficientKeccak256Prefixed(computedHash, proof[i]);
} else {
computedHash = Hashes.efficientKeccak256Prefixed(proof[i], computedHash);
}
slotIndex >>= 1;
unchecked { ++i; }
}
return computedHash;
}
}
contract GatekeeperWeaverTest is Test {
address constant ALICE = 0x0000000000000000000000000000000000000001;
address constant BOB = 0x0000000000000000000000000000000000000002;
uint256 constant AMOUNT = 1 * 1e7;
MockStaking staking;
GatekeeperWeaver gatekeeper;
function setUp() public {
vm.prank(ALICE);
staking = new MockStaking();
gatekeeper = staking.gatekeeper();
}
function test_insertationWorksAsExpected() public {
uint256 maxCount = gatekeeper.ENTRIES();
uint256 currentWeavingSession;
uint256 newWeavingSession;
uint256 globalIndex;
(bytes32[] memory whos, uint256[] memory amounts) = _prepareArrays(maxCount);
vm.recordLogs();
for (uint256 i = 0; i < maxCount; i++) {
if (i % 5 == 0) { vm.roll(block.number + 1); }
(globalIndex, currentWeavingSession) = _insertWithAssert(amounts[i], whos[i]);
assertTrue(_verifyProof(globalIndex, currentWeavingSession, block.number, amounts[i], whos[i]));
}
(globalIndex, newWeavingSession) = _insertWithAssert(amounts[69], whos[69]);
assertEq(currentWeavingSession + 1, newWeavingSession);
vm.roll(block.number + 1337);
assertTrue(_verifyProof(globalIndex, newWeavingSession, block.number, amounts[69], whos[69]));
assertTrue(_verifyProof(0, currentWeavingSession, block.number, amounts[0], whos[0]));
assertTrue(_verifyProof(69, currentWeavingSession, block.number, amounts[69], whos[69]));
assertTrue(_verifyProof(420, currentWeavingSession, block.number, amounts[420], whos[420]));
assertTrue(_verifyProof(1337, currentWeavingSession, block.number, amounts[1337], whos[1337]));
assertTrue(_verifyProof(2047, currentWeavingSession, block.number, amounts[2047], whos[2047]));
assertTrue(_verifyProof(0, currentWeavingSession, 100, amounts[0], whos[0]));
assertTrue(_verifyProof(69, currentWeavingSession, 100, amounts[69], whos[69]));
assertTrue(_verifyProof(420, currentWeavingSession, 100, amounts[420], whos[420]));
assertFalse(_verifyProof(globalIndex, newWeavingSession, 100, amounts[69], whos[69]));
assertFalse(_verifyProof(1337, currentWeavingSession, 100, amounts[1337], whos[1337]));
assertFalse(_verifyProof(2047, currentWeavingSession, 100, amounts[2047], whos[2047]));
vm.roll(block.number + 420);
vm.prank(ALICE);
staking.createNewGatekeeper();
gatekeeper = staking.gatekeeper();
uint256 finalSession = gatekeeper.currentWeavingSession();
for (uint256 i = 0; i < 69; i++) {
if (i % 2 == 0) { vm.roll(block.number + 1); }
vm.prank(ALICE);
staking.ghost(whos[i], amounts[i]);
}
assertTrue(_verifyProof(globalIndex, newWeavingSession, block.number, amounts[69], whos[69]));
assertTrue(_verifyProof(0, currentWeavingSession, block.number, amounts[0], whos[0]));
assertTrue(_verifyProof(69, currentWeavingSession, block.number, amounts[69], whos[69]));
assertTrue(_verifyProof(420, currentWeavingSession, block.number, amounts[420], whos[420]));
assertTrue(_verifyProof(1337, currentWeavingSession, block.number, amounts[1337], whos[1337]));
assertTrue(_verifyProof(2047, currentWeavingSession, block.number, amounts[2047], whos[2047]));
assertTrue(_verifyProof(0, currentWeavingSession, 100, amounts[0], whos[0]));
assertTrue(_verifyProof(69, currentWeavingSession, 100, amounts[69], whos[69]));
assertTrue(_verifyProof(420, currentWeavingSession, 100, amounts[420], whos[420]));
assertFalse(_verifyProof(globalIndex, newWeavingSession, 100, amounts[69], whos[69]));
assertFalse(_verifyProof(1337, currentWeavingSession, 100, amounts[1337], whos[1337]));
assertFalse(_verifyProof(2047, currentWeavingSession, 100, amounts[2047], whos[2047]));
assertTrue(_verifyProof(0, finalSession, block.number, amounts[0], whos[0]));
assertTrue(_verifyProof(34, finalSession, block.number, amounts[34], whos[34]));
assertTrue(_verifyProof(35, finalSession, block.number, amounts[35], whos[35]));
}
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 amount,
bytes32 who
) private returns (uint256 globalIndex, uint256 emittedSession) {
uint256 preSession = gatekeeper.currentWeavingSession();
uint256 targetSlot = gatekeeper.filledEntries(preSession);
bytes32 previousHash = gatekeeper.treeNodesLatest(preSession, targetSlot);
uint256 prevEntries = gatekeeper.filledEntries(preSession);
uint160 prevLength = gatekeeper.slotLengths(preSession, targetSlot);
vm.prank(ALICE);
staking.ghost(who, amount);
(globalIndex, emittedSession) = _getInsertedDataFromLogs();
if (preSession != emittedSession) {
assertEq(preSession + 1, emittedSession);
assertEq(emittedSession, gatekeeper.currentWeavingSession());
assertEq(prevLength, gatekeeper.DEPTH());
assertEq(gatekeeper.slotLengths(emittedSession, 0), 1);
assertEq(prevEntries, gatekeeper.ENTRIES());
assertEq(gatekeeper.filledEntries(emittedSession), 1);
previousHash = bytes32(0);
} else {
assertEq(prevLength + 1, gatekeeper.slotLengths(emittedSession, targetSlot));
assertEq(prevEntries + 1, gatekeeper.filledEntries(emittedSession));
}
{
bytes32 preimage1 = gatekeeper.slotValues(emittedSession, globalIndex);
bytes32 preimage2 = gatekeeper.computePreimage(globalIndex, amount, who);
assertEq(preimage1, preimage2);
}
{
bytes32 nodeHash1 = gatekeeper.treeNodesLatest(emittedSession, globalIndex);
bytes32 preimageHash = Hashes.efficientKeccak256(
gatekeeper.computePreimage(globalIndex, amount, who)
);
bytes32 nodeHash2 = Hashes.efficientKeccak256(previousHash, preimageHash);
assertEq(nodeHash1, nodeHash2);
}
}
function _prepareArrays(uint256 count) private view returns (bytes32[] memory, uint256[] memory) {
bytes32[] memory whos = new bytes32[](count);
uint256[] memory amounts = new uint256[](count);
uint256 existential = gatekeeper.EXISTENTIAL_DEPOSIT();
for (uint256 i = 0; i < count; i++) {
whos[i] = keccak256(abi.encodePacked("user", i));
amounts[i] = existential + 1 + i;
}
return (whos, amounts);
}
function _getInsertedDataFromLogs() private returns (uint256 index, uint256 session) {
Vm.Log[] memory entries = vm.getRecordedLogs();
bytes32 targetTopic = keccak256("ThreadInserted(uint256,uint256)");
for (uint256 i = entries.length; i > 0; i--) {
if (entries[i - 1].topics.length > 0 && entries[i - 1].topics[0] == targetTopic) {
(index, session) = abi.decode(entries[i - 1].data, (uint256, uint256));
return (index, session);
}
}
revert("Target event not found in logs");
}
}