225 lines
7.0 KiB
Solidity
225 lines
7.0 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import "@openzeppelin-contracts/utils/Address.sol";
|
|
import "@openzeppelin-contracts/token/ERC20/extensions/ERC20Permit.sol";
|
|
|
|
import "./interfaces/IGHST.sol";
|
|
import "./interfaces/ISTNK.sol";
|
|
import "./interfaces/IStaking.sol";
|
|
|
|
contract Stinky is ISTNK, ERC20Permit {
|
|
uint256 private constant INITIAL_SHARES_SUPPLY = 5 * 10**15;
|
|
uint256 private constant TOTAL_SHARES = type(uint256).max - (type(uint256).max % INITIAL_SHARES_SUPPLY);
|
|
uint256 private constant MAX_SUPPLY = type(uint128).max;
|
|
|
|
uint256 internal immutable _internalIndex;
|
|
address internal _initializer;
|
|
|
|
uint256 private _sharesPerUnit;
|
|
uint256 private _totalSupply;
|
|
|
|
address public staking;
|
|
address public ghst;
|
|
address public treasury;
|
|
|
|
mapping(address => uint256) public override debtBalances;
|
|
mapping(address => uint256) private _shares;
|
|
mapping(address => mapping(address => uint256)) private _allowedValue;
|
|
|
|
constructor(uint256 usedIndex) ERC20("Stinky", "STNK") ERC20Permit("Stinky") {
|
|
_initializer = msg.sender;
|
|
_internalIndex = usedIndex;
|
|
_totalSupply = INITIAL_SHARES_SUPPLY;
|
|
_sharesPerUnit = TOTAL_SHARES / INITIAL_SHARES_SUPPLY;
|
|
}
|
|
|
|
function initialize(
|
|
address _staking,
|
|
address _treasury,
|
|
address _ghst
|
|
) external {
|
|
if (msg.sender != _initializer) revert NotInitializer();
|
|
|
|
staking = _staking;
|
|
treasury = _treasury;
|
|
ghst = _ghst;
|
|
|
|
_shares[_staking] = TOTAL_SHARES;
|
|
_initializer = address(0);
|
|
|
|
emit Transfer(address(0), _staking, _totalSupply);
|
|
emit LogStakingContractUpdated(_staking);
|
|
}
|
|
|
|
function rebase(
|
|
uint256 supplyDelta,
|
|
uint256 epoch
|
|
) public override returns (uint256 newTotalSupply) {
|
|
if (msg.sender != staking) revert NotStakingContract();
|
|
uint256 currentCirculatingSupply = circulatingSupply();
|
|
uint256 rebasePercent = 0;
|
|
|
|
if (supplyDelta == 0) {
|
|
newTotalSupply = _totalSupply;
|
|
} else {
|
|
if (currentCirculatingSupply > 0) {
|
|
supplyDelta = supplyDelta * _totalSupply / currentCirculatingSupply;
|
|
rebasePercent = supplyDelta * 1e18 / currentCirculatingSupply;
|
|
}
|
|
newTotalSupply = _totalSupply + supplyDelta;
|
|
}
|
|
|
|
if (newTotalSupply > MAX_SUPPLY) {
|
|
newTotalSupply = MAX_SUPPLY;
|
|
}
|
|
|
|
_totalSupply = newTotalSupply;
|
|
_sharesPerUnit = TOTAL_SHARES / newTotalSupply;
|
|
|
|
emit LogSupply(epoch, newTotalSupply);
|
|
emit LogRebase(epoch, rebasePercent, index());
|
|
}
|
|
|
|
function transfer(
|
|
address to,
|
|
uint256 value
|
|
) public override(IERC20, ERC20) returns (bool) {
|
|
_transferInner(msg.sender, to, value);
|
|
return true;
|
|
}
|
|
|
|
function transferFrom(
|
|
address from,
|
|
address to,
|
|
uint256 value
|
|
) public override(IERC20, ERC20) returns (bool) {
|
|
_spendAllowance(from, msg.sender, value);
|
|
_transferInner(from, to, value);
|
|
return true;
|
|
}
|
|
|
|
function approve(
|
|
address spender,
|
|
uint256 value
|
|
) public override(IERC20, ERC20) returns (bool) {
|
|
_approve(msg.sender, spender, value, true);
|
|
return true;
|
|
}
|
|
|
|
function increaseAllowance(
|
|
address spender,
|
|
uint256 imbalance
|
|
) public override returns (bool) {
|
|
imbalance = imbalance + _allowedValue[msg.sender][spender];
|
|
_approve(msg.sender, spender, imbalance, true);
|
|
return true;
|
|
}
|
|
|
|
function decreaseAllowance(
|
|
address spender,
|
|
uint256 imbalance
|
|
) public override returns (bool) {
|
|
uint256 prevAllowance = _allowedValue[msg.sender][spender];
|
|
imbalance = imbalance < prevAllowance ? prevAllowance - imbalance : 0;
|
|
_approve(msg.sender, spender, imbalance, true);
|
|
return true;
|
|
}
|
|
|
|
function decimals() public pure override returns (uint8) {
|
|
return 9;
|
|
}
|
|
|
|
function totalSupply() public view override(IERC20, ERC20) returns (uint256) {
|
|
return _totalSupply;
|
|
}
|
|
|
|
function balanceOf(
|
|
address who
|
|
) public view override(IERC20, ERC20) returns (uint256) {
|
|
return _shares[who] / _sharesPerUnit;
|
|
}
|
|
|
|
function allowance(
|
|
address owner,
|
|
address spender
|
|
) public view override(IERC20, ERC20) returns (uint256) {
|
|
return _allowedValue[owner][spender];
|
|
}
|
|
|
|
function sharesForBalance(uint256 amount) public view override returns (uint256) {
|
|
return amount * _sharesPerUnit;
|
|
}
|
|
|
|
function balanceForShares(uint256 shares) public view override returns (uint256) {
|
|
return shares / _sharesPerUnit;
|
|
}
|
|
|
|
function toGhst(uint256 amount) external view override returns (uint256) {
|
|
return IGHST(ghst).balanceTo(amount);
|
|
}
|
|
|
|
function fromGhst(uint256 amount) external view override returns (uint256) {
|
|
return IGHST(ghst).balanceFrom(amount);
|
|
}
|
|
|
|
function circulatingSupply() public view override returns (uint256) {
|
|
return _totalSupply +
|
|
IGHST(ghst).balanceFrom(IERC20(ghst).totalSupply()) +
|
|
IStaking(staking).supplyInWarmup() -
|
|
balanceOf(staking);
|
|
}
|
|
|
|
function index() public view override returns (uint256) {
|
|
return balanceForShares(_internalIndex);
|
|
}
|
|
|
|
function changeDebt(
|
|
uint256 amount,
|
|
address debtor,
|
|
bool add
|
|
) external override {
|
|
if (msg.sender != treasury) revert NotTreasury();
|
|
uint256 debtBalance = debtBalances[debtor];
|
|
debtBalance = add ? debtBalance + amount : debtBalance - amount;
|
|
if (debtBalance > balanceOf(debtor)) revert InsufficientBalance();
|
|
debtBalances[debtor] = debtBalance;
|
|
}
|
|
|
|
function _transferInner(
|
|
address from,
|
|
address to,
|
|
uint256 value
|
|
) internal {
|
|
uint256 sharesValue = value * _sharesPerUnit;
|
|
_shares[from] = _shares[from] - sharesValue;
|
|
_shares[to] = _shares[to] + sharesValue;
|
|
if (balanceOf(from) < debtBalances[from]) revert DebtExists();
|
|
emit Transfer(from, to, value);
|
|
}
|
|
|
|
function _spendAllowance(address owner, address spender, uint256 value) internal override {
|
|
uint256 currentAllowance = allowance(owner, spender);
|
|
if (currentAllowance != type(uint256).max) {
|
|
if (currentAllowance < value) {
|
|
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
|
|
}
|
|
unchecked {
|
|
_approve(owner, spender, currentAllowance - value, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
function _approve(
|
|
address owner,
|
|
address spender,
|
|
uint256 value,
|
|
bool emitEvent
|
|
) internal override {
|
|
if (owner == address(0)) revert ERC20InvalidApprover(address(0));
|
|
if (spender == address(0)) revert ERC20InvalidSpender(address(0));
|
|
_allowedValue[owner][spender] = value;
|
|
if (emitEvent) emit Approval(owner, spender, value);
|
|
}
|
|
}
|