ghost-dao-contracts/src/types/NoteKeeper.sol
Uncle Fatso 0860792f12
make governance great again
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2026-09-04 11:22:22 +03:00

161 lines
5.1 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IGHST} from "../interfaces/IGHST.sol";
import {IStaking} from "../interfaces/IStaking.sol";
import {ITreasury} from "../interfaces/ITreasury.sol";
import {INoteKeeper} from "../interfaces/INoteKeeper.sol";
import {EnumerableSet} from "@openzeppelin-contracts/utils/structs/EnumerableSet.sol";
import {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
abstract contract NoteKeeper is INoteKeeper {
using EnumerableSet for EnumerableSet.UintSet;
using SafeERC20 for IGHST;
mapping(address => Note[]) public notes;
mapping(address => mapping(uint256 => address)) private _noteTransfers;
mapping(address => EnumerableSet.UintSet) private _pendingIndexes;
IGHST internal immutable _GHST;
IStaking internal immutable _STAKING;
ITreasury internal immutable _TREASURY;
constructor(address _ghstAddress, address _stakingAddress, address _treasuryAddress) {
_GHST = IGHST(_ghstAddress);
_STAKING = IStaking(_stakingAddress);
_TREASURY = ITreasury(_treasuryAddress);
_STAKING.toggleLock();
}
function addNote(
address user,
uint256 payout,
uint48 expiry,
uint48 marketId
) internal returns (uint256 index) {
index = notes[user].length;
_pendingIndexes[user].add(index);
notes[user].push(
Note({
payout: _GHST.balanceTo(payout),
created: uint48(block.timestamp),
matured: expiry,
redeemed: 0,
marketId: marketId
})
);
_TREASURY.mint(address(this), payout);
_STAKING.stake(payout, address(this), false, true);
}
function redeem(
address user,
bool sendGhst,
uint256[] memory indexes
) public override returns (uint256 payout) {
uint256 expected = _maturedPayout(msg.sender, indexes);
uint256 balance = _GHST.balanceOf(address(this));
if (balance < expected) {
uint256 deficit = expected - balance;
payout = balance + _STAKING.claimByAmount(address(this), deficit, false);
} else payout = expected;
if (payout < expected) revert IncompleteRedeemPayout();
if (sendGhst) _GHST.safeTransfer(user, payout);
else _STAKING.unwrap(user, payout);
}
function forceRedeem(
bytes32 receiver,
uint256[] memory indexes
) public override returns (uint256 expected) {
uint256 payout = _maturedPayout(msg.sender, indexes);
uint256 balance = _GHST.balanceOf(address(this));
expected = payout;
if (balance > 0) {
uint256 toGhost = payout > balance ? balance : payout;
_STAKING.ghost(receiver, toGhost);
payout -= toGhost;
}
if (payout > 0) {
payout -= _STAKING.breakout(receiver, payout);
}
if (payout > 0) revert IncompleteRedeemPayout();
}
function redeemAll(
address user,
bool sendGhst
) external override returns (uint256) {
return redeem(user, sendGhst, indexesFor(user));
}
function forceRedeemAll(bytes32 receiver) external override returns (uint256) {
return forceRedeem(receiver, indexesFor(msg.sender));
}
function pushNote(address to, uint256 index) external override {
if (notes[msg.sender][index].created == 0) revert NoteNotFound(msg.sender, index);
_noteTransfers[msg.sender][index] = to;
}
function pullNote(
address from,
uint256 index
) external override returns (uint256 newIndex) {
if (_noteTransfers[from][index] != msg.sender) revert TransferNotFound(from, index);
if (!_pendingIndexes[from].contains(index)) revert AlreadyRedeemed(from, index);
newIndex = notes[msg.sender].length;
notes[msg.sender].push(notes[from][index]);
_pendingIndexes[msg.sender].add(newIndex);
delete notes[from][index];
_pendingIndexes[from].remove(index);
}
function indexesFor(address user)
public
view
override
returns (uint256[] memory)
{
return _pendingIndexes[user].values();
}
function pendingFor(address user, uint256 index)
public
view
override
returns (
uint256 payout,
bool matured
)
{
Note memory note = notes[user][index];
payout = note.payout;
matured = _pendingIndexes[user].contains(index) && note.matured <= block.timestamp && payout > 0;
}
function _maturedPayout(address user, uint256[] memory indexes) private returns (uint256 payout) {
uint48 time = uint48(block.timestamp);
uint256 i = 0;
for (; i < indexes.length; ) {
(uint256 pay, bool matured) = pendingFor(user, indexes[i]);
if (matured) {
_pendingIndexes[user].remove(indexes[i]);
notes[user][indexes[i]].redeemed = time;
payout += pay;
}
unchecked { ++i; }
}
}
}