ghost-dao-contracts/src/Staking.sol
Uncle Fatso 46b33b4c75
initial push for smart-contracts
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-04-28 14:17:04 +03:00

190 lines
5.7 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin-contracts/token/ERC20/IERC20.sol";
import "./types/GhostAccessControlled.sol";
import "./interfaces/ISTNK.sol";
import "./interfaces/IGHST.sol";
import "./interfaces/IDistributor.sol";
import "./interfaces/IStaking.sol";
contract GhostStaking is IStaking, GhostAccessControlled {
using SafeERC20 for IERC20;
using SafeERC20 for ISTNK;
using SafeERC20 for IGHST;
address public immutable ftso;
address public immutable stnk;
address public immutable ghst;
uint48 public warmupPeriod;
Epoch public epoch;
address public distributor;
uint256 public sharesInWarmup;
mapping(address => Claim) public warmupInfo;
constructor(
address _ftso,
address _stnk,
address _ghst,
uint48 _epochLength,
uint48 _firstEpochNumber,
uint48 _firstEpochTime,
address _authority
) GhostAccessControlled(IGhostAuthority(_authority)) {
ftso = _ftso;
stnk = _stnk;
ghst = _ghst;
epoch = Epoch({
length: _epochLength,
number: _firstEpochNumber,
end: _firstEpochTime,
distribute: 0
});
}
function stake(
uint256 amount,
address to,
bool isRebase,
bool isClaim
) external override returns (uint256 returnAmount) {
returnAmount = amount + rebase();
IERC20(ftso).safeTransferFrom(msg.sender, address(this), amount);
if (isClaim && warmupPeriod == 0) {
returnAmount = _send(returnAmount, to, isRebase);
} else {
Claim storage info = warmupInfo[to];
if (info.lock && to != msg.sender) revert ExternalDepositsLocked();
info.deposit += returnAmount;
info.shares += ISTNK(stnk).sharesForBalance(returnAmount);
info.expiry = epoch.number + warmupPeriod;
sharesInWarmup += ISTNK(stnk).sharesForBalance(returnAmount);
}
}
function claim(address to, bool isRebase) public override returns (uint256 claimedAmount) {
Claim memory info = warmupInfo[to];
if (info.lock && to != msg.sender) revert ExternalDepositsLocked();
if (epoch.number >= info.expiry && info.expiry > 0) {
delete warmupInfo[to];
sharesInWarmup -= info.shares;
claimedAmount = _send(ISTNK(stnk).balanceForShares(info.shares), to, isRebase);
}
}
function forfeit() external override returns (uint256) {
Claim memory info = warmupInfo[msg.sender];
delete warmupInfo[msg.sender];
sharesInWarmup -= info.shares;
IERC20(ftso).safeTransfer(msg.sender, info.deposit);
return info.deposit;
}
function toggleLock() external override {
warmupInfo[msg.sender].lock = !warmupInfo[msg.sender].lock;
}
function unstake(
uint256 amount,
address to,
bool isTrigger,
bool isRebase
) external override returns (uint256) {
amount += isTrigger ? rebase() : 0;
if (isRebase) {
ISTNK(stnk).safeTransferFrom(msg.sender, address(this), amount);
} else {
IGHST(ghst).burn(msg.sender, amount);
amount = IGHST(ghst).balanceFrom(amount);
}
if (amount > IERC20(ftso).balanceOf(address(this))) revert InsufficientBalance();
IERC20(ftso).safeTransfer(to, amount);
return amount;
}
function wrap(
address to,
uint256 amount
) external override returns (uint256 balance) {
ISTNK(stnk).safeTransferFrom(msg.sender, address(this), amount);
balance = IGHST(ghst).balanceTo(amount);
IGHST(ghst).mint(to, balance);
}
function unwrap(
address to,
uint256 amount
) external override returns (uint256 balance) {
IGHST(ghst).burn(msg.sender, amount);
balance = IGHST(ghst).balanceFrom(amount);
ISTNK(stnk).safeTransfer(to, balance);
}
function rebase() public override returns (uint256 bounty) {
if (epoch.end <= block.timestamp) {
ISTNK(stnk).rebase(epoch.distribute, epoch.number);
unchecked {
epoch.end += epoch.length;
++epoch.number;
}
if (distributor != address(0)) {
IDistributor(distributor).distribute();
bounty = IDistributor(distributor).retrieveBounty();
}
uint256 balance = IERC20(ftso).balanceOf(address(this));
uint256 extra = ISTNK(stnk).circulatingSupply() + bounty;
epoch.distribute = balance > extra ? balance - extra : 0;
}
}
function setDistributor(address _distributor) external onlyGovernor {
distributor = _distributor;
emit DistributorSet(_distributor);
}
function setWarmupPeriod(uint256 _warmupPeriod) external onlyGovernor {
warmupPeriod = uint48(_warmupPeriod);
emit WarmupSet(_warmupPeriod);
}
function index() public view override returns (uint256) {
return ISTNK(stnk).index();
}
function supplyInWarmup() public view override returns (uint256) {
return ISTNK(stnk).balanceForShares(sharesInWarmup);
}
function _send(
uint256 amount,
address to,
bool isRebase
) internal returns (uint256) {
if (isRebase) {
ISTNK(stnk).safeTransfer(to, amount);
return amount;
} else {
uint256 balanceTo = IGHST(ghst).balanceTo(amount);
IGHST(ghst).mint(to, balanceTo);
return balanceTo;
}
}
}