implementation of buyback logic

Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
Uncle Fatso 2026-08-25 20:22:02 +03:00
parent 0d043ea964
commit f71ae5aebe
Signed by: f4ts0
GPG Key ID: 565F4F2860226EBB
8 changed files with 131 additions and 52 deletions

View File

@ -10,12 +10,15 @@ 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 = 2**32; uint256 private constant COMMISSION_DIVISOR = type(uint32).max;
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;
@ -28,7 +31,7 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
address private _previousAddress; address private _previousAddress;
Checkpoints.Trace256 private _aggregatedPublicKeys; Checkpoints.Trace256 private _aggregatedPublicKeys;
mapping(bytes32 => RotationState) private _publicKeyMetadatas; mapping(bytes32 => uint256) private _packedRotationStates;
constructor( constructor(
uint256 _existentialDeposit, uint256 _existentialDeposit,
@ -54,17 +57,14 @@ 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 in any case // and should push to hardcoded exodusSession 0 always
// deployer = address(0); // deployer = address(0);
RotationState memory rotationState = RotationState({ // forge-lint: disable-next-line(unsafe-typecast)
parity: parity, uint256 packedRotationState = RotationPacking.pack(parity, uint64(exodusSession));
// forge-lint: disable-next-line(unsafe-typecast)
session: uint64(exodusSession)
});
_aggregatedPublicKeys.push(exodusSession, uint256(publicKey)); _aggregatedPublicKeys.push(exodusSession, uint256(publicKey));
_publicKeyMetadatas[publicKey] = rotationState; _packedRotationStates[publicKey] = packedRotationState;
} }
function previousAddress() external override view returns (address) { function previousAddress() external override view returns (address) {
@ -77,40 +77,32 @@ 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());
RotationState memory state = _publicKeyMetadatas[latestPublicKey]; uint256 packed = _packedRotationStates[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));
RotationState memory rotationState = _publicKeyMetadatas[publicKey]; uint256 packed = _packedRotationStates[publicKey];
RotationPacking.RotationState memory state = RotationPacking.unpack(packed);
if (exodusSession < rotationState.session) { if (exodusSession < state.session) {
return IGatekeeper(_previousAddress).getRotationInfoAt(exodusSession); return IGatekeeper(_previousAddress).getRotationInfoAt(exodusSession);
} }
return (publicKey, rotationState.parity, rotationState.session); return (publicKey, state.parity, state.session);
} }
function verify( function verify(
bytes calldata call, bytes calldata call,
bytes calldata nonce, bytes calldata nonce,
bytes32 s bytes32 s
) external nonReentrant returns (bytes memory) { ) external nonReentrant payable returns (bytes memory) {
(bytes32 px, uint8 p) = _extractPublicKey(call); _verifySchnorrSignature(call, nonce, s);
uint8 v = p == 0 ? 27 : 28; (bool success, bytes memory data) = address(this).call{ value: msg.value }(call);
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;
@ -129,23 +121,34 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
function materialize( function materialize(
uint256 exodusSession, uint256 exodusSession,
uint256 amount, uint256 amount,
uint256 commission, uint256 packed
address receiver ) external override payable {
) 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, commission, COMMISSION_DIVISOR); uint256 commissionAmount = FullMath.mulDiv(amount, uint256(payload.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) {
IStaking(staking).materialize(tx.origin, commission); 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(receiver, amount); emit Materialized(payload.receiver, amount);
} }
function rotate( function rotate(
@ -156,18 +159,30 @@ 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({ // forge-lint: disable-next-line(unsafe-typecast)
parity: newParity, uint256 packedRotationState = RotationPacking.pack(newParity, uint64(exodusSession));
// forge-lint: disable-next-line(unsafe-typecast)
session: uint64(exodusSession)
});
_aggregatedPublicKeys.push(exodusSession, uint256(newPublicKey)); _aggregatedPublicKeys.push(exodusSession, uint256(newPublicKey));
_publicKeyMetadatas[newPublicKey] = rotationState; _packedRotationStates[newPublicKey] = packedRotationState;
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 {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin-contracts/token/ERC20/IERC20.sol"; import {IERC20} from "@openzeppelin-contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import {GhostWarmup} from "./Warmup.sol"; import {GhostWarmup} from "./Warmup.sol";
import {Gatekeeper} from "./Gatekeeper.sol"; import {Gatekeeper} from "./Gatekeeper.sol";
@ -11,11 +11,12 @@ 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;
@ -228,6 +229,15 @@ 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,18 +3,16 @@ pragma solidity ^0.8.20;
interface IGatekeeper { interface IGatekeeper {
error NotStaking(); error NotStaking();
error NotGatekeeper(); error SendFailed();
error WrongChainId();
error BadSignature(); error BadSignature();
error NotGatekeeper();
error AlreadyExecuted(); error AlreadyExecuted();
error NonExistentAmount();
error InvalidCalldata(); error InvalidCalldata();
error NonExistentAmount();
error InsufficientValue();
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);
@ -26,7 +24,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 commission, address receiver) external; function materialize(uint256 session, uint256 amount, uint256 payload) external payable;
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,6 +18,7 @@ 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,4 +74,6 @@ 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,5 +65,6 @@ 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);
} }

54
src/libraries/Packing.sol Normal file
View File

@ -0,0 +1,54 @@
// 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,7 +44,6 @@ 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);
} }
@ -52,7 +51,6 @@ 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);
} }