161 lines
5.1 KiB
Solidity
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; }
|
|
}
|
|
}
|
|
}
|