ghost-dao-contracts/src/Gatekeeper.sol
Uncle Fatso f71ae5aebe
implementation of buyback logic
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2026-08-25 20:22:02 +03:00

253 lines
9.5 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IStaking} from "./interfaces/IStaking.sol";
import {IGatekeeper} from "./interfaces/IGatekeeper.sol";
import {IStorageHistory} from "./interfaces/IStorageHistory.sol";
import {StorageHistory} from "./types/StorageHistory.sol";
import {Weaver} from "./types/Weaver.sol";
import {FullMath} from "./libraries/FullMath.sol";
import {Checkpoints} from "./libraries/Checkpoints.sol";
import {RotationPacking, RequestPacking} from "./libraries/Packing.sol";
import {ReentrancyGuard} from "@openzeppelin-contracts/utils/ReentrancyGuard.sol";
contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
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 SECP256K1_N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;
uint256 private constant SECP256K1_Q = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;
uint256 private constant SHIFT_FACTOR = (2**128) % SECP256K1_N;
address public override staking;
address public override storageHistory;
uint256 public override existentialDeposit;
address public deployer;
address private _previousAddress;
Checkpoints.Trace256 private _aggregatedPublicKeys;
mapping(bytes32 => uint256) private _packedRotationStates;
constructor(
uint256 _existentialDeposit,
address _previousGatekeeperAddress
) Weaver(_previousGatekeeperAddress) {
existentialDeposit = _existentialDeposit;
if (_previousGatekeeperAddress != address(0)) {
require(_previousGatekeeperAddress != address(0));
require(_previousGatekeeperAddress != address(this));
_previousAddress = _previousGatekeeperAddress;
storageHistory = IGatekeeper(_previousGatekeeperAddress).storageHistory();
staking = IGatekeeper(_previousGatekeeperAddress).staking();
} else {
StorageHistory newStorageHistory = new StorageHistory(msg.sender);
storageHistory = address(newStorageHistory);
staking = msg.sender;
deployer = tx.origin;
}
}
function updatePublicKeyMetadata(uint256 exodusSession, bytes32 publicKey, uint8 parity) external {
require(msg.sender == deployer);
// TODO: uncomment line below, needed only for testing purposes
// and should push to hardcoded exodusSession 0 always
// deployer = address(0);
// forge-lint: disable-next-line(unsafe-typecast)
uint256 packedRotationState = RotationPacking.pack(parity, uint64(exodusSession));
_aggregatedPublicKeys.push(exodusSession, uint256(publicKey));
_packedRotationStates[publicKey] = packedRotationState;
}
function previousAddress() external override view returns (address) {
return _previousAddress;
}
function ghostedSupply() external override view returns (uint256) {
return IStorageHistory(storageHistory).bridgeImbalance();
}
function latestPublicKeyInfo() external override view returns (bytes32, uint8, uint64) {
bytes32 latestPublicKey = bytes32(_aggregatedPublicKeys.latest());
uint256 packed = _packedRotationStates[latestPublicKey];
RotationPacking.RotationState memory state = RotationPacking.unpack(packed);
return (latestPublicKey, state.parity, state.session);
}
function getRotationInfoAt(uint256 exodusSession) public override view returns (bytes32, uint8, uint64) {
bytes32 publicKey = bytes32(_aggregatedPublicKeys.upperLookup(exodusSession));
uint256 packed = _packedRotationStates[publicKey];
RotationPacking.RotationState memory state = RotationPacking.unpack(packed);
if (exodusSession < state.session) {
return IGatekeeper(_previousAddress).getRotationInfoAt(exodusSession);
}
return (publicKey, state.parity, state.session);
}
function verify(
bytes calldata call,
bytes calldata nonce,
bytes32 s
) external nonReentrant payable returns (bytes memory) {
_verifySchnorrSignature(call, nonce, s);
(bool success, bytes memory data) = address(this).call{ value: msg.value }(call);
if (!success) revert ExecutionReverted();
return data;
}
function ghost(bytes32 receiver, uint256 amount) external override returns (uint256) {
if (msg.sender != staking) revert NotStaking();
if (amount < existentialDeposit) revert NonExistentAmount();
IStorageHistory(storageHistory).increaseBridgeIn(amount);
emit Ghosted(receiver, amount);
return _insertTreeNode(receiver, amount);
}
function materialize(
uint256 exodusSession,
uint256 amount,
uint256 packed
) external override payable {
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);
IStorageHistory(storageHistory).tryIncreaseBridgeOut(amount);
uint256 commissionAmount = FullMath.mulDiv(amount, uint256(payload.commission), COMMISSION_DIVISOR);
uint256 receiverAmount = amount - commissionAmount;
IStaking(staking).materialize(payload.receiver, receiverAmount);
if (commissionAmount > 0) {
uint256 totalReserves = IStaking(staking).totalReserves();
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);
}
function rotate(
uint256 exodusSession,
bytes32 newPublicKey,
uint8 newParity
) external override {
if (msg.sender != address(this)) revert NotGatekeeper();
StorageHistory(storageHistory).trySetTransactionExecuted(exodusSession);
// forge-lint: disable-next-line(unsafe-typecast)
uint256 packedRotationState = RotationPacking.pack(newParity, uint64(exodusSession));
_aggregatedPublicKeys.push(exodusSession, uint256(newPublicKey));
_packedRotationStates[newPublicKey] = packedRotationState;
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) {
if (nonce.length != 65) revert BadSignature();
if (nonce[0] != 0x04) revert BadSignature();
bytes32 rx = bytes32(nonce[1:33]);
bytes32 ry = bytes32(nonce[33:65]);
return address(uint160(uint256(keccak256(abi.encodePacked(rx, ry)))));
}
function _extractPublicKey(bytes calldata call) internal view returns (bytes32, uint8) {
if (call.length < 36) revert InvalidCalldata();
uint256 exodusSession;
assembly {
exodusSession := calldataload(add(call.offset, 4))
}
(bytes32 publicKey, uint8 parity,) = getRotationInfoAt(exodusSession);
return (publicKey, parity);
}
function _challenge(
bytes calldata call,
bytes calldata r,
bytes32 pk,
uint8 v
) internal pure returns (bytes32) {
if (r.length != 65) revert BadSignature();
bytes32 rx = bytes32(r[1:33]);
bytes1 rv;
{
bytes32 ry = bytes32(r[33:65]);
uint8 rParityByte = (uint256(ry) & 1) == 0 ? 0x02 : 0x03;
rv = bytes1(rParityByte);
}
uint8 pv = v == 0 ? 0x02 : 0x03;
bytes memory message = abi.encodePacked(rv, rx, pv, pk, call);
bytes memory domain = "FROST-secp256k1-SHA256-v1chal";
uint8 dstlen = uint8(domain.length);
bytes32 b0 = sha256(abi.encodePacked(
bytes32(0),
bytes32(0),
message,
hex"0030",
uint8(0),
domain,
dstlen
));
bytes32 b1 = sha256(abi.encodePacked(b0, uint8(1), domain, dstlen));
bytes32 b2 = sha256(abi.encodePacked(b0 ^ b1, uint8(2), domain, dstlen));
uint256 high32 = uint256(b1);
uint256 lowPart = uint256(b2) >> 128;
uint256 highReduced = mulmod(high32, SHIFT_FACTOR, SECP256K1_N);
uint256 finalChallenge = addmod(highReduced, lowPart, SECP256K1_N);
return bytes32(finalChallenge);
}
}