ghost-dao-contracts/src/BondDepository.sol
Uncle Fatso 8ac8b67767
get rid of annoying foundry warnings
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2026-03-09 00:37:41 +03:00

327 lines
11 KiB
Solidity

// SPDX-License-Identifier: MIT
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 {IERC20Metadata} from "@openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {NoteKeeper} from "./types/NoteKeeper.sol";
import {IBondDepository} from "./interfaces/IBondDepository.sol";
contract GhostBondDepository is IBondDepository, NoteKeeper {
using SafeERC20 for IERC20;
Market[] public markets;
Term[] public terms;
Metadata[] public metadatas;
mapping(uint256 => Adjustment) public adjustments;
mapping(address => uint256[]) public marketsForQuote;
constructor(
address authority,
address ftso,
address ghst,
address staking,
address treasury
) NoteKeeper(authority, ftso, ghst, staking, treasury) {
_ftso.approve(staking, type(uint256).max);
}
function deposit(
uint256 id,
uint256 amount,
uint256 maxPrice,
address user,
address referral
)
external
override
returns (
uint256 payout,
uint256 expiry,
uint256 index
)
{
Market storage market = markets[id];
Term memory term = terms[id];
uint48 currentTime = uint48(block.timestamp);
if (currentTime >= term.conclusion) revert DepositoryConcluded(term.conclusion);
_decay(id, currentTime);
uint256 price = _marketPrice(id);
if (price > maxPrice) revert DepositoryMaxPrice(price, maxPrice);
expiry = term.fixedTerm ? term.vesting + currentTime : term.vesting;
payout = ((amount * 1e18) / price) / (10**metadatas[id].quoteDecimals);
if (payout > market.maxPayout) revert DepositoryMaxSize(payout, market.maxPayout);
market.capacity -= market.capacityInQuote ? amount : payout;
market.purchased += amount;
market.sold += uint64(payout); // forge-lint: disable-line(unsafe-typecast)
market.totalDebt += uint64(payout); // forge-lint: disable-line(unsafe-typecast)
emit Bond(id, amount, price);
index = addNote(user, payout, uint48(expiry), uint48(id), referral); // forge-lint: disable-line(unsafe-typecast)
IERC20(market.quoteToken).safeTransferFrom(msg.sender, address(_treasury), amount);
if (term.maxDebt < market.totalDebt) {
market.capacity = 0;
emit MarketClosed(id);
} else {
_tune(id, currentTime);
}
}
function _decay(uint256 id, uint48 time) internal {
markets[id].totalDebt -= debtDecay(id);
metadatas[id].lastDecay = time;
if (adjustments[id].active) {
Adjustment storage adjustment = adjustments[id];
(uint64 adjustBy, uint48 secondsSince, bool stillActive) = _controlDecay(id);
terms[id].controlVariable -= adjustBy;
if (stillActive) {
adjustment.change -= adjustBy;
adjustment.timeToAdjusted -= secondsSince;
adjustment.lastAdjustment = time;
} else {
adjustment.active = false;
}
}
}
function _tune(uint256 id, uint48 time) internal {
Metadata memory meta = metadatas[id];
if (time >= meta.lastTune + meta.tuneInterval) {
Market memory market = markets[id];
uint256 timeRemaining = terms[id].conclusion - time;
uint256 price = _marketPrice(id);
uint256 capacity = market.capacityInQuote
? ((market.capacity * 1e18) / price) / (10**meta.quoteDecimals)
: market.capacity;
// forge-lint: disable-next-line(unsafe-typecast)
markets[id].maxPayout = uint64((capacity * meta.depositInterval) / timeRemaining);
uint256 targetDebt = (capacity * meta.length) / timeRemaining;
// forge-lint: disable-next-line(unsafe-typecast)
uint64 newControlVariable = uint64((price * _treasury.baseSupply()) / targetDebt);
emit Tuned(id, terms[id].controlVariable, newControlVariable);
if (newControlVariable >= terms[id].controlVariable) {
terms[id].controlVariable = newControlVariable;
} else {
uint64 change = terms[id].controlVariable - newControlVariable;
adjustments[id] = Adjustment({
change: change,
lastAdjustment: time,
timeToAdjusted: meta.tuneInterval,
active: true
});
}
metadatas[id].lastTune = time;
}
}
function create(
uint256[3] calldata _market,
uint256[2] calldata _terms,
address _quoteToken,
uint32[2] calldata _intervals,
bool[2] calldata _booleans
) external override returns (uint256 id) {
if (
msg.sender != authority.governor() &&
msg.sender != authority.policy()
) revert NotGuardianOrPolicy();
uint256 secondsToConclusion = _terms[1] - block.timestamp;
uint256 decimals = IERC20Metadata(_quoteToken).decimals();
uint64 targetDebt = uint64(_booleans[0]
? ((_market[0] * 1e18) / _market[1]) / 10**decimals
: _market[0]);
// forge-lint: disable-next-line(unsafe-typecast)
uint64 maxPayout = uint64((targetDebt * _intervals[0]) / secondsToConclusion);
uint256 maxDebt = targetDebt + ((targetDebt * _market[2]) / 1e5);
uint256 controlVariable = (_market[1] * _treasury.baseSupply()) / targetDebt;
id = markets.length;
markets.push(
Market({
quoteToken: _quoteToken,
capacityInQuote: _booleans[0],
capacity: _market[0],
totalDebt: targetDebt,
maxPayout: maxPayout,
purchased: 0,
sold: 0
})
);
terms.push(
Term({
fixedTerm: _booleans[1],
controlVariable: uint64(controlVariable), // forge-lint: disable-line(unsafe-typecast)
vesting: uint48(_terms[0]), // forge-lint: disable-line(unsafe-typecast)
conclusion: uint48(_terms[1]), // forge-lint: disable-line(unsafe-typecast)
maxDebt: uint64(maxDebt) // forge-lint: disable-line(unsafe-typecast)
})
);
metadatas.push(
Metadata({
lastTune: uint48(block.timestamp),
lastDecay: uint48(block.timestamp),
// forge-lint: disable-next-line(unsafe-typecast)
length: uint48(secondsToConclusion),
depositInterval: _intervals[0],
tuneInterval: _intervals[1],
// forge-lint: disable-next-line(unsafe-typecast)
quoteDecimals: uint8(decimals)
})
);
marketsForQuote[_quoteToken].push(id);
emit MarketCreated(id, address(_ftso), _quoteToken, _market[1]);
}
function close(uint256 id) external override {
if (
msg.sender != authority.governor() &&
msg.sender != authority.policy()
) revert NotGuardianOrPolicy();
terms[id].conclusion = uint48(block.timestamp);
markets[id].capacity = 0;
emit MarketClosed(id);
}
function marketPrice(uint256 id) public view override returns (uint256) {
uint256 nominator = currentControlVariable(id) * debtRatio(id);
return nominator / (10**metadatas[id].quoteDecimals);
}
function payoutFor(
uint256 id,
uint256 amount
) external view override returns (uint256) {
uint256 nominator = (amount * 1e18) / marketPrice(id);
return nominator / 10**metadatas[id].quoteDecimals;
}
function debtRatio(uint256 id) public view override returns (uint256) {
uint256 nominator = currentDebt(id) * (10**metadatas[id].quoteDecimals);
return nominator / _treasury.baseSupply();
}
function currentDebt(uint256 id) public view override returns (uint256) {
return markets[id].totalDebt - debtDecay(id);
}
function debtDecay(uint256 id) public view override returns (uint64) {
Metadata memory meta = metadatas[id];
uint256 secondsSince = block.timestamp - meta.lastDecay;
return uint64((markets[id].totalDebt * secondsSince) / meta.length);
}
function currentControlVariable(uint256 id) public view override returns (uint256) {
(uint64 decay, , ) = _controlDecay(id);
return terms[id].controlVariable - decay;
}
function isLive(uint256 id) public view override returns (bool) {
return (markets[id].capacity > 0 && terms[id].conclusion > block.timestamp);
}
function liveMarkets() external view override returns (uint256[] memory) {
uint256 number;
uint256 i;
uint256 j;
for (; i < markets.length; ) {
if (isLive(i)) {
unchecked { ++number; }
}
unchecked { ++i; }
}
uint256[] memory ids = new uint256[](number);
i = 0;
for (; i < markets.length; ) {
if (isLive(i)) {
ids[j] = i;
unchecked { ++j; }
}
unchecked { ++i; }
}
return ids;
}
function liveMarketsFor(address token) external view override returns (uint256[] memory) {
uint256[] memory mkts = marketsForQuote[token];
uint256 number;
uint256 i;
uint256 j;
for (; i < mkts.length; ) {
if (isLive(mkts[i])) {
unchecked { ++number; }
}
unchecked { ++i; }
}
uint256[] memory ids = new uint256[](number);
i = 0;
for (; i < mkts.length; ) {
if (isLive(mkts[i])) {
ids[j] = mkts[i];
unchecked { ++j; }
}
unchecked { ++i; }
}
return ids;
}
function _marketPrice(uint256 _id) internal view returns (uint256) {
uint256 nominator = terms[_id].controlVariable * _debtRatio(_id);
return nominator / (10**metadatas[_id].quoteDecimals);
}
function _debtRatio(uint256 id) internal view returns (uint256) {
uint256 nominator = markets[id].totalDebt * (10**metadatas[id].quoteDecimals);
return nominator / _treasury.baseSupply();
}
function _controlDecay(uint256 id)
internal
view
returns (
uint64 decay,
uint48 secondsSince,
bool active
)
{
Adjustment memory info = adjustments[id];
if (!info.active) return (0, 0, false);
secondsSince = uint48(block.timestamp) - info.lastAdjustment;
active = secondsSince < info.timeToAdjusted;
decay = active
? (info.change * secondsSince) / info.timeToAdjusted
: info.change;
}
}