Compare commits

..

No commits in common. "40b6d02e8dc990a4ba4b3475973d467d31e05c67" and "0d043ea964e7cc377cbb3a05396c02871bb1b665" have entirely different histories.

11 changed files with 111 additions and 381 deletions

2
.gitignore vendored
View File

@ -4,7 +4,7 @@ out/
script/ script/
# Ignores development broadcast logs # Ignores development broadcast logs
/broadcast !/broadcast
/broadcast/*/31337/ /broadcast/*/31337/
/broadcast/**/dry-run/ /broadcast/**/dry-run/

View File

@ -10,15 +10,12 @@ import {Weaver} from "./types/Weaver.sol";
import {FullMath} from "./libraries/FullMath.sol"; import {FullMath} from "./libraries/FullMath.sol";
import {Checkpoints} from "./libraries/Checkpoints.sol"; import {Checkpoints} from "./libraries/Checkpoints.sol";
import {RotationPacking, RequestPacking} from "./libraries/Packing.sol";
import {ReentrancyGuard} from "@openzeppelin-contracts/utils/ReentrancyGuard.sol"; import {ReentrancyGuard} from "@openzeppelin-contracts/utils/ReentrancyGuard.sol";
contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard { contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
using Checkpoints for Checkpoints.Trace256; using Checkpoints for Checkpoints.Trace256;
using RotationPacking for RotationPacking.RotationState;
using RequestPacking for RequestPacking.RequestPayload;
uint256 private constant COMMISSION_DIVISOR = type(uint32).max; uint256 private constant COMMISSION_DIVISOR = 2**32;
uint256 private constant SECP256K1_N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141; uint256 private constant SECP256K1_N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;
uint256 private constant SECP256K1_Q = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141; uint256 private constant SECP256K1_Q = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;
uint256 private constant SHIFT_FACTOR = (2**128) % SECP256K1_N; uint256 private constant SHIFT_FACTOR = (2**128) % SECP256K1_N;
@ -31,7 +28,7 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
address private _previousAddress; address private _previousAddress;
Checkpoints.Trace256 private _aggregatedPublicKeys; Checkpoints.Trace256 private _aggregatedPublicKeys;
mapping(bytes32 => uint256) private _packedRotationStates; mapping(bytes32 => RotationState) private _publicKeyMetadatas;
constructor( constructor(
uint256 _existentialDeposit, uint256 _existentialDeposit,
@ -57,14 +54,17 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
function updatePublicKeyMetadata(uint256 exodusSession, bytes32 publicKey, uint8 parity) external { function updatePublicKeyMetadata(uint256 exodusSession, bytes32 publicKey, uint8 parity) external {
require(msg.sender == deployer); require(msg.sender == deployer);
// TODO: uncomment line below, needed only for testing purposes // TODO: uncomment line below, needed only for testing purposes
// and should push to hardcoded exodusSession 0 always // and should push to hardcoded exodusSession 0 in any case
// deployer = address(0); // deployer = address(0);
RotationState memory rotationState = RotationState({
parity: parity,
// forge-lint: disable-next-line(unsafe-typecast) // forge-lint: disable-next-line(unsafe-typecast)
uint256 packedRotationState = RotationPacking.pack(parity, uint64(exodusSession)); session: uint64(exodusSession)
});
_aggregatedPublicKeys.push(exodusSession, uint256(publicKey)); _aggregatedPublicKeys.push(exodusSession, uint256(publicKey));
_packedRotationStates[publicKey] = packedRotationState; _publicKeyMetadatas[publicKey] = rotationState;
} }
function previousAddress() external override view returns (address) { function previousAddress() external override view returns (address) {
@ -77,32 +77,40 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
function latestPublicKeyInfo() external override view returns (bytes32, uint8, uint64) { function latestPublicKeyInfo() external override view returns (bytes32, uint8, uint64) {
bytes32 latestPublicKey = bytes32(_aggregatedPublicKeys.latest()); bytes32 latestPublicKey = bytes32(_aggregatedPublicKeys.latest());
uint256 packed = _packedRotationStates[latestPublicKey]; RotationState memory state = _publicKeyMetadatas[latestPublicKey];
RotationPacking.RotationState memory state = RotationPacking.unpack(packed);
return (latestPublicKey, state.parity, state.session); return (latestPublicKey, state.parity, state.session);
} }
function getRotationInfoAt(uint256 exodusSession) public override view returns (bytes32, uint8, uint64) { function getRotationInfoAt(uint256 exodusSession) public override view returns (bytes32, uint8, uint64) {
bytes32 publicKey = bytes32(_aggregatedPublicKeys.upperLookup(exodusSession)); bytes32 publicKey = bytes32(_aggregatedPublicKeys.upperLookup(exodusSession));
uint256 packed = _packedRotationStates[publicKey]; RotationState memory rotationState = _publicKeyMetadatas[publicKey];
RotationPacking.RotationState memory state = RotationPacking.unpack(packed);
if (exodusSession < state.session) { if (exodusSession < rotationState.session) {
return IGatekeeper(_previousAddress).getRotationInfoAt(exodusSession); return IGatekeeper(_previousAddress).getRotationInfoAt(exodusSession);
} }
return (publicKey, state.parity, state.session); return (publicKey, rotationState.parity, rotationState.session);
} }
function verify( function verify(
bytes calldata call, bytes calldata call,
bytes calldata nonce, bytes calldata nonce,
bytes32 s bytes32 s
) external nonReentrant payable returns (bytes memory) { ) external nonReentrant returns (bytes memory) {
_verifySchnorrSignature(call, nonce, s); (bytes32 px, uint8 p) = _extractPublicKey(call);
(bool success, bytes memory data) = address(this).call{ value: msg.value }(call); uint8 v = p == 0 ? 27 : 28;
bytes32 e = _challenge(call, nonce, px, p);
bytes32 sp = bytes32(SECP256K1_Q - mulmod(uint256(s), uint256(px), SECP256K1_Q));
bytes32 ep = bytes32(SECP256K1_Q - mulmod(uint256(e), uint256(px), SECP256K1_Q));
address recoveredR = ecrecover(sp, v, px, ep);
if (sp == 0) revert BadSignature();
if (recoveredR == address(0)) revert BadSignature();
if (recoveredR != _uncompressedPointToAddress(nonce)) revert BadSignature();
(bool success, bytes memory data) = address(this).call(call);
if (!success) revert ExecutionReverted(); if (!success) revert ExecutionReverted();
return data; return data;
@ -121,34 +129,23 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
function materialize( function materialize(
uint256 exodusSession, uint256 exodusSession,
uint256 amount, uint256 amount,
uint256 packed uint256 commission,
) external override payable { address receiver
) external override {
if (msg.sender != address(this)) revert NotGatekeeper(); if (msg.sender != address(this)) revert NotGatekeeper();
RequestPacking.RequestPayload memory payload = RequestPacking.unpack(packed);
if (payload.chainId != block.chainid) revert WrongChainId();
StorageHistory(storageHistory).trySetTransactionExecuted(exodusSession); StorageHistory(storageHistory).trySetTransactionExecuted(exodusSession);
IStorageHistory(storageHistory).tryIncreaseBridgeOut(amount); IStorageHistory(storageHistory).tryIncreaseBridgeOut(amount);
uint256 commissionAmount = FullMath.mulDiv(amount, uint256(payload.commission), COMMISSION_DIVISOR); uint256 commissionAmount = FullMath.mulDiv(amount, commission, COMMISSION_DIVISOR);
uint256 receiverAmount = amount - commissionAmount; uint256 receiverAmount = amount - commissionAmount;
IStaking(staking).materialize(receiver, receiverAmount);
IStaking(staking).materialize(payload.receiver, receiverAmount);
if (commissionAmount > 0) { if (commissionAmount > 0) {
uint256 totalReserves = IStaking(staking).totalReserves(); IStaking(staking).materialize(tx.origin, commission);
uint256 baseSupply = IStaking(staking).baseSupply();
uint256 minimumNativeRequired = FullMath.mulDiv(commissionAmount, totalReserves, baseSupply);
if (minimumNativeRequired > msg.value) revert InsufficientValue();
IStaking(staking).materialize(tx.origin, commissionAmount);
(bool sentSuccess,) = payload.receiver.call{ value: msg.value }("");
if (!sentSuccess) revert SendFailed();
} }
emit Materialized(payload.receiver, amount); emit Materialized(receiver, amount);
} }
function rotate( function rotate(
@ -159,30 +156,18 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
if (msg.sender != address(this)) revert NotGatekeeper(); if (msg.sender != address(this)) revert NotGatekeeper();
StorageHistory(storageHistory).trySetTransactionExecuted(exodusSession); StorageHistory(storageHistory).trySetTransactionExecuted(exodusSession);
RotationState memory rotationState = RotationState({
parity: newParity,
// forge-lint: disable-next-line(unsafe-typecast) // forge-lint: disable-next-line(unsafe-typecast)
uint256 packedRotationState = RotationPacking.pack(newParity, uint64(exodusSession)); session: uint64(exodusSession)
});
_aggregatedPublicKeys.push(exodusSession, uint256(newPublicKey)); _aggregatedPublicKeys.push(exodusSession, uint256(newPublicKey));
_packedRotationStates[newPublicKey] = packedRotationState; _publicKeyMetadatas[newPublicKey] = rotationState;
emit Rotated(newPublicKey, newParity); emit Rotated(newPublicKey, newParity);
} }
function _verifySchnorrSignature(bytes calldata call, bytes calldata nonce, bytes32 s) internal view {
(bytes32 px, uint8 p) = _extractPublicKey(call);
uint8 v = p == 0 ? 27 : 28;
bytes32 e = _challenge(call, nonce, px, p);
bytes32 sp = bytes32(SECP256K1_Q - mulmod(uint256(s), uint256(px), SECP256K1_Q));
bytes32 ep = bytes32(SECP256K1_Q - mulmod(uint256(e), uint256(px), SECP256K1_Q));
address recoveredR = ecrecover(sp, v, px, ep);
if (sp == 0) revert BadSignature();
if (recoveredR == address(0)) revert BadSignature();
if (recoveredR != _uncompressedPointToAddress(nonce)) revert BadSignature();
}
function _uncompressedPointToAddress(bytes calldata nonce) internal pure returns (address) { function _uncompressedPointToAddress(bytes calldata nonce) internal pure returns (address) {
if (nonce.length != 65) revert BadSignature(); if (nonce.length != 65) revert BadSignature();
if (nonce[0] != 0x04) revert BadSignature(); if (nonce[0] != 0x04) revert BadSignature();

View File

@ -1,8 +1,8 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20; pragma solidity ^0.8.20;
import {IERC20} from "@openzeppelin-contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; import {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin-contracts/token/ERC20/IERC20.sol";
import {GhostWarmup} from "./Warmup.sol"; import {GhostWarmup} from "./Warmup.sol";
import {Gatekeeper} from "./Gatekeeper.sol"; import {Gatekeeper} from "./Gatekeeper.sol";
@ -11,12 +11,11 @@ import {GhostAccessControlled} from "./types/GhostAccessControlled.sol";
import {ISTNK} from "./interfaces/ISTNK.sol"; import {ISTNK} from "./interfaces/ISTNK.sol";
import {IGHST} from "./interfaces/IGHST.sol"; import {IGHST} from "./interfaces/IGHST.sol";
import {IStaking} from "./interfaces/IStaking.sol"; import {IStaking} from "./interfaces/IStaking.sol";
import {ITreasury} from "./interfaces/ITreasury.sol";
import {IGatekeeper} from "./interfaces/IGatekeeper.sol"; import {IGatekeeper} from "./interfaces/IGatekeeper.sol";
import {IDistributor} from "./interfaces/IDistributor.sol"; import {IDistributor} from "./interfaces/IDistributor.sol";
import {IGhostWarmup} from "./interfaces/IGhostWarmup.sol";
import {IStorageHistory} from "./interfaces/IStorageHistory.sol"; import {IStorageHistory} from "./interfaces/IStorageHistory.sol";
import {IGhostAuthority} from "./interfaces/IGhostAuthority.sol"; import {IGhostAuthority} from "./interfaces/IGhostAuthority.sol";
import {IGhostWarmup} from "./interfaces/IGhostWarmup.sol";
contract GhostStaking is IStaking, GhostAccessControlled { contract GhostStaking is IStaking, GhostAccessControlled {
using SafeERC20 for IERC20; using SafeERC20 for IERC20;
@ -229,15 +228,6 @@ contract GhostStaking is IStaking, GhostAccessControlled {
return (deposit, payout, expiry, lock); return (deposit, payout, expiry, lock);
} }
function totalReserves() external override view returns (uint256) {
address treasury = ISTNK(stnk).treasury();
return ITreasury(treasury).totalReserves();
}
function baseSupply() external override view returns (uint256) {
return IERC20(ftso).totalSupply();
}
function _sendStnkBased( function _sendStnkBased(
uint256 amount, uint256 amount,
address to, address to,

View File

@ -3,16 +3,18 @@ pragma solidity ^0.8.20;
interface IGatekeeper { interface IGatekeeper {
error NotStaking(); error NotStaking();
error SendFailed();
error WrongChainId();
error BadSignature();
error NotGatekeeper(); error NotGatekeeper();
error BadSignature();
error AlreadyExecuted(); error AlreadyExecuted();
error InvalidCalldata();
error NonExistentAmount(); error NonExistentAmount();
error InsufficientValue(); error InvalidCalldata();
error ExecutionReverted(); error ExecutionReverted();
struct RotationState {
uint8 parity;
uint64 session;
}
event Ghosted(bytes32 indexed receiver, uint256 indexed amount); event Ghosted(bytes32 indexed receiver, uint256 indexed amount);
event Materialized(address indexed receiver, uint256 indexed amount); event Materialized(address indexed receiver, uint256 indexed amount);
event Rotated(bytes32 indexed aggregatedPublicKey, uint8 indexed parity); event Rotated(bytes32 indexed aggregatedPublicKey, uint8 indexed parity);
@ -24,7 +26,7 @@ interface IGatekeeper {
function latestPublicKeyInfo() external view returns (bytes32, uint8, uint64); function latestPublicKeyInfo() external view returns (bytes32, uint8, uint64);
function getRotationInfoAt(uint256 session) external view returns (bytes32, uint8, uint64); function getRotationInfoAt(uint256 session) external view returns (bytes32, uint8, uint64);
function materialize(uint256 session, uint256 amount, uint256 payload) external payable; function materialize(uint256 session, uint256 amount, uint256 commission, address receiver) external;
function rotate(uint256 session, bytes32 publicKey, uint8 parity) external; function rotate(uint256 session, bytes32 publicKey, uint8 parity) external;
function ghost(bytes32 receiver, uint256 amount) external returns (uint256); function ghost(bytes32 receiver, uint256 amount) external returns (uint256);
} }

View File

@ -18,7 +18,6 @@ interface ISTNK is IERC20 {
function increaseAllowance(address spender, uint256 imbalance) external returns (bool); function increaseAllowance(address spender, uint256 imbalance) external returns (bool);
function decreaseAllowance(address spender, uint256 imbalance) external returns (bool); function decreaseAllowance(address spender, uint256 imbalance) external returns (bool);
function circulatingSupply() external view returns (uint256); function circulatingSupply() external view returns (uint256);
function treasury() external view returns (address);
function sharesForBalance(uint256 amount) external view returns (uint256); function sharesForBalance(uint256 amount) external view returns (uint256);
function balanceForShares(uint256 shares) external view returns (uint256); function balanceForShares(uint256 shares) external view returns (uint256);
function index() external view returns (uint256); function index() external view returns (uint256);

View File

@ -74,6 +74,4 @@ interface IStaking {
function index() external view returns (uint256); function index() external view returns (uint256);
function supplyInWarmup() external view returns (uint256); function supplyInWarmup() external view returns (uint256);
function ghostedSupply() external view returns (uint256); function ghostedSupply() external view returns (uint256);
function totalReserves() external view returns (uint256);
function baseSupply() external view returns (uint256);
} }

View File

@ -65,6 +65,5 @@ interface ITreasury {
function indexInRegistry(address _address, STATUS _status) external view returns (bool, uint256); function indexInRegistry(address _address, STATUS _status) external view returns (bool, uint256);
function excessReserves() external view returns (uint256); function excessReserves() external view returns (uint256);
function baseSupply() external view returns (uint256); function baseSupply() external view returns (uint256);
function totalReserves() external view returns (uint256);
} }

View File

@ -1,54 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
library RotationPacking {
struct RotationState {
uint8 parity;
uint64 session;
}
function pack(uint8 parity, uint64 session) internal pure returns (uint256 packed) {
// forge-lint: disable-next-line(unsafe-typecast)
return (uint256(session) << 8) | uint256(parity);
}
function pack(RotationState calldata state) internal pure returns (uint256 packed) {
return pack(state.parity, state.session);
}
function unpack(uint256 packed) internal pure returns (RotationState memory state) {
// forge-lint: disable-next-line(unsafe-typecast)
state.parity = uint8(packed & 0xFF);
state.session = uint64((packed >> 8) & 0xFFFFFFFFFFFFFFFF);
return state;
}
}
library RequestPacking {
struct RequestPayload {
uint32 commission;
uint64 chainId;
address receiver;
}
function pack(uint32 commission, uint64 chainId, address receiver) internal pure returns (uint256 packed) {
packed = uint256(uint160(receiver)) << 96;
packed |= uint256(chainId) << 32;
packed |= uint256(commission);
return packed;
}
function pack(RequestPayload calldata data) internal pure returns (uint256 packed) {
return pack(data.commission, data.chainId, data.receiver);
}
function unpack(uint256 packed) internal pure returns (RequestPayload memory data) {
// forge-lint: disable-next-line(unsafe-typecast)
data.commission = uint32(packed & 0xFFFFFFFF);
// forge-lint: disable-next-line(unsafe-typecast)
data.chainId = uint64((packed >> 32) & 0xFFFFFFFFFFFFFFFF);
// forge-lint: disable-next-line(unsafe-typecast)
data.receiver = address(uint160(packed >> 96));
return data;
}
}

View File

@ -44,6 +44,7 @@ contract StorageHistory is IStorageHistory {
function increaseBridgeIn(uint256 amount) external override { function increaseBridgeIn(uint256 amount) external override {
if (msg.sender != _currentOwner) revert NotCreator(); if (msg.sender != _currentOwner) revert NotCreator();
// forge-lint: disable-next-line(unsafe-typecast) // forge-lint: disable-next-line(unsafe-typecast)
_snapshot.amountIn += uint104(amount); _snapshot.amountIn += uint104(amount);
} }
@ -51,6 +52,7 @@ contract StorageHistory is IStorageHistory {
function tryIncreaseBridgeOut(uint256 amount) external override { function tryIncreaseBridgeOut(uint256 amount) external override {
if (msg.sender != _currentOwner) revert NotCreator(); if (msg.sender != _currentOwner) revert NotCreator();
if (bridgeImbalance() < amount) revert BridgeOutImpossible(); if (bridgeImbalance() < amount) revert BridgeOutImpossible();
// forge-lint: disable-next-line(unsafe-typecast) // forge-lint: disable-next-line(unsafe-typecast)
_snapshot.amountOut += uint104(amount); _snapshot.amountOut += uint104(amount);
} }

View File

@ -3,76 +3,34 @@ pragma solidity 0.8.20;
import {Test} from "forge-std/Test.sol"; import {Test} from "forge-std/Test.sol";
import {Gatekeeper} from "../../src/Gatekeeper.sol"; import {Gatekeeper} from "../../src/Gatekeeper.sol";
import {FullMath} from "../../src/libraries/FullMath.sol";
import {RequestPacking} from "../../src/libraries/Packing.sol";
import {IStorageHistory} from "../../src/interfaces/IStorageHistory.sol";
contract MockStaking is Test {
Gatekeeper public gatekeeper;
mapping(address => uint256) private _materializedAmounts;
constructor(uint256 existential) {
gatekeeper = new Gatekeeper(existential, address(0));
}
function runGhost(bytes32 receiver, uint256 amount) external {
gatekeeper.ghost(receiver, amount);
}
function runMaterialize(
uint256 exodusSession,
uint256 amount,
uint256 packed,
address caller
) external payable {
if (msg.value > 0) {
vm.deal(address(gatekeeper), address(gatekeeper).balance + msg.value);
}
vm.prank(address(gatekeeper), caller);
gatekeeper.materialize{value: msg.value}(exodusSession, amount, packed);
}
function materialize(address receiver, uint256 amount) external {
_materializedAmounts[receiver] += amount;
}
function materializedAmount(address who) external view returns (uint256) {
return _materializedAmounts[who];
}
function totalReserves() external pure returns (uint256) {
return type(uint256).max;
}
function baseSupply() external pure returns (uint256) {
return type(uint256).max;
}
}
contract GatekeeperTest is Test { contract GatekeeperTest is Test {
using RequestPacking for RequestPacking.RequestPayload;
address constant ALICE = 0x0000000000000000000000000000000000000001; address constant ALICE = 0x0000000000000000000000000000000000000001;
address constant BOB = 0x0000000000000000000000000000000000000002; address constant BOB = 0x0000000000000000000000000000000000000002;
uint256 constant EXISTENTIAL = 1337; uint256 constant EXISTENTIAL = 1337;
uint256 constant INIT_AMOUNT = 69 * 1e18; uint256 constant INIT_AMOUNT = 69 * 1e18;
Gatekeeper gatekeeper; Gatekeeper gatekeeper;
MockStaking staking;
event Ghosted(bytes32 indexed receiver, uint256 indexed amount); event Ghosted(bytes32 indexed receiver, uint256 indexed amount);
function setUp() public { function setUp() public {
vm.prank(ALICE, ALICE); vm.prank(ALICE, ALICE);
staking = new MockStaking(EXISTENTIAL); gatekeeper = new Gatekeeper(EXISTENTIAL, address(0));
gatekeeper = staking.gatekeeper();
} }
function test_correctInitialization() public view { function test_correctInitialization() public {
assertEq(gatekeeper.staking(), address(staking)); assertEq(gatekeeper.staking(), ALICE);
assertEq(gatekeeper.ghostedSupply(), 0); assertEq(gatekeeper.ghostedSupply(), 0);
bytes32 receiver = bytes32(abi.encodePacked(ALICE));
vm.prank(ALICE);
gatekeeper.ghost(receiver, INIT_AMOUNT);
vm.prank(BOB);
Gatekeeper anotherGatekeeper = new Gatekeeper(EXISTENTIAL, address(gatekeeper));
assertEq(anotherGatekeeper.staking(), ALICE);
assertEq(anotherGatekeeper.ghostedSupply(), INIT_AMOUNT);
assertEq(anotherGatekeeper.existentialDeposit(), EXISTENTIAL);
} }
function test_ghostTokensWork(uint256 ghostAmount) public { function test_ghostTokensWork(uint256 ghostAmount) public {
@ -81,7 +39,7 @@ contract GatekeeperTest is Test {
uint256 ghostedSupply = gatekeeper.ghostedSupply(); uint256 ghostedSupply = gatekeeper.ghostedSupply();
vm.prank(ALICE); vm.prank(ALICE);
staking.runGhost(receiver, ghostAmount); gatekeeper.ghost(receiver, ghostAmount);
assertEq(gatekeeper.ghostedSupply(), ghostedSupply + ghostAmount); assertEq(gatekeeper.ghostedSupply(), ghostedSupply + ghostAmount);
} }
@ -91,7 +49,7 @@ contract GatekeeperTest is Test {
vm.expectRevert(); vm.expectRevert();
vm.prank(someone); vm.prank(someone);
staking.runGhost(receiver, 69); gatekeeper.ghost(receiver, 69);
assertEq(gatekeeper.ghostedSupply(), 0); assertEq(gatekeeper.ghostedSupply(), 0);
} }
@ -102,40 +60,14 @@ contract GatekeeperTest is Test {
vm.expectEmit(true, true, true, false, address(gatekeeper)); vm.expectEmit(true, true, true, false, address(gatekeeper));
emit Ghosted(receiver, ghostAmount); emit Ghosted(receiver, ghostAmount);
vm.prank(ALICE); vm.prank(ALICE);
staking.runGhost(receiver, ghostAmount); gatekeeper.ghost(receiver, ghostAmount);
} }
function test_materializeWork(uint256 exodusSession, uint256 bobAmount, uint32 bobCommission) public { // TODO: revisit with actual signatures from the cargo test
vm.assume(bobAmount > 1337 && bobAmount < 1_000 ether); // function test_materializeWork(uint256 ghostAmount) public {
vm.assume(bobCommission > 0 && bobCommission <= type(uint32).max); // vm.prank(ALICE);
// gatekeeper.materialize(0, ghostAmount, 0, ALICE);
address storageHistory = gatekeeper.storageHistory(); // }
uint256 commissionAmount = FullMath.mulDiv(bobAmount, uint256(bobCommission), type(uint32).max);
uint256 bobPacked = RequestPacking.pack(uint32(bobCommission), uint64(block.chainid), BOB);
vm.prank(ALICE);
staking.runGhost(bytes32(abi.encodePacked(ALICE)), bobAmount);
uint256 nativeNeeded = FullMath.mulDiv(commissionAmount, staking.totalReserves(), staking.baseSupply());
vm.deal(ALICE, nativeNeeded + 1 ether);
assertEq(BOB.balance, 0 ether);
uint256 aliceStartingNative = ALICE.balance;
uint256 previousAliceAmount = staking.materializedAmount(ALICE);
uint256 previousBobAmount = staking.materializedAmount(BOB);
vm.prank(ALICE);
staking.runMaterialize{value: nativeNeeded}(exodusSession, bobAmount, bobPacked, ALICE);
assertApproxEqAbs(previousAliceAmount + commissionAmount, staking.materializedAmount(ALICE), 1000);
assertApproxEqAbs(previousBobAmount + (bobAmount - commissionAmount), staking.materializedAmount(BOB), 1000);
assertEq(BOB.balance, nativeNeeded);
assertEq(ALICE.balance, aliceStartingNative - nativeNeeded);
assert(IStorageHistory(storageHistory).isTransactionExecuted(exodusSession));
}
function test_rotateWork(bytes32 aggregatedPublicKey) public { function test_rotateWork(bytes32 aggregatedPublicKey) public {
vm.warp(block.timestamp + 1); vm.warp(block.timestamp + 1);
@ -149,24 +81,18 @@ contract GatekeeperTest is Test {
vm.expectRevert(); vm.expectRevert();
vm.prank(ALICE); vm.prank(ALICE);
staking.runGhost(receiver, amount); gatekeeper.ghost(receiver, amount);
assertEq(gatekeeper.ghostedSupply(), 0); assertEq(gatekeeper.ghostedSupply(), 0);
} }
function test_signatureVerificationsWorks() public { function test_signatureVerificationsWorks() public {
assertEq(gatekeeper.deployer(), ALICE); assertEq(gatekeeper.deployer(), ALICE);
vm.prank(ALICE);
staking.runGhost(bytes32(abi.encodePacked(ALICE)), type(uint104).max);
vm.prank(ALICE); vm.prank(ALICE);
gatekeeper.updatePublicKeyMetadata(0, 0x875cdcba4ae5494518fa2602791e667ca0998402b6a69e5b7cb4c72dba4e4669, 0); gatekeeper.updatePublicKeyMetadata(0, 0x875cdcba4ae5494518fa2602791e667ca0998402b6a69e5b7cb4c72dba4e4669, 0);
(bytes32 prevPublicKey, uint8 prevParity,) = gatekeeper.latestPublicKeyInfo(); (bytes32 prevPublicKey, uint8 prevParity, uint64 prevSession) = gatekeeper.latestPublicKeyInfo();
// genesis validators 4 validators, DKG #0
// exodus session #0
gatekeeper.verify( gatekeeper.verify(
hex"49f03a670000000000000000000000000000000000000000000000000000000000000000875cdcba4ae5494518fa2602791e667ca0998402b6a69e5b7cb4c72dba4e46690000000000000000000000000000000000000000000000000000000000000000", hex"49f03a670000000000000000000000000000000000000000000000000000000000000000875cdcba4ae5494518fa2602791e667ca0998402b6a69e5b7cb4c72dba4e46690000000000000000000000000000000000000000000000000000000000000000",
hex"044d9fa18df9da04381ed256981570a6ef6a0893496ded966fc994662df931ed423b29e2737394b31d8498594caad7b77e36ab31ac79df3796a1b6f26baa5552ae", hex"044d9fa18df9da04381ed256981570a6ef6a0893496ded966fc994662df931ed423b29e2737394b31d8498594caad7b77e36ab31ac79df3796a1b6f26baa5552ae",
@ -176,46 +102,41 @@ contract GatekeeperTest is Test {
(bytes32 publicKey, uint8 parity, uint64 session) = gatekeeper.latestPublicKeyInfo(); (bytes32 publicKey, uint8 parity, uint64 session) = gatekeeper.latestPublicKeyInfo();
assertEq(publicKey, prevPublicKey); assertEq(publicKey, prevPublicKey);
assertEq(parity, prevParity); assertEq(parity, prevParity);
assertEq(session, 0); assertEq(session, prevSession);
// could not apply signature from DKG #2
vm.expectRevert(); vm.expectRevert();
gatekeeper.verify( gatekeeper.verify(
hex"49f03a6700000000000000000000000000000000000000000000000000000000000000043492bc8bb10848f1f788496ccadde7d458eac4ac2c1eca2dd2c7a506d8fa2c5b0000000000000000000000000000000000000000000000000000000000000000", hex"49f03a6700000000000000000000000000000000000000000000000000000000000000023492bc8bb10848f1f788496ccadde7d458eac4ac2c1eca2dd2c7a506d8fa2c5b0000000000000000000000000000000000000000000000000000000000000000",
hex"0496bacdcc4260a610c9ef465cc1d9f973dda7d4794a65c1d1ea8ea2bb8aa3ddb08d171f5aa267c1ddc64343b4f054424af0c61b57e1f359cd4e23876a0fa4a704", hex"042b45a8909a137e7bab5caf4b2870ece0469c6a4149c10074cfdaf2c114a65daefe14179830c95d07fea439ce78d2ad11898578e5e678dbcb98a7576da9e57c77",
0x34383aacbe014a6fed8b5976ccfcdcbb93cef7e41a6634f934432d9ede912c14 0x5a61b59cededac4010b89bc6935e0cb7d975f5e2db457227ee473203de5247ed
); );
// genesis validators 7 validators, DKG #1
// exodus session #2
gatekeeper.verify( gatekeeper.verify(
hex"49f03a6700000000000000000000000000000000000000000000000000000000000000024e8c1fe96d6737cdabbcc96501d6656b9d0b659b3a528ef507f2d52bef29e1570000000000000000000000000000000000000000000000000000000000000000", hex"49f03a6700000000000000000000000000000000000000000000000000000000000000014e8c1fe96d6737cdabbcc96501d6656b9d0b659b3a528ef507f2d52bef29e1570000000000000000000000000000000000000000000000000000000000000000",
hex"04460e7dce1ebd4cc7527ba80852b6fcdbf8810da3b2173c2f3b3da63206a44407f79d5b5b4c3adcdcce2404c7488240873babb856f065fa551caa6dddea68ca06", hex"04d4c4daeb68bf8e40db4b516ddf3be9dd0f56a81809945e30bf06b7e5b26d1b27c83367de842f00c915f06ab1755dc511a3a3fe213308b5e65c6832d9d9bb3b38",
0x7f6ac70e1e3f3ddd7101a439e7da6f8622a0c8cc38ce42ce81d911d8ad23af90 0xd93d1b1613277ddb27df66bdea16d7b91adf975eb150e27a872ce035655ec13d
); );
(publicKey, parity, session) = gatekeeper.latestPublicKeyInfo(); (publicKey, parity, session) = gatekeeper.latestPublicKeyInfo();
assert(publicKey != prevPublicKey); assert(publicKey != prevPublicKey);
assertEq(parity, prevParity); assertEq(parity, prevParity);
assertEq(session, 2); assertEq(session, prevSession + 1);
prevPublicKey = publicKey; prevPublicKey = publicKey;
prevParity = parity; prevParity = parity;
prevSession = session;
// genesis validators 5 validators, DKG #2
// exodus session #4
gatekeeper.verify( gatekeeper.verify(
hex"49f03a6700000000000000000000000000000000000000000000000000000000000000043492bc8bb10848f1f788496ccadde7d458eac4ac2c1eca2dd2c7a506d8fa2c5b0000000000000000000000000000000000000000000000000000000000000000", hex"49f03a6700000000000000000000000000000000000000000000000000000000000000023492bc8bb10848f1f788496ccadde7d458eac4ac2c1eca2dd2c7a506d8fa2c5b0000000000000000000000000000000000000000000000000000000000000000",
hex"0496bacdcc4260a610c9ef465cc1d9f973dda7d4794a65c1d1ea8ea2bb8aa3ddb08d171f5aa267c1ddc64343b4f054424af0c61b57e1f359cd4e23876a0fa4a704", hex"042b45a8909a137e7bab5caf4b2870ece0469c6a4149c10074cfdaf2c114a65daefe14179830c95d07fea439ce78d2ad11898578e5e678dbcb98a7576da9e57c77",
0x34383aacbe014a6fed8b5976ccfcdcbb93cef7e41a6634f934432d9ede912c14 0x5a61b59cededac4010b89bc6935e0cb7d975f5e2db457227ee473203de5247ed
); );
(publicKey, parity, session) = gatekeeper.latestPublicKeyInfo(); (publicKey, parity, session) = gatekeeper.latestPublicKeyInfo();
assert(publicKey != prevPublicKey); assert(publicKey != prevPublicKey);
assertEq(parity, prevParity); assertEq(parity, prevParity);
assertEq(session, 4); assertEq(session, prevSession + 1);
// could not apply DKG #0 no more
vm.expectRevert(); vm.expectRevert();
gatekeeper.verify( gatekeeper.verify(
hex"49f03a670000000000000000000000000000000000000000000000000000000000000000875cdcba4ae5494518fa2602791e667ca0998402b6a69e5b7cb4c72dba4e46690000000000000000000000000000000000000000000000000000000000000000", hex"49f03a670000000000000000000000000000000000000000000000000000000000000000875cdcba4ae5494518fa2602791e667ca0998402b6a69e5b7cb4c72dba4e46690000000000000000000000000000000000000000000000000000000000000000",
@ -223,63 +144,18 @@ contract GatekeeperTest is Test {
0x24029a571fcaeadd14f4afe8be62afe3d07876ae270c3caf446ecb6cfc7c08f2 0x24029a571fcaeadd14f4afe8be62afe3d07876ae270c3caf446ecb6cfc7c08f2
); );
// could not apply DKG #1 no more
vm.expectRevert(); vm.expectRevert();
gatekeeper.verify( gatekeeper.verify(
hex"49f03a6700000000000000000000000000000000000000000000000000000000000000024e8c1fe96d6737cdabbcc96501d6656b9d0b659b3a528ef507f2d52bef29e1570000000000000000000000000000000000000000000000000000000000000000", hex"49f03a6700000000000000000000000000000000000000000000000000000000000000014e8c1fe96d6737cdabbcc96501d6656b9d0b659b3a528ef507f2d52bef29e1570000000000000000000000000000000000000000000000000000000000000000",
hex"04460e7dce1ebd4cc7527ba80852b6fcdbf8810da3b2173c2f3b3da63206a44407f79d5b5b4c3adcdcce2404c7488240873babb856f065fa551caa6dddea68ca06", hex"04d4c4daeb68bf8e40db4b516ddf3be9dd0f56a81809945e30bf06b7e5b26d1b27c83367de842f00c915f06ab1755dc511a3a3fe213308b5e65c6832d9d9bb3b38",
0x7f6ac70e1e3f3ddd7101a439e7da6f8622a0c8cc38ce42ce81d911d8ad23af90 0xd93d1b1613277ddb27df66bdea16d7b91adf975eb150e27a872ce035655ec13d
); );
// could not apply DKG #2 no more
vm.expectRevert(); vm.expectRevert();
gatekeeper.verify( gatekeeper.verify(
hex"49f03a6700000000000000000000000000000000000000000000000000000000000000043492bc8bb10848f1f788496ccadde7d458eac4ac2c1eca2dd2c7a506d8fa2c5b0000000000000000000000000000000000000000000000000000000000000000", hex"49f03a6700000000000000000000000000000000000000000000000000000000000000023492bc8bb10848f1f788496ccadde7d458eac4ac2c1eca2dd2c7a506d8fa2c5b0000000000000000000000000000000000000000000000000000000000000000",
hex"0496bacdcc4260a610c9ef465cc1d9f973dda7d4794a65c1d1ea8ea2bb8aa3ddb08d171f5aa267c1ddc64343b4f054424af0c61b57e1f359cd4e23876a0fa4a704", hex"042b45a8909a137e7bab5caf4b2870ece0469c6a4149c10074cfdaf2c114a65daefe14179830c95d07fea439ce78d2ad11898578e5e678dbcb98a7576da9e57c77",
0x34383aacbe014a6fed8b5976ccfcdcbb93cef7e41a6634f934432d9ede912c14 0x5a61b59cededac4010b89bc6935e0cb7d975f5e2db457227ee473203de5247ed
); );
uint256 aliceAmountBefore = staking.materializedAmount(ALICE);
uint256 bobAmountBefore = staking.materializedAmount(BOB);
uint256 aliceBalanceBefore = ALICE.balance;
uint256 bobBalanceBefore = BOB.balance;
// execute bridge out, happened on DKG #0
// amount: 69; commission: 0%; ALICE
// exodus session #1
gatekeeper.verify(
hex"39cd99050000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004500000000000000000000000000000000000000010000000000007a6900000000",
hex"04bf0bcb3d42d8187c543a82ee201a221525047020ff0ac03e4d60e322d3eb6d8ab5bfdae43ea0fbf59128d201b8076727c3c63fe8a5c2b890fdb3c97a7da5f207",
0x08f121a1615782c83cce41d7fc2064be57fdeeae8b45f716211b3c8c873f3e4c
);
assertEq(aliceAmountBefore + 69, staking.materializedAmount(ALICE));
assertEq(bobAmountBefore, staking.materializedAmount(BOB));
assertEq(aliceBalanceBefore, ALICE.balance);
assertEq(bobBalanceBefore, BOB.balance);
vm.deal(ALICE, 220);
aliceAmountBefore = staking.materializedAmount(ALICE);
bobAmountBefore = staking.materializedAmount(BOB);
aliceBalanceBefore = ALICE.balance;
bobBalanceBefore = BOB.balance;
uint256 buyback = 220;
// execute bridge out, happened on DKG #1
// amount: 420; commission: 50%; BOB
// exodus session #3
vm.prank(ALICE, ALICE);
gatekeeper.verify{value: buyback}(
hex"39cd9905000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000001a400000000000000000000000000000000000000020000000000007a6980000000",
hex"04089940ed9bb8d7f3a77adfafb0d5ebc45af558ade46844856c049aa912b9d8401826e17f24c0aa625524ff4a65b430a19422b4f9754cec3b862641f17f99b1a9",
0x9d2cf40ab0b1820858fe6f8950e2f511b4acb57b00b37bb93760978e2add4243
);
assertEq(aliceAmountBefore + 210, staking.materializedAmount(ALICE));
assertEq(bobAmountBefore + 210, staking.materializedAmount(BOB));
assertEq(aliceBalanceBefore - buyback, ALICE.balance);
assertEq(bobBalanceBefore + buyback, BOB.balance);
} }
} }

View File

@ -4,56 +4,19 @@ import {Test} from "forge-std/Test.sol";
import {Gatekeeper} from "../../src/Gatekeeper.sol"; import {Gatekeeper} from "../../src/Gatekeeper.sol";
import {IStorageHistory} from "../../src/interfaces/IStorageHistory.sol"; import {IStorageHistory} from "../../src/interfaces/IStorageHistory.sol";
import {RequestPacking} from "../../src/libraries/Packing.sol";
contract MockStaking is Test {
Gatekeeper public gatekeeper;
mapping(address => uint256) private _materializedAmounts;
constructor(uint256 existential) {
gatekeeper = new Gatekeeper(existential, address(0));
}
function redoGatekeeper(uint256 existential) external {
gatekeeper = new Gatekeeper(existential, address(gatekeeper));
}
function runGhost(bytes32 receiver, uint256 amount) external {
gatekeeper.ghost(receiver, amount);
}
function runMaterialize(uint256 exodusSession, uint256 amount, uint256 packed) external {
vm.prank(address(gatekeeper));
gatekeeper.materialize(exodusSession, amount, packed);
}
function materialize(address receiver, uint256 amount) external {
_materializedAmounts[receiver] += amount;
}
function materializedAmount(address who) external view returns (uint256) {
return _materializedAmounts[who];
}
}
contract GatekeeperStorageHistoryTest is Test { contract GatekeeperStorageHistoryTest is Test {
using RequestPacking for RequestPacking.RequestPayload;
address constant ALICE = 0x0000000000000000000000000000000000000001; address constant ALICE = 0x0000000000000000000000000000000000000001;
address constant BOB = 0x0000000000000000000000000000000000000002; address constant BOB = 0x0000000000000000000000000000000000000002;
uint256 constant INIT_AMOUNT = 1337 * 1e18; uint256 constant INIT_AMOUNT = 69 * 1e18;
uint256 constant INIT_GHOSTED = type(uint104).max / 2;
uint256 constant EXISTENTIAL = 0; uint256 constant EXISTENTIAL = 0;
Gatekeeper gatekeeper; Gatekeeper gatekeeper;
MockStaking staking;
function setUp() public { function setUp() public {
vm.prank(ALICE); vm.prank(ALICE);
staking = new MockStaking(EXISTENTIAL); gatekeeper = new Gatekeeper(EXISTENTIAL, address(0));
staking.runGhost(bytes32(abi.encodePacked(ALICE)), INIT_AMOUNT);
gatekeeper = staking.gatekeeper();
} }
function test_correctStorageHistoryInitialization() public view { function test_correctStorageHistoryInitialization() public view {
@ -61,16 +24,13 @@ contract GatekeeperStorageHistoryTest is Test {
IStorageHistory.DeploymentSnapshot memory snapshot = IStorageHistory(storageHistory).deploymentSnapshot(); IStorageHistory.DeploymentSnapshot memory snapshot = IStorageHistory(storageHistory).deploymentSnapshot();
assertEq(snapshot.deployedAt, 0); assertEq(snapshot.deployedAt, 0);
assertEq(snapshot.amountIn, INIT_AMOUNT); assertEq(snapshot.amountIn, 0);
assertEq(snapshot.amountOut, 0); assertEq(snapshot.amountOut, 0);
assertEq(gatekeeper.ghostedSupply(), INIT_AMOUNT); assertEq(gatekeeper.ghostedSupply(), 0);
} }
function test_historicalAmountsOnlyIncrease(uint256 ghostAmount, uint64 exodusSession) public { function test_historicalAmountsOnlyIncrease(uint256 ghostAmount) public {
vm.assume(ghostAmount > 0 && ghostAmount < INIT_AMOUNT); vm.assume(ghostAmount > 0 && ghostAmount < INIT_GHOSTED / 2);
uint256 amountToGhost = ghostAmount;
uint256 amountToMaterialize = ghostAmount / 2;
bytes32 receiver = bytes32(abi.encodePacked(ALICE)); bytes32 receiver = bytes32(abi.encodePacked(ALICE));
address storageHistory = gatekeeper.storageHistory(); address storageHistory = gatekeeper.storageHistory();
@ -80,19 +40,17 @@ contract GatekeeperStorageHistoryTest is Test {
uint104 amountOut = snapshot.amountOut; uint104 amountOut = snapshot.amountOut;
if (ghostAmount % 2 == 0) { if (ghostAmount % 2 == 0) {
staking.runGhost(receiver, amountToGhost); vm.prank(ALICE);
gatekeeper.ghost(receiver, ghostAmount);
// forge-lint: disable-next-line(unsafe-typecast) // forge-lint: disable-next-line(unsafe-typecast)
amountIn += uint104(amountToGhost); amountIn += uint104(ghostAmount);
} else { } else {
if (IStorageHistory(storageHistory).bridgeImbalance() >= ghostAmount) {
vm.prank(ALICE);
gatekeeper.materialize(0, ghostAmount, 0, BOB);
// forge-lint: disable-next-line(unsafe-typecast) // forge-lint: disable-next-line(unsafe-typecast)
uint256 packed = RequestPacking.pack(0, uint64(block.chainid), BOB); amountOut += uint104(ghostAmount);
uint256 previousAmount = staking.materializedAmount(BOB); }
staking.runMaterialize(exodusSession, amountToMaterialize, packed);
assertEq(previousAmount + amountToMaterialize, staking.materializedAmount(BOB));
// forge-lint: disable-next-line(unsafe-typecast)
amountOut += uint104(amountToMaterialize);
} }
IStorageHistory.DeploymentSnapshot memory newSnapshot = IStorageHistory(storageHistory).deploymentSnapshot(); IStorageHistory.DeploymentSnapshot memory newSnapshot = IStorageHistory(storageHistory).deploymentSnapshot();
@ -101,29 +59,4 @@ contract GatekeeperStorageHistoryTest is Test {
assertEq(gatekeeper.ghostedSupply(), amountIn - amountOut); assertEq(gatekeeper.ghostedSupply(), amountIn - amountOut);
} }
function test_inheritanceWorksForHistoricalStorge() public {
uint256 exodusSession = 69;
uint256 packed = RequestPacking.pack(0, uint64(block.chainid), BOB);
staking.runMaterialize(exodusSession, INIT_AMOUNT, packed);
staking.runGhost(bytes32(abi.encodePacked(ALICE)), INIT_AMOUNT);
address prevStorageHistory = gatekeeper.storageHistory();
IStorageHistory.DeploymentSnapshot memory prevSnapshot = IStorageHistory(prevStorageHistory).deploymentSnapshot();
staking.redoGatekeeper(EXISTENTIAL + 1);
assert(gatekeeper != staking.gatekeeper());
address currStorageHistory = gatekeeper.storageHistory();
IStorageHistory.DeploymentSnapshot memory currSnapshot = IStorageHistory(currStorageHistory).deploymentSnapshot();
assertEq(prevSnapshot.amountIn, currSnapshot.amountIn);
assertEq(prevSnapshot.amountOut, currSnapshot.amountOut);
assertEq(prevSnapshot.deployedAt, currSnapshot.deployedAt);
assert(IStorageHistory(prevStorageHistory).isTransactionExecuted(exodusSession));
assert(IStorageHistory(currStorageHistory).isTransactionExecuted(exodusSession));
assertEq(gatekeeper.existentialDeposit(), EXISTENTIAL);
assertEq(staking.gatekeeper().existentialDeposit(), EXISTENTIAL + 1);
}
} }